파이썬3번9 MIN
Python 2 vs 3: 왜 모두가 3으로 넘어왔는가
Python 2와 3의 핵심 차이를 코드로 비교합니다. print, 나누기, 유니코드, range의 변화와 마이그레이션 도구를 설명합니다.
이 글은 PALDYN Tech Blog의 글입니다.원문 보기
지난 글에서 Python 2와 3이 10년 넘게 공존했던 복잡한 역사를 살펴봤다. 이번 글에서는 두 버전의 차이를 코드 수준에서 구체적으로 비교한다. Python 2는 2020년에 공식 지원이 종료되었지만, 레거시 코드베이스나 마이그레이션 작업을 위해 여전히 차이를 이해해야 하는 경우가 있다. 그리고 왜 Python 3의 설계가 더 올바른지를 이해하면 언어 자체에 대한 이해가 깊어진다.
가장 눈에 띄는 변화: print
Python을 처음 배울 때 가장 먼저 만나는 함수가 print다. 이것이 Python 2와 3 사이에서 가장 상징적인 변화다.
# Python 2
print "Hello, World!" # 문(statement)
print "a", "b", "c" # 쉼표로 여러 값 출력
print >> sys.stderr, "error" # 파일 리디렉션
# Python 3
print("Hello, World!") # 함수(function)
print("a", "b", "c") # 동일하게 동작
print("error", file=sys.stderr) # 키워드 인수 사용
print가 문(statement)에서 함수(function)로 바뀐 것은 단순한 괄호 추가가 아니다. 함수가 되면서 변수에 할당하거나, 다른 함수에 인수로 넘기거나, sep·end·file·flush 같은 키워드 인수를 사용할 수 있게 되었다.
# Python 3 print()의 추가 기능
print("a", "b", "c", sep="-") # → a-b-c
print("loading", end="...") # → loading... (줄바꿈 없음)
print(1, 2, 3, sep="\n") # 각 값을 새 줄에
import sys
print("오류!", file=sys.stderr) # stderr로 출력
정수 나누기: 가장 흔한 버그의 원인
Python 2에서 정수끼리 나누면 결과가 항상 정수였다. 이 동작이 수많은 버그의 원인이었다.
# Python 2
5 / 2 # → 2 (floor division, 소수점 버림)
5 / 2.0 # → 2.5 (float가 하나라도 있으면 float 결과)
# Python 3
5 / 2 # → 2.5 (항상 float 반환)
5 // 2 # → 2 (명시적 floor division)
5 % 2 # → 1 (나머지)
Python 3에서 /는 항상 실수 나누기를 하고, //는 명시적으로 버림 나누기를 한다. 의도를 코드에 명확히 드러내게 한 것이다. Python 2에서 5 / 2가 2인지 2.5인지를 인수 타입으로 판단해야 했던 문제가 사라졌다.
# Python 3에서 안전한 정수 나누기
count = 7
half = count // 2 # 3 (정수 보장)
ratio = count / 2 # 3.5 (실수)
# 나누기 결과를 정수로 원할 때
result = int(count / 2) # 3
result = count // 2 # 3 (더 명확)
문자열과 유니코드
Python 2에서 문자열 처리는 악몽이었다. str이 기본적으로 bytes였고, 유니코드 문자열은 u"..." 접두사를 붙여야 했다.
# Python 2
s1 = "hello" # bytes (str)
s2 = u"안녕" # unicode
type(s1) # <type 'str'>
type(s2) # <type 'unicode'>
# 한글 출력 시 오류 발생 가능
print "안녕" # UnicodeDecodeError 발생 가능
# Python 3
s1 = "hello" # str (항상 Unicode)
s2 = "안녕" # 동일하게 str
b1 = b"hello" # bytes (바이트열)
type(s1) # <class 'str'>
type(b1) # <class 'bytes'>
Python 3에서 str은 항상 Unicode다. 전 세계 모든 문자를 아무 접두사 없이 사용할 수 있다. 바이트열이 필요할 때는 b"..." 접두사를 명시적으로 붙인다. 이 변화 덕분에 인코딩 관련 버그가 대폭 줄었다.
# Python 3의 인코딩 처리
text = "안녕하세요"
encoded = text.encode("utf-8") # bytes
decoded = encoded.decode("utf-8") # str
print(decoded) # 안녕하세요
# 파일 읽기 시 인코딩 명시
with open("file.txt", "r", encoding="utf-8") as f:
content = f.read()
range()와 xrange()
Python 2에서 range()는 리스트를 즉시 생성했고, xrange()는 이터레이터를 반환했다. 두 함수가 공존해서 초보자를 혼란스럽게 했다.
# Python 2
range(5) # → [0, 1, 2, 3, 4] (리스트 즉시 생성)
xrange(5) # → xrange(5) (이터레이터, 메모리 효율)
# range(10000000) → 1000만 개 리스트를 메모리에 올림
# xrange(10000000) → 필요할 때마다 하나씩 생성
# Python 3
range(5) # → range(0, 5) (항상 이터레이터)
list(range(5)) # → [0, 1, 2, 3, 4] (리스트로 변환)
# 10000000번 루프: 메모리 걱정 없음
for i in range(10_000_000):
pass
Python 3에서는 xrange()가 제거되고 range()가 이터레이터 방식으로 통일되었다. range(10_000_000)도 메모리를 거의 쓰지 않는다. 실제 숫자가 필요할 때만 하나씩 계산하기 때문이다.
input()과 raw_input()
# Python 2
x = raw_input("값 입력: ") # 항상 str 반환
y = input("값 입력: ") # eval() 후 반환 (보안 문제!)
# Python 3
x = input("값 입력: ") # 항상 str 반환 (raw_input 동작)
y = int(input("숫자: ")) # 명시적 형변환 필요
Python 2의 input()은 사용자 입력을 eval()로 처리했다. os.system("rm -rf /") 같은 입력도 실행될 수 있었다. Python 3에서는 input()이 항상 문자열을 반환하도록 바뀌었고, Python 2의 안전하지 않은 input()은 제거되었다.
예외 처리 문법
# Python 2
try:
risky()
except ValueError, e: # 쉼표로 예외 변수 바인딩
print e
# Python 3
try:
risky()
except ValueError as e: # as 키워드 사용
print(e)
Python 2의 except Error, e: 문법은 except (A, B): 처럼 여러 예외를 묶는 문법과 혼동되었다. Python 3에서 as 키워드로 명확하게 분리되었다.
super() 사용법
# Python 2
class Child(Parent):
def __init__(self):
super(Child, self).__init__() # 클래스와 self를 명시
# Python 3
class Child(Parent):
def __init__(self):
super().__init__() # 인수 불필요
마이그레이션 도구
레거시 Python 2 코드를 3으로 옮겨야 한다면 도구를 활용하자.
# 2to3: Python 2 코드를 3으로 자동 변환
2to3 -w my_script.py
# pyupgrade: 최신 Python 3 문법으로 업그레이드
pip install pyupgrade
pyupgrade --py310-plus my_file.py
# modernize: 2/3 호환 코드로 변환
pip install modernize
python-modernize -w my_file.py
자동 변환 도구가 모든 것을 처리하지는 못한다. 특히 문자열 인코딩, 의존 라이브러리의 3 호환성은 수동으로 확인해야 한다.
결론: 지금 시작한다면 Python 3
새로운 프로젝트를 시작한다면 Python 3를 사용해야 한다. Python 2는 2020년에 공식 지원이 종료되었고, 보안 패치도 더 이상 제공되지 않는다. 주요 라이브러리(NumPy, pandas, Django 등) 모두 Python 2 지원을 종료했다.
Python 3의 변화들은 단순히 "다르게 만들기 위한 변화"가 아니다. 각각의 변화에는 명확한 이유가 있다. print를 함수로 만든 것은 일관성 때문이고, 나누기를 float으로 한 것은 예상치 못한 버그를 방지하기 위해서다. 유니코드를 기본으로 한 것은 국제화 시대에 맞는 올바른 결정이었다.
다음 글에서는 CPython만이 Python의 유일한 구현체가 아님을 살펴본다. PyPy, Jython, IronPython 등 다양한 구현체와 그 용도를 알아볼 것이다.
지난 글: Python의 탄생과 역사: Guido에서 3.12까지
다음 글: CPython과 PyPy: Python 구현체의 세계
읽어주셔서 감사합니다. 😊

