[python] 파이썬 버전별 주요 변화 - 3.5부터 3.10까지
Python 버전별 차이점
개요
최근 들어 가장 많은 주목을 받고 있는 언어는 단연 Python일 것이다.
프로그래밍을 배우지 않았어도 감각적으로 이해할 수 있는 쉬운 코드,
실제로도 매우 낮은 진입 장벽,
그에 반에 매우 압도적인 범용성과 실용성…
분명 다른 언어들에 비해 확실히 느리다는 매우 명확하고 치명적인 단점이 있으나,
위의 장점에서 오는 효용이 단점을 충분히 커버하고도 남는다.
특히, 3.11에서 비약적인 성능의 발전과 다른 언어와의 조합을 통해 속도의 문제는 점차 해결되고 있다.
2023년이 끝나가는 현재, 3.12.x 버전이 개발 중이다.
내가 처음 프로그래밍을 접했던 때에, 3.9.x가 개발 중이었던 때니,
대단히 빠른 속도로 발전해나가고 있는 것 같다.
나는 SSAFY 교육생 시절에도, 코치 시절에도 Python 예찬론자였다. (물론 백엔드니까 JAVA도 다 했다.)
그럼에도 Python의 버전별 차이를 그다지 생각했던 적은 없는 것 같다.
대략 3.7 이후부터는 어지간해서는 버전 충돌 문제가 발생하지 않고,
까다로운 라이브러리가 있으면(보통은 AI 모델을 학습시킬 때였다) 해당 라이브러리의 requirement에 무조건 맞추다보니 버전을 생각해볼 겨를이 없었다.
그래서 이참에 한번 정리해보려고 한다.
3.12는 현재 개발중이니, 따로 포스팅을 진행할 예정이다.
비동기를 지원하기 시작한 3.5부터 3.10까지 한번 정리해보고자 한다!
Python의 버전 표기법
Python의 버전은 일반적으로 3.9.13
과 같이 표기된다.
이는 각각, Major.Minor.Micro
를 의미한다.
현재
Major
버전은 3으로, 2008년 12월 3일 출시되었다.
대격변 수준의 변화가 있을 때 업데이트 될 것이다.
그러나 현재로써는 업데이트 계획이 없으며, 앞으로도 없을 것으로 예상한다.
Python의 아버지인 귀도 반 로섬이 Python4를 대놓고 부정했다.Minor
버전 약 1년에서 1년 6개월 주기를 갖고 업데이트 되고 있다.
2년만에 3.10에서 3.12까지 올라왔으니 말이다. 일반적으로 새로운 기능이 추가되거나, 괄목할만한 성능향상이 있는 업데이트라고 한다.Micro
버전 돌아보면 나와있다…
버그 픽스, 최적화, 보안 업데이트 등, 기능과 관련된 부분보단 안정성 향상의 목적이 크다.
보다 정확한 릴리즈 주기를 확인하려면 해당 PEP 문서를 확인하자
Python 3.5 - 2015.09 릴리즈
비동기 지원
이 때문에 혹자는 python 3.5부터는 4로 봐야한다고 하는 사람이 있을 정도로 큰 변화가 생긴 것이다!
GIL
(Global Interpreter Lock)으로 인해, Python에서는 멀티 스레딩에서 오히려 속도가 느린 문제가 있었고,
이로 이해 비동기 처리에도 문제가 생겼던 것이다.
비동기를 지원하며 코루틴이 추가되었고, async
, await
를 활용한 비동기 프로그래밍이 가능하게 되었다!
일반적인 가정용 PC가 듀얼코어 조차 유물이 되고, 쿼드코어가 거의 일반적으로 사용되던 시기인데도 어째서 이렇게 늦게 비동기를 지원하게 되었을까?
기본적으로 Python은 CPU 바운드 위주의 작업(CPU 사용 기간이 I/O wating보다 긴)을 위해 사용하지 않는다.
훨씬 가볍고 빠르게 돌아가는 수많은 언어가 있는데, 굳이 Python을 쓸 필요가 없다.
AI가 대두되고, Web Framework에서도 비동기 처리가 중요해질 때쯤, 필요에 따라 업데이트를 했다고 생각한다.
Type Hint (타입 힌트)
1
2
3
def plus(a : int, b : int) -> int:
res = a + b
return res
FastAPI를 쓰기 전까지는 Python을 많이 쓴 사람도 타입 힌트에 대해 잘 모르는 경우가 많다. (보통 알아도 잘 안 쓰더라.)
Java에서는 변수 생성 시 타입을 지정해주는 것이 필수지만, 동적 정형 언어인 Python은 변수 생성 시 타입 지정이 강제되지 않는다.
그래서 타입 힌팅을 해놔도 사실 대부분 무시할 수 있다. 강제성이 없는 말 그대로 힌트일 뿐이므로…
대부분의 교재나 블로그에서는 타입 힌트를 딱히 다루지 않는다.
주석으로 설명하는 게 더욱 자유롭기 때문인듯하다. 형식 없이 자유롭게 설명이 가능하니까…
다만, FastAPI에서는 타입 힌트가 어느 정도 강제성을 가지기 때문에, 거의 필수적인 문법이다.
또한 버전이 올라가면서 타입 힌트 기능을 대폭 추가, 개선해 매우 유용한 기능으로 만들었으며,
기본 문법이나 클래스를 활용할 때도 강제성을 가진 타입 힌트들이 추가되었다.
이외에 행렬곱 연산자(a @ b)
, % 문자열 포매팅
, venv
모듈 추가 등의 업데이트가 있었다.
Python 3.6 - 2016.12 릴리즈
f-string 포매팅(Formatted String Literals) 도입
python의 진입 장벽을 보다 낮춰주는, f-string 포매팅이 도입되었다.
1
2
3
name = "정인모"
age = 28
"내 이름은 %s이며, 나이는 %d살 입니다." % (name, age)
%s
따위의 형식으로 위치와 타입을 지정하고,
문자열 뒤에 다시 변수(또는 해당 타입의 데이터 자체)를 적어줘야 했으나,
1
2
3
name = "정인모"
age = 28
f"내 이름은 {name}이며, 나이는 {age}살 입니다."
와 같은 형식의, 매우 간결하면서 가독성있고 효율적인 방식이 추가된 것이다.
dictionary 성능 향상
Dictionary(사전)의 구현 방법을 수정하여, 3.5에 비해 약 20 ~ 25%의 메모리를 적게 소모하도록 업그레이드되었다.
숫자 타입의 단위 명시 기능 추가
매우 흥미로운 변경사항이 있었는데,
Python을 매우 좋아하는 나조차도 모르는 내용이었다.
만약 1조의 숫자를 직접 써야한다면 어떻게 해야할까?
1조는 10^12이다. 즉, 0이 12개가 붙는다는 것이다.
1000000000000
라고 직접 입력하면, 따로 주석이 붙어있지 않는 한 0을 세야 한다…
1.000.000.000.000
이나 1,000,000,000,000
은 당연히 숫자형으로 인식할 수 없다.
그래서 Python은 _
를 ,
대용으로 쓸 수 있도록 만들었다!
1
2
test_int = 1_000_000_000_000
print(type(test_int)) -> "<class 'int'>"
이제부터 자릿수가 긴 숫자를 하드코딩 해야할 때 언더바를 넣도록 하자! 근데 그냥 주석이 편할 것 같다
이외에도 여러 추가사항이 있는데,
3.5 버전에서 비동기 처리가 추가된 만큼 이와 관련된 기능 업데이트가 많은 것 같다.
비동기 컴프리헨션
, 비동기 제네레이터
, 변수 어노테이션 문법
등이 추가 되었다고 한다!
python 3.7 2018.06 릴리즈
기본 인코딩을 ASCII에서 UTF-8로 변경
아주 오래된 레거시 python 코드나 솔루션을 보다보면, 문서의 최상단에 이상한 게 있다.
1
2
3
# -*- coding: utf-8 -*-
변수 = "변수에 누가 한글을 씁니까"
한글이 포함된 문서는 반드시 저 주석을 삽입해줘야 했다.
python은 기본 인코딩이 ASCII
였다.
즉, 한글을 인식할 수 없으므로 강제로 UTF-8
로 바꿔야 했던 것이다.
3.7
부터는 기본적으로 UTF-8
인코딩이 강제되므로, 더 이상 쓸 일이 없는 구시대의 유물이다.
저것이 들어간 코드는 python2나, 3.7 이전의 고리짝 코드란 것이다.동료가 저게 들어간 코드를 들고 오면 주의하자
dictionary
타입의 객체 삽입 순서 보전
이전까지는 공식적으로 그렇다
가 아니었으나, 공식적으로 못을 박았다.
딕셔너리에 임의의 인덱스로 접근할 수 있다는 것이 아니다!!!
컴프리헨션 또는 루프 시에 객체에 접근하는 순서가 객체 삽입의 순서로 이루어짐을 뜻한다.
즉, 삽입과 추출 순서를 완벽히 통제할 수 있으면 인덱스가 유효하다 할 수 있는 것이다.
이외의 주요 변경점으로 dataclass 데코레이터
, 타입 힌트 기능 강화
, 에러 메세지 강화
등의 변화가 있었다.
이 중, dataclass 데코레이터
는 상당히 유용해보이는 기능이므로 따로 다뤄볼 예정이다.
Python 3.8 - 2019.10 릴리즈
바다코끼리 표현식 - 대입 표현식
중대한 기능들은 아니지만, 디버깅과 유지보수 측면에서 유용한 기능들이 대거 추가되었다.
바다코끼리 표현식(Walrus operator)의 정식 명칭은 대입 표현식(Assignment expressions)이다.
왜 바다코끼리인가? 표현식이 :=
이다. 귀여워.
용법은 다음과 같다.
1
2
3
4
5
6
7
# len()함수 호출과 동시에 할당
if (n := len(a)) > 10:
print(f"List is too long ({n} elements, expected <= 10)")
# loof 탈출 조건으로 활용
while (block := f.read(256)) != '':
print(block)
실로 간단하지만, 보기 깔끔하고 편리하지 않을 수 없다.
위치 전용 매개 변수 추가
위치 전용 매개 변수는 처음에는 조금 아리송했다.
/
을 기준으로 이전 매개 변수는 키워드 지정이 불가능하고, 오로지 위치로만 인수를 전달할 수 있다.
따라서, /
좌측에는 **karg
를 선언할 수 없게 된다.
*
을 기준으로 이후 매개 변수는 반드시 키워드 지정을 해주어야 한다.
1
2
3
4
def f(a, b, /, c, d, *, e, f):
print(a, b, c, d, e, f)
f(1, 2, 3, d=4, e=5, f=6)
와 같이 쓸 수 있다.
/
좌측의 매개 변수인 a
에 대해서는 a=1
과 같이 할당할 수 없으며,
*
우측의 매개 변수인 e
에 대해서는 반드시 e=5
와 같이 할당해줘야 한다.
이러한 기능이 필요한 이유는
함수의 매개 변수명이 바뀔 수 있는 함수에 대해 키워드 지정을 할 경우,
해당 코드에 대한 유지보수가 힘들기 때문!
len()
은 매개변수 키워드가 org
라고 한다. len(org=x)
라고 써놓은 코드가 나중에 매개 변수명 변경으로 인해 사용이 불가한 경우를 생각해보자…
f-string formating의 변수 설명자
매우 단순하다. f-string 포매팅으로 표현한 변수의 설명해주는 것이다.
1
2
3
4
name = "정인모"
print(f"{name=}는 제 이름입니다.")
>>> name='정인모'는 제 이름입니다.
와 같은 식으로, 변수명과 객체의 데이터형을 보여주는 것이다.
이외에도 꾸준히 여러가지 성능 개선과 디버깅이 진행되었다.
Python 3.9 - 2020.10 릴리즈
Python2의 지원종료
python 3.9버전을 끝으로, Python2버전에 대한 지원을 완전히 종료했다.
더 이상 python2 버전의 코드가 제대로 작동하리란 보장이 없어진 것이다…
3가 출시된 지 12년 만에 이전 Major 버전의 지원을 종료했는데,
그 여파가 너무나 커 Python 4 출시 생각이 전혀 없다는 것을 보면, 12년 조차도 부족한 시간이었나보다.
새로운 구문 분석기(parser) 도입
새로운 구문 분석기를 채택하여, 3.10 버전부터는 해당 구문 분석기를 활용한 Python이 출시될 것이라 예고했다.
이는 우리처럼 Python을 다운받아 사용하는 사용자 입장에서는 특별히 와닿지 않는 내용이지만,
Python 자체를 설계하거나, low한 레벨의 모듈을 제작하는 사람들에게 상당한 변화라고 한다.
이 덕분인지, 3.10 버전 이후부터는 python의 성능이 상당한 속도로 개선되고 있다고 한다.
dictionary 병합 연산자
Python이 dict 타입에 얼마나 진심인지, 이번에는 병합 연산자가 새로 나왔다.
1
2
3
4
5
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
merged_dict = dict1 | dict2
# 키가 겹치는 경우, 밸류를 튜플로 만들거나 하는 것이 아닌 뒤(우측)에 온 온 딕셔너리의 키:밸류 쌍이 저장된다.
와 같이 사용할 수 있으며, 이전의 dict.update
와 {**d1, **d2}
를 보완한다.
python 3.10 - 2021. 10 릴리즈
구문분석기 변경을 통한 편의성 증대
어느 순간부터 Python의 에러 메시지를 읽기가 쉬워졌다
3.9에서 얘기한 구문분석기의 변경으로 더욱 유연한 에러 포착과 메시지 생성이 가능해졌다고 한다!
예가 너무나 많지만, PEP 문서의 예시를 하나 가져와봤다.
1
2
3
4
5
>>> foo(x, z for z in range(10), t, w)
File "<stdin>", line 1
foo(x, z for z in range(10), t, w)
^
SyntaxError: Generator expression must be parenthesized
에서
1
2
3
4
5
>>> foo(x, z for z in range(10), t, w)
File "<stdin>", line 1
foo(x, z for z in range(10), t, w)
^^^^^^^^^^^^^^^^^^^^
SyntaxError: Generator expression must be parenthesized
이런 식으로 말이다.
이 외에도 구문 분석기 변경을 통해 굉장히 많은 변화가 있었다고 한다.
공식 DOCS에서 직접 확인해보자
Match/Case 구문 추가
Java의 Switch/Case 구문과 비슷하나, Python답게 훨씬 다양한 기능과 사용성을 제공한다.
기본적인 사용법은 다음과 같다.
1
2
3
4
5
6
7
8
9
10
def http_error(status):
match status:
case 400:
return "Bad request"
case 404:
return "Not found"
case 418:
return "I'm a teapot"
case _:
return "Something's wrong with the internet"
이러한 기본적인 사용방법 외에도 클래스와 패턴을 이용한 매칭이 가능하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Point:
x: int
y: int
def location(point):
match point:
case Point(x=0, y=0):
print("Origin is the point's location.")
case Point(x=0, y=y):
print(f"Y={y} and the point is on the y-axis.")
case Point(x=x, y=0):
print(f"X={x} and the point is on the x-axis.")
case Point():
print("The point is located somewhere else on the plane.")
case _:
print("Not a point")
if/else, for, while 수준의 규모의 기능이 추가된 것인만큼
대단히 많은 용법과 추가적인 기능과 편의성이 있다.
현재 3.10 버전 공식문서는 번역이 미비한 편이다.