PEP 702 – 타입 시스템을 사용한 사용 중단 표시
- Author:
- Jelle Zijlstra <jelle.zijlstra at gmail.com>
- Discussions-To:
- Discourse thread
- Status:
- Final
- Type:
- Standards Track
- Topic:
- Typing
- Created:
- 30-Dec-2022
- Python-Version:
- 3.13
- Post-History:
- 01-Jan-2023, 22-Jan-2023
- Resolution:
- 07-Nov-2023
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
추상
이 PEP는 클래스 또는 함수를 사용 중단으로 표시하는 @warnings.deprecated() 데코레이터를 추가하여, 정적 검사기가 해당 항목이 사용될 때 경고할 수 있도록 합니다. 기본적으로 이 데코레이터는 런타임에 DeprecationWarning도 발생시킵니다.
동기
소프트웨어가 발전함에 따라 새로운 기능이 추가되고 기존 기능은 더 이상 사용되지 않게 됩니다. 라이브러리 개발자는 사용자가 새로운 API로 마이그레이션할 시간을 제공하면서 더 이상 사용되지 않는 코드를 제거하기 위해 노력하고자 합니다. Python은 이러한 목표를 달성하기 위한 메커니즘을 제공합니다. 바로 사용 중단된 기능이 사용될 때 경고를 표시하는 데 사용되는 DeprecationWarning 경고 클래스입니다. 이 메커니즘은 널리 사용됩니다. 이 PEP를 작성하는 시점에 CPython 메인 브랜치에는 DeprecationWarning을 발생시키는 서로 다른 코드 경로가 약 150개 포함되어 있습니다. 많은 서드파티 라이브러리도 사용 중단을 표시하기 위해 DeprecationWarning을 사용합니다. 상위 5000개 PyPI 패키지에는 다음이 있습니다.
- 정규 표현식
warnings\.warn.*\bDeprecationWarning\b에 대해 1911개의 일치 항목이 있으며, 이는DeprecationWarning사용을 나타냅니다(경고가 여러 줄에 걸쳐 분할된 경우는 제외합니다); - 정규 표현식
^\s*@deprecated에 대해 1661개의 일치 항목이 있으며, 이는 일종의 사용 중단 데코레이터 사용을 나타냅니다.
그러나 현재 메커니즘만으로는 사용 중단된 기능의 사용자가 적시에 코드를 업데이트하도록 보장하기 어려운 경우가 많습니다. 예를 들어, 오랫동안 사용 중단된 다양한 unittest 기능의 제거는 사용자가 코드를 업데이트할 시간을 더 확보할 수 있도록 Python 3.11에서 되돌려야 했습니다. 사용자는 실용적인 이유로 경고를 비활성화한 상태에서 테스트 스위트를 실행할 수 있으며, 테스트가 다루지 않는 코드 경로에서 사용 중단이 트리거될 수도 있습니다.
사용자가 사용 중단된 기능에 대해 알아볼 수 있는 방법을 더 많이 제공하면 마이그레이션 프로세스를 빠르게 진행할 수 있습니다. 이 PEP는 정적 타입 검사기를 활용하여 사용자에게 사용 중단을 알릴 것을 제안합니다. 이러한 검사기는 사용자 코드의 의미를 철저히 이해하므로, 단 한 번의 grep 호출로는 찾을 수 없는 사용 중단을 감지하고 보고할 수 있습니다. 또한 많은 타입 검사기가 IDE와 통합되므로 사용자는 편집기에서 바로 사용 중단 경고를 확인할 수 있습니다.
근거
처음 보면 사용 중단은 타입 검사기가 관여해야 할 주제처럼 보이지 않을 수 있습니다. 결국 타입 검사기는 잠재적인 향후 변경이 아니라 코드가 현재 상태 그대로 작동할지를 검사하는 데 관심을 둡니다. 그러나 타입 검사기가 타입 오류를 찾기 위해 코드에 수행하는 분석은 많은 사용 중단의 사용을 감지하는 데 필요한 분석과 매우 유사합니다. 따라서 타입 검사기는 사용 중단을 찾고 보고하는 데 적합합니다.
다른 언어에는 이미 유사한 기능이 있습니다.
- GCC는 함수 선언에서
deprecated속성을 지원합니다. 이는 CPython의Py_DEPRECATED매크로를 구동합니다. - GraphQL은 필드를
@deprecated로 표시하는 것을 지원합니다. - Kotlin은
Deprecated어노테이션을 지원합니다. - Scala는
@deprecated어노테이션을 지원합니다. - Swift는 supports는
@available특성을 사용하여 API를 더 이상 사용되지 않는 것으로 표시합니다. - TypeScript는 uses는
@deprecatedJSDoc 태그를 사용하여 더 이상 사용되지 않는 기능의 사용을 표시하는 힌트를 제공합니다.
여러 사용자가 이러한 기능에 대한 지원을 요청했습니다:
유사한 기존 서드파티 도구가 있습니다:
- Deprecated은 클래스, 함수 또는 메서드를 더 이상 사용되지 않는 것으로 표시하는 데코레이터를 제공합니다. 데코레이터가 적용된 객체에 접근하면 런타임 경고가 발생하지만, 타입 검사기는 이를 탐지하지 못합니다.
- flake8-deprecated은 더 이상 사용되지 않는 기능의 사용을 경고하는 린터 플러그인입니다. 그러나 이는 하드코딩된 짧은 폐기 목록으로 제한됩니다.
사양
새로운 데코레이터 @deprecated()가 warnings 모듈에 추가됩니다. 이 데코레이터는 클래스, 함수 또는 메서드에 사용하여 이를 더 이상 사용되지 않는 것으로 표시할 수 있습니다. 여기에는 typing.TypedDict 및 typing.NamedTuple 정의가 포함됩니다. 오버로드된 함수에서는 데코레이터를 개별 오버로드에 적용하여 해당 오버로드가 더 이상 사용되지 않음을 나타낼 수 있습니다. 데코레이터는 오버로드 구현 함수에도 적용하여 전체 함수가 더 이상 사용되지 않음을 나타낼 수 있습니다.
데코레이터는 다음 인수를 사용합니다:
- 폐기 메시지를 나타내는 필수 위치 전용 인자입니다.
- 런타임 동작을 제어하는 두 개의 키워드 전용 인자
category및stacklevel입니다(아래의 “런타임 동작”을 참조하십시오).
위치 전용 인자는 str 타입이며, 타입 검사기가 데코레이터가 적용된 객체의 사용을 발견할 때 표시해야 하는 메시지를 포함합니다. 도구는 표시를 위해 폐기 메시지를 정리할 수 있으며, 예를 들어 inspect.cleandoc() 또는 이에 상응하는 로직을 사용할 수 있습니다. 메시지는 문자열 리터럴이어야 합니다. 폐기 메시지의 내용은 사용자가 결정하지만, 더 이상 사용되지 않는 객체가 제거될 버전과 권장되는 대체 API에 대한 정보를 포함할 수 있습니다.
타입 검사기는 더 이상 사용되지 않는 것으로 표시된 객체의 사용을 발견할 때마다 진단을 생성해야 합니다. 더 이상 사용되지 않는 오버로드의 경우, 여기에는 해당 오버로드로 결정되는 모든 호출이 포함됩니다. 더 이상 사용되지 않는 클래스와 함수의 경우, 여기에는 다음이 포함됩니다:
- 모듈, 클래스 또는 인스턴스 속성을 통한 참조(
module.deprecated_object,module.SomeClass.deprecated_method,module.SomeClass().deprecated_method) - 해당 객체를 정의하는 모듈에서 더 이상 사용되지 않는 객체를 사용하는 경우(
module.py의x = deprecated_object()) import *를 사용하는 경우 모듈에서 더 이상 사용되지 않는 객체를 사용하는 경우(from module import *; x = deprecated_object())from임포트(from module import deprecated_object)- 함수 호출을 간접적으로 트리거하는 모든 구문입니다. 예를 들어, 클래스 C의
__add__메서드가 더 이상 사용되지 않는 것으로 표시된 경우, 코드C() + C()는 진단을 발생시켜야 합니다. 마찬가지로 프로퍼티의 setter가 더 이상 사용되지 않는 것으로 표시된 경우, 해당 프로퍼티를 설정하려는 시도는 진단을 발생시켜야 합니다.
메서드가 PEP 698의 typing.override() 데코레이터로 표시되어 있고, 해당 메서드가 재정의하는 베이스 클래스 메서드가 더 이상 사용되지 않는 것으로 표시된 경우, 타입 검사기는 진단을 생성해야 합니다.
더 이상 사용되지 않음이 적용될 수 있는 추가 시나리오가 있습니다. 예를 들어, 객체가 typing.Protocol을 구현할 수 있지만, 프로토콜 준수에 필요한 메서드 중 하나가 더 이상 사용되지 않는 것으로 표시될 수 있습니다. 이와 같은 시나리오는 복잡하고 실제로 발생할 가능성이 비교적 낮아 보이므로, 이 PEP에서는 타입 검사기가 이를 감지하도록 요구하지 않습니다.
예시
예를 들어, library.pyi라는 이름의 이 라이브러리 스텁을 고려하십시오.
from warnings import deprecated
@deprecated("Use Spam instead")
class Ham: ...
@deprecated("It is pining for the fiords")
def norwegian_blue(x: int) -> int: ...
@overload
@deprecated("Only str will be allowed")
def foo(x: int) -> str: ...
@overload
def foo(x: str) -> str: ...
class Spam:
@deprecated("There is enough spam in the world")
def __add__(self, other: object) -> object: ...
@property
@deprecated("All spam will be equally greasy")
def greasy(self) -> float: ...
@property
def shape(self) -> str: ...
@shape.setter
@deprecated("Shapes are becoming immutable")
def shape(self, value: str) -> None: ...
다음은 타입 검사기가 이 라이브러리의 사용을 처리해야 하는 방식입니다.
from library import Ham # error: Use of deprecated class Ham. Use Spam instead.
import library
library.norwegian_blue(1) # error: Use of deprecated function norwegian_blue. It is pining for the fiords.
map(library.norwegian_blue, [1, 2, 3]) # error: Use of deprecated function norwegian_blue. It is pining for the fiords.
library.foo(1) # error: Use of deprecated overload for foo. Only str will be allowed.
library.foo("x") # no error
ham = Ham() # no error (already reported above)
spam = library.Spam()
spam + 1 # error: Use of deprecated method Spam.__add__. There is enough spam in the world.
spam.greasy # error: Use of deprecated property Spam.greasy. All spam will be equally greasy.
spam.shape # no error
spam.shape = "cube" # error: Use of deprecated property setter Spam.shape. Shapes are becoming immutable.
진단의 정확한 문구는 타입 검사기에 따라 다르며 사양의 일부가 아닙니다.
런타임 동작
위치 전용 message 인자 외에, @deprecated 데코레이터는 키워드 전용 인자 두 개를 받습니다.
category: 경고 클래스입니다. 기본값은DeprecationWarning입니다. 이를None으로 설정하면 런타임에 경고가 발행되지 않으며, 데코레이터는__deprecated__속성을 설정하는 경우를 제외하고 원래 객체를 반환합니다(아래 참조).stacklevel: 경고를 발행할 때 건너뛸 스택 프레임의 수입니다. 기본값은 1이며, 이는 더 이상 사용되지 않는 객체가 호출되는 지점에서 경고를 발행해야 함을 의미합니다. 내부적으로 구현은 래퍼 코드에서 사용하는 스택 프레임 수를 더합니다.
데코레이트된 객체가 클래스인 경우, 데코레이터는 클래스를 인스턴스화할 때 경고가 발생하도록 __new__ 메서드를 래핑합니다. 데코레이트된 객체가 호출 가능 객체인 경우, 데코레이터는 원래 호출 가능 객체를 래핑하지만 호출될 때 경고를 발생시키는 새로운 호출 가능 객체를 반환합니다. 그 외의 경우 데코레이터는 category=None이 전달되지 않았다면 TypeError를 발생시킵니다.
오버로드, Protocol 클래스 및 추상 메서드를 비롯하여 데코레이트된 객체를 사용해도 경고를 발생시킬 수 없는 시나리오가 여러 가지 있습니다. 이러한 경우 category=None없이 @deprecated를 사용하면 타입 검사기가 경고를 표시할 수 있습니다.
런타임 인트로스펙션을 지원하기 위해, 데코레이터는 전달받은 객체와 더불어 더 이상 사용되지 않는 클래스 및 함수에 대해 생성하는 래퍼 호출 가능 객체에도 __deprecated__ 속성을 설정합니다. 이 속성의 값은 데코레이터에 전달된 메시지입니다. 이 속성을 설정할 수 없는 객체를 데코레이트하는 것은 지원되지 않습니다.
@runtime_checkable 데코레이터가 적용된 Protocol이 더 이상 사용되지 않는 것으로 표시된 경우, __deprecated__ 속성은 프로토콜의 멤버로 간주되지 않아야 하므로 해당 속성의 존재가 isinstance 검사에 영향을 주지 않아야 합니다.
관련 typing.get_overloads()와의 호환성을 위해 @deprecated 데코레이터는 @overload 데코레이터 뒤에 배치해야 합니다.
타입 검사기 동작
이 PEP는 타입 검사기가 사용자에게 사용 중단 진단을 정확히 어떻게 표시해야 하는지는 명시하지 않습니다. 그러나 일부 사용자(예: 특정 Python 버전만을 대상으로 하는 애플리케이션 개발자)는 사용 중단을 신경 쓰지 않을 수 있는 반면, 다른 사용자(예: 라이브러리가 향후 Python 버전과 계속 호환되기를 원하는 라이브러리 개발자)는 CI 파이프라인에서 사용 중단된 기능의 사용을 모두 발견하기를 원할 것입니다. 따라서 타입 검사기는 두 사용 사례를 모두 다룰 수 있는 구성 옵션을 제공하는 것이 좋습니다. 다른 타입 검사기 오류와 마찬가지로, # type: ignore 주석을 사용하여 사용 중단을 무시할 수도 있습니다.
사용 중단 정책
가능한 경우 새로운 사용 중단이 이 PEP의 기능을 사용하여 사용자에게 사용 중단 사실을 알리도록 CPython의 사용 중단 정책(PEP 387)을 개정할 것을 제안합니다. 구체적으로 이는 새로운 사용 중단에 적절한 위치에 @deprecated 데코레이터를 추가하도록 typeshed 저장소를 변경해야 한다는 의미입니다. 이 요구 사항은 이 PEP의 기능을 사용하여 표현할 수 없는 사용 중단에는 적용되지 않습니다.
하위 호환성
새로운 데코레이터를 만들어도 하위 호환성 문제가 발생하지 않습니다. 새로운 타이핑 기능과 마찬가지로 @deprecated 데코레이터는 typing_extensions 모듈에 추가되므로, 이전 버전의 Python에서도 사용할 수 있습니다.
이 내용을 가르치는 방법
IDE 또는 타입 검사기 출력에서 사용 중단 경고를 접하는 사용자가 받는 메시지는 명확하고 자명해야 합니다. @deprecated 데코레이터의 사용은 주로 라이브러리 작성자와 관련된 고급 기능이 될 것입니다. 데코레이터는 관련 문서(예: PEP 387 및 DeprecationWarning 문서)에서 사용 중단된 기능을 표시하는 추가 방법으로 언급되어야 합니다.
참조 구현
@deprecated 데코레이터의 런타임 구현은 버전 4.5.0부터 typing-extensions 라이브러리에서 사용할 수 있습니다. pyanalyze 타입 검사기는 사용 중단 오류를 출력하기 위한 prototype support를 제공하며, Pyright도 마찬가지입니다.
기각된 아이디어
모듈 및 속성의 사용 중단
이 PEP는 클래스, 함수 및 오버로드의 사용 중단을 다룹니다. 이를 통해 타입 검사기는 가능한 사용 중단의 대부분을 감지할 수 있지만 전부를 감지할 수 있는 것은 아닙니다. 추가 기능이 가치가 있을지 평가하기 위해 저는 CPython 표준 라이브러리의 현재 모든 사용 중단 항목을 조사했습니다(examined).
다음과 같았습니다:
- 함수, 메서드 및 클래스의 사용 중단 74건(이 PEP에서 지원됨)
- 모듈 전체의 사용 중단 28건(대부분 PEP 594로 인함)
- 함수 매개변수의 사용 중단 9건(오버로드에 데코레이터를 적용하여 이 PEP에서 지원됨)
- 상수의 사용 중단 1건
- 타입 시스템에서 쉽게 감지할 수 없는 사용 중단 38건(예를 들어, 활성 이벤트 루프 없이
asyncio.get_event_loop()를 호출하는 경우)
__deprecated__ 모듈 수준 상수를 추가하여 모듈을 사용 중단된 것으로 표시할 수 있습니다. 그러나 이를 필요로 하는 경우는 제한적이며, grep만으로도 사용 중단된 모듈의 사용을 비교적 쉽게 탐지할 수 있습니다. 따라서 이 PEP에서는 모듈 전체의 사용 중단을 지원하지 않습니다. 해결 방법으로 사용자는 모든 모듈 수준 클래스와 함수를 @deprecated로 표시할 수 있습니다.
모듈 수준 상수, 객체 속성 및 함수 매개변수를 사용 중단하기 위해 Annotated와 유사한 Deprecated[type, message] 타입 수정자를 추가할 수 있습니다. 그러나 이렇게 하면 타입 시스템에서 문자열이 정방향 참조가 아닌 단순한 문자열로 취급되는 새로운 영역이 생겨 타입 검사기의 구현이 복잡해집니다. 또한 제 데이터에 따르면 이 기능은 일반적으로 필요하지 않습니다.
더 많은 종류의 객체를 사용 중단하기 위한 기능은 향후 PEP에서 추가될 수 있습니다.
데코레이터를 typing 모듈에 배치하기
이 PEP의 이전 버전에서는 @deprecated 데코레이터를 typing 모듈에 배치할 것을 제안했습니다. 그러나 typing 모듈의 데코레이터가 런타임 동작을 수행하는 것은 예상 밖이라는 의견이 있었습니다. 따라서 현재 이 PEP에서는 데코레이터를 warnings 모듈에 추가할 것을 제안합니다.
감사의 말
typing-sig 밋업 그룹과의 통화에서 이 제안에 대한 유용한 피드백을 얻었습니다.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.