파이썬 3.6의 새로운 변경사항

2023년 05월 05일

Python

# 파이썬# 3.6

📕 목차

개요

이 글은 파이썬 3.6 버전의 새로운 변경사항을 소개합니다.

파이썬 3.6 배포 주요 사항

파이썬 3.6 버전은 2016년 12월 23일에 릴리즈 되었습니다. 자세한 내용은 https://docs.python.org/3/whatsnew/3.6.html 를 참고하시기 바랍니다.

PEP 498: Formatted string literals (f-strings)

PEP 498은 새로운 종류의 문자열 리터럴을 소개합니다. 이 문자열 리터럴은 f-strings 라고 부릅니다.

이 문자열 리터럴은 대체가능한 필드들을 둘러싼 중괄호를 포함합니다. 대체 필드들은 런타임에 평가될 수 있고, format() 프로토콜을 사용하여 포매팅될 수 있는 식(expression)입니다.

name = "Fred"
f"He said his name is {name}." # => "He said his name is Fred."

width = 10
precision = 4
value = decimal.Decimal("12.34567")
f"result: {value:{width}.{precision}}" # => "result:      12.35"

PEP 526: Syntax for Variable Annotations

PEP 484는 함수의 파라미터의 타입 어노테이션을 위한 표준에 대해서 소개한 PEP입니다. 이번 PEP 526은 클래스 변수와 인스턴스 변수를 포함한 변수들의 타입에 어노테이션을 위한 파이썬 문법 추가입니다.

primes: List[int] = []

captain: str # Note: no initial value!

class Starship:
    stats: Dict[str, int] = {}

함수 어노테이션과 마찬가지로, 파이썬 인터프리터는 변수 어노테이션에 어떠한 의미도 부여하지 않고, 단지 클래스 혹은 모듈의 annotations 속성에 저장합니다.

정적 타입 언어의 변수 선언과 대조적으로, 어노테이션 문법의 목표는 추상 구문 트리와 annotations 속성을 통해 서드 파티 도구와 라이브러리를 위한 구조화된 타입 메타데이터를 명시하기 위한 쉬운 방법을 제공하는 것입니다.

여기서 말하는 서드 파티 도구들은 mypy, pytype, PyCharm, vscode 등과 같은 도구를 말합니다.

PEP 515: Underscores in Numeric Literals

PEP 515는 가독성 향상을 위해서 숫자 리터럴에 밑줄을 사용할 수 있도록 합니다.

1_000_000_000_000_000 # => 1000000000000000

0x_FF_FF_FF_FF # => 4294967295

단일 언더스코어는 숫자 사이와 베이스 접두사 (0b, 0o, 0x) 뒤에서만 사용할 수 있습니다.

문자열 포맷팅 언어 또한 ’_’ 옵션을 부동 소수점 표현 타입과 십진수 표현을 위한 천단위 구분을 위해 사용할 수 있습니다.

'{:_}'.format(1000000) # => '1_000_000'
'{:_x}'.format(0xFFFFFFFF) # => 'ffff_ffff'

PEP 525: Asynchronous Generators

PEP 492 에서는 네이티브 코루틴을 위한 지원과 함께, async / await 문법을 파이썬 3.5버전에서 소개했었습니다. 파이썬 3.5버전에서 이 새로운 문법에 대한 주목할만한 한계점은 await과 yield를 동일한 함수 내부에서 사용할 수 없다는 것입니다. 파이썬 3.6 버전에서는 이러한 제약사항을 제거하여, 비동기 제너레이터(asynchronous generator)를 정의하는 것이 가능해졌습니다.

이러한 제약이 없었을 때는 다음과 같이 작성했어야 합니다.

class Ticker:
    """Yield numbers from 0 to `to` every `delay` seconds."""

    def __init__(self, delay, to):
        self.delay = delay
        self.i = 0
        self.to = to

    def __aiter__(self):
        return self

    async def __anext__(self):
        i = self.i
        if i >= self.to:
            raise StopAsyncIteration
        self.i += 1
        if i:
            await asyncio.sleep(self.delay)
        return i

이제는 다음과 같이 작성할 수 있습니다.

async def ticker(delay, to):
    """Yield numbers from 0 to *to* every *delay* seconds."""
    for i in range(to):
        yield i
        await asyncio.sleep(delay)

PEP 530: Asynchronous Comprehensions

PEP 530을 통해서 list, set, dict 컴프리헨션과 제너레이터 식에서 async for를 사용하는 것이 추가되었습니다.

result = [i async for i in aiter() if i % 2]

await 식도 모든 종류의 컴프리헨션에서 지원됩니다.

result = [await fun() for fun in funcs if await condition()]

PEP 506: Adding A Secrets Module To The Standard Library.

The main purpose of the new secrets module is to provide an obvious way to reliably generate cryptographically strong pseudo-random values suitable for managing secrets, such as account authentication, tokens, and similar.

파이썬 3.6버전에서는 PEP 506에 따라 secrets 모듈이 추가되었습니다. 이 모듈은 암호학적으로 안전한 난수를 생성하기 위한 함수를 제공합니다.

CPython 구현 향상

  • 딕셔너리 타입은 Raymond Hettinger의 제안을 기반으로 더 간단한 표현으로 재구현 되었습니다. 이로 인해 파이썬 3.5와 비교했을 때 20% ~ 25% 더 적은 메모리를 사용하는 딕셔너리가 되었습니다.

PEP 487: Simpler customization of class creation

클래스 생성의 커스터마이징이 새로운 프로토콜로 단순화 되었습니다. 이제는 metaclass의 사용 없이 서브 클래스의 생성을 커스터마이징 할 수 있습니다. 새로운 __init_subclass__ 클래스 메소드를 사용하여 클래스 생성을 커스터마이징 할 수 있습니다. 이 클래스 메서드는 베이스 클래스에 대한 새로운 서브 클래스가 생성될때마다 호출됩니다.

class PluginBase:
    subclasses = []

    def __init__subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.subclasses.append(cls)

class Plugin1(PluginBase):
    pass

class Plugin2(PluginBase):
    pass

init_subclass() 구현체로부터 정확하게 동작하는 제로-인자 super() 호출을 허용하기 위해서, 커스텀 메타클래스는 새로운 classcell 네임스페이스 엔트리가 type.__new__로 전파되도록 해야합니다. 자세한 내용은 https://docs.python.org/3/reference/datamodel.html#class-object-creation 을 참고하세요.

PEP 487: Descriptor Protocol Enhancements

PEP 487에서는 새로운 옵셔널 __set_name__() 메서드를 포함하기 위한 디스크립터 프로토콜을 확장합니다. 새로운 클래스가 정의되었을 때, 이 새로운 메서드가 정의에 포함된 모든 디스크립터들에서 호출됩니다.

class IntField:
    def __get__(self, instance, owner):
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise ValueError(f'expecting integer in {self.name}')
        instance.__dict__[self.name] = value

    # 새로운 이니셜라이져
    def __set_name__(self, owner, name):
        self.name = name

class Model:
    int_field = IntField() # owner: Model 클래스, name: 'int_field'

PEP 519: Adding a file system path protocol

파일 시스템 경로들은 전통적으로 문자열 혹은 바이트 객체로서 표현되었는데요, 이러한 점은 파일 시스템 경로 기반으로 동작하는 코드를 작성하는 사람들이 이러한 객체들이 오직 두 타입(str, bytes) 중 하나라고 가정하게 만들었습니다. (파일 디스크립터를 나타내는 int는 파일 경로가 아니기 때문에 해당하지 않습니다.) 불행하게도 이러한 가정은 pathlib과 같은 파일 시스템 경로의 대체 객체 표현이 파이썬 표준 라이브러리를 포함한 기존 코드와 함께 동작하는 것을 방해합니다.

이러한 상황을 막기위해서, os.PathLike 를 나타내는 새로운 인터페이스가 정의되었습니다. __fspath__() 메서드를 구현함으로써, 객체는 경로를 나타낸다는 것을 알립니다. 그러면 객체는 파일 시스템 경로의 저수준 표현을 str 혹은 bytes 객체로 제공할 수 있습니다.

이것은 객체가 os.PathLike를 구현하거나 파일 시스템 경로를 나타내는 str 혹은 bytes 객체인 경우 객체가 경로와 유사하다는 것을 의미합니다. 코드는 명시적으로 str 사용하거나 또는 bytes 표현을 얻기 위해 os.fspath(), os.fsdecode(), 또는 os.fsencode()를 사용할 수 있습니다.

내장 open() 함수는 os.PathLike 객체를 처리할 수 있도록 업데이트되었고, os와 os.path 모듈의 모든 관련 함수들, 그리고 표준 라이브러리의 대부분의 다른 함수들과 클래스들도 업데이트되었습니다. os.DirEntry 클래스와 pathlib의 관련 클래스들도 os.PathLike를 구현하도록 업데이트되었습니다.

파일 시스템 경로에서 동작하기 위한 기본적인 함수들을 업데이트 하는 것은 서드 파티 코드들이 코드의 변경 없이 암시적으로 모든 path-like 객체들을 지원하는 것을 기대합니다. 또는 최소한의 변경(예: 코드의 시작 부분에서 os.fspath()를 호출)으로 지원할 수 있도록 합니다.

import pathlib

with open(pathlib.Path("README")) as f:
    contents = f.read()

import os.path
os.path.splitext(pathlib.Path("some_file.txt"))
# output: ('some_file', '.txt')

os.path.join("/a/b", pathlib.Path("c"))
# output: '/a/b/c'

import os
os.fspath(pathlib.Path("some_file.txt"))
# output: 'some_file.txt'

PEP 495: Local Time Disambiguation

대부분의 세계 지역에서, 로컬 시계가 변경되는 경우가 있었고 이것은 앞으로도 반복될 일이라고 합니다. 이러한 경우 로컬 시계가 같은 날 같은 시간을 두 번 표시하는 경우가 발생하는데요, 이러한 상황에서 로컬 시계에 표시된 정보는 이러한 상황을 구별하기 위한 정보가 부족합니다.

그래서, PEP 495는 로컬 시간에 나타나는 동일한 두 시간을 구분하기 위해서 새로운 fold 속성을 datetime.datetime과 datetime.time 클래스의 인스턴스에 추가합니다.

u0 = datetime(2016, 11, 6, 4, tzinfo=timezone.utc)
for i in range(4):
    u = u0 + i*HOUR
    t = u.astimezone(Eastern)
    print(u.time(), 'UTC =', t.time(), t.tzname(), t.fold)

# 04:00:00 UTC = 00:00:00 EDT 0
# 05:00:00 UTC = 01:00:00 EDT 0
# 06:00:00 UTC = 01:00:00 EST 1
# 07:00:00 UTC = 02:00:00 EST 0

PEP 529: Change Windows filesystem encoding to UTF-8

파일시스템 경로를 나타내는 것은 bytes를 사용하는 것보다 str(Unicode)를 사용할 때 더 잘 동작합니다. 하지만, 특정 상황에서는 bytes를 사용하는 것이 충분하고 적합한 경우가 있습니다.

Python 3.6 버전 이전에는, 윈도우 운영체제에서 bytes 경로를 사용하는 것은 데이터 손실을 초래했습니다. 이 PEP 529를 통해서 이제는, 지금은 기본으로 utf-8로 설정된 sys.getfilesystemencoding()으로 인코딩된 바이트를 사용함으로써 윈도우 운영체제에서 bytes 경로를 사용하는 것이 가능해졌습니다.

경로를 나타내기위해 str를 사용하지 않는 애플리케이션은 bytes가 정확하게 인코딩 될 수 있도록 os.fsencode() 그리고 os.fsdecode()를 사용해야합니다. 이러한 변경을 이전 동작으로 되돌리고 싶다면, PYTHONLEGACYWINDOWSFSENCODING 환경변수를 설정하거나 혹은 sys._enablelegacywindowsfsencoding() 함수를 호출하면 됩니다.

더 자세한 내용은 PEP 529 여기서 확인하면 됩니다.

PEP 528: Change Windows console encoding to UTF-8

윈도우에서 기본 콘솔은 이제 모든 유니코드 문자들을 사용할 수 있게 되었고, 정확하게 str 객체를 읽어 파이썬 코드로 제공합니다. sys.stdin, sys.stdout, sys.stderr은 이제 기본으로 utf-8로 인코딩합니다.

이러한 변경은 interactive 콘솔을 사용할때만 적용되고, 파일 혹은 파이프를 사용할때는 적용되지 않습니다. interactive 콘솔의 동작을 이전으로 돌리고싶다면, PYTHONLEGACYWINDOWSSTDIO 환경변수를 설정하면 됩니다.

PEP 520: Preserving Class Attribute Definition Order

클래스의 바디의 속성들은 이제 소스 코드에 보이는 순서와 동일한 순서를 가집니다. 이 순서는 이제 클래스의 새로운 속성인 __dict__ 속성에 보존되어집니다.

또한, type.__prepare__() 로부터 반환되는 기본 클래스 실행 네임스페이스도 이제는 삽입 순서를 보존합니다.

PEP 468: Preserving Keyword Argument Order

함수의 시그니쳐의 **kwargs(키워드 인자)는 이제 키워드 인자를 함수에 전달한 순서대로 보존합니다.

New dict implementation

The dict type now uses a “compact” representation based on a proposal by Raymond Hettinger which was first implemented by PyPy. The memory usage of the new dict() is between 20% and 25% smaller compared to Python 3.5.

The order-preserving aspect of this new implementation is considered an implementation detail and should not be relied upon (this may change in the future, but it is desired to have this new dict implementation in the language for a few releases before changing the language spec to mandate order-preserving semantics for all current and future Python implementations; this also helps preserve backwards-compatibility with older versions of the language where random iteration order is still in effect, e.g. Python 3.5).

(Contributed by INADA Naoki in bpo-27350. Idea originally suggested by Raymond Hettinger.)

PEP 523: Adding a frame evaluation API to CPython

파이썬에서는 코드를 어떻게 실행할 지 커스터마이징 할 수 있는 지원을 많이 해주는데요, 프레임 객체(frame object)의 평가에서는 불가능했습니다. 프레임의 평가를 가로체기 위한 방법을 원했다면 정의된 함수의 함수 포인터를 직접 조작하지 않고는 어려웠습니다.

PEP 523은 C 레벨에서 프레임 평가를 플러그인으로 만들어서 API로 제공함으로써 이 문제를 해결했습니다. 이제 파이썬 코드의 실행 전에 프레임 평가를 가로채려고하는 디버거나 JIT와 같은 도구들이 프레임 평가를 커스터마이징 할 수 있게 되었습니다. 이러한 변경은 파이썬 코드에 대한 대체 평가 구현(alternative evaluation implementations), 프레임 평가 추적 등을 가능하게 합니다.

이 API는 제한된 C API의 일부가 아니며, 이 API의 사용은 제한되고 매우 선택적이며 저수준의 사용 사례에만 적용될 것으로 예상되기 때문에 private로 표시되었습니다. 이 API의 의미는 필요에 따라 변경될 수 있으니 조심해야할 것 같습니다.

PYTHONMALLOC environment variable

새로운 PYTHONMALLOC 환경 변수는 파이썬의 메모리 할당자를 설정하는 것과 디버그 훅을 설치하는 것을 허용해줍니다.

PYTHONMALLOC=debug 를 사용해 릴리즈 모드로 컴파일된 파이썬에서 메모리 할당자에 디버그 훅을 설치할 수 있습니다.

Effects of debug hoos:

  • 새롭게 할당된 메모리는 0xCB로 채워집니다.
  • 해제된 메모리(Freed memory)는 0xDB 바이트로 채워집니다.
  • 파이썬 메모리 할당자 API 위반을 검사합니다. 예를들어, 메모리 블록에서 호출되는 PyObject_Free()은 PyMem_Malloc()에 의해 할당됩니다.
  • 버퍼의 시작 전에 쓰기를 검사합니다. (buffer underflows)
  • 버퍼의 끝 이후를 쓰는 것을 검사합니다. (buffer overflows)
  • Check that the GIL is held when allocator functions of PYMEM_DOMAIN_OBJ (ex: PyObject_Malloc()) and PYMEM_DOMAIN_MEM (ex: PyMem_Malloc()) domains are called.

GIL 락이 걸려있는 것을 검사하는 것은 파이썬 3.6의 새로운 기능입니다.

See the PyMem_SetupDebugHooks() function for debug hooks on Python memory allocators.

It is now also possible to force the usage of the malloc() allocator of the C library for all Python memory allocations using PYTHONMALLOC=malloc. This is helpful when using external memory debuggers like Valgrind on a Python compiled in release mode.

On error, the debug hooks on Python memory allocators now use the tracemalloc module to get the traceback where a memory block was allocated.

Example of fatal error on buffer overflow using python3.6 -X tracemalloc=5 (store 5 frames in traces):

Debug memory block at address p=0x7fbcd41666f8: API 'o'
    4 bytes originally requested
    The 7 pad bytes at p-7 are FORBIDDENBYTE, as expected.
    The 8 pad bytes at tail=0x7fbcd41666fc are not all FORBIDDENBYTE (0xfb):
        at tail+0: 0x02 *** OUCH
        at tail+1: 0xfb
        at tail+2: 0xfb
        at tail+3: 0xfb
        at tail+4: 0xfb
        at tail+5: 0xfb
        at tail+6: 0xfb
        at tail+7: 0xfb
    The block was made by call #1233329 to debug malloc/realloc.
    Data at p: 1a 2b 30 00

Memory block allocated at (most recent call first):
  File "test/test_bytes.py", line 323
  File "unittest/case.py", line 600
  File "unittest/case.py", line 648
  File "unittest/suite.py", line 122
  File "unittest/suite.py", line 84

Fatal Python error: bad trailing pad byte

Current thread 0x00007fbcdbd32700 (most recent call first):
  File "test/test_bytes.py", line 323 in test_hex
  File "unittest/case.py", line 600 in run
  File "unittest/case.py", line 648 in __call__
  File "unittest/suite.py", line 122 in run
  File "unittest/suite.py", line 84 in __call__
  File "unittest/suite.py", line 122 in run
  File "unittest/suite.py", line 84 in __call__
  ...

DTrace and SystemTap probing support

파이썬은 이제 인터프리터에서 다음 이벤트들을 위한 정적 마커를 활성화할 수 있는 --with-dtrace를 사용할 수 있습니다.

  • function call/return
  • garbage collection started/finished
  • line of code executed.

이 옵션은 상용 환경에서 실행중인 인터프리터가 디버그 빌드를 다시 컴파일하거나 특정 애플리케이션에 대한 프로파일링/디버깅 코드를 제공할 필요 없이 사용할 수 있도록 합니다. 현재 구현은 리눅스와 맥OS에서 테스트되었습니다. 또 다른 마커들은 미래에 추가되어질 수 있습니다.

기타 언어 변경 사항

파이썬 코어에서 몇 가지 작은 변경점이 있습니다.

  • global 혹은 nonlocal statement는 이제 같은 스코프에서 사용되는 이름의 첫 사용 전에 텍스트로 나타나야 합니다. 이전에는 SyntaxWarning이었습니다.
  • 연산이 이용 불가능하다는 것을 나타내기 위해 None을 특별한 메서드로 설정하는 것이 이제는 가능해졌습니다. 예를들어, 만약 클래스의 __iter__() 메서드를 None으로 설정한다면, 이 클래스는 이터러블이 불가능하다는 것을 나타냅니다.
  • 반복되는 traceback들의 긴 시퀀스는 이제 다음과 같이 축약됩니다. => “[Previous line repeated {count more times}“]
  • Import now raises the new exception ModuleNotFoundError (subclass of ImportError) when it cannot find a module. Code that currently checks for ImportError (in try-except) will still work. (Contributed by Eric Snow in bpo-15767.)
  • 임포트(Import)는 이제 새로운 예외인 ModuleNotFoundError(ImportError의 서브클래스) 를 모듈을 찾을 수 없을 때 발생시킵니다. 현재 ImportError를 확인하는 코드(try-except)는 여전히 작동합니다.
  • zero-argument super()에 의존하는 클래스 메서드들은 이제 클래스의 생성 도중 메타클래스 메서드들로부터 호출될 때 정확하게 동작할 것입니다.
profile

박민기

단순하게 살아라. 현대인은 쓸데없는 절차와 일 때문에 얼마나 복잡한 삶을 살아가는가? - 이드리스 샤흐

© 2023, 미나리와 함께 만들었음