PEP 713 – 호출 가능 객체 모듈
- Author:
- Amethyst Reese <amethyst at n7.gg>
- Sponsor:
- Łukasz Langa <lukasz at python.org>
- Discussions-To:
- Discourse thread
- Status:
- Rejected
- Type:
- Standards Track
- Created:
- 20-Apr-2023
- Python-Version:
- 3.12
- Post-History:
- 23-Apr-2023
- Resolution:
- Discourse message
Table of Contents
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
거부 통지
Steering Council은 일관성 측면에서 이 PEP를 구현할 수 있음이 분명하더라도, 이를 제안해야 할 설득력 있는 이유가 없다고 판단했습니다. 향후에 이 아이디어가 다시 제기된다면, 참고할 만한 유용한 이전 논의입니다.
초록
모듈은 현재 직접 호출 가능 객체가 아닙니다. 클래스는 인스턴스 객체를 호출 가능 객체로 만드는 __call__ 메서드를 정의할 수 있지만, 전역 모듈 범위에서 이와 유사한 이름의 함수를 정의해도 아무런 효과가 없으며, 해당 함수는 직접 가져오거나 module.__call__로 참조해야만 호출할 수 있습니다. PEP 562는 모듈에 대해 __getattr__() 및 __dir__() 지원을 추가했지만, __getattr__를 정의하여 __call__에 대한 값을 반환하더라도 모듈이 호출 가능 객체가 되지는 않습니다.
이 PEP는 모듈의 전역 네임스페이스에 __call__ 객체를 표준 함수 또는 임의의 호출 가능 객체로 정의하여 모듈을 직접 호출할 수 있도록 지원할 것을 제안합니다.
동기
많은 모듈은 해당 기능에 대한 단 하나의 주요 인터페이스만 갖습니다. 많은 경우 이 인터페이스는 하나의 호출 가능 객체이며, 모듈 자체를 호출 가능 객체로 직접 가져와 사용할 수 있으면 사용자에게 더욱 “Python다운” 인터페이스를 제공합니다.:
# user.py
import fancy
@fancy
def func(...):
...
현재 이러한 유형의 인터페이스를 제공하려면 모듈 객체를 런타임에 수정하여 호출 가능 객체로 만들어야 합니다.
일반적으로 sys.modules의 모듈 객체를 호출 가능 객체 대체물(예: 함수 또는 클래스 인스턴스)로 교체하여 구현합니다:
# fancy.py
def fancy(...):
...
sys.modules[__name__] = fancy
이렇게 하면 작성자가 추가 후크를 제공하지 않는 한, from module import member를 사용하더라도 원래 모듈에 사실상 접근할 수 없게 됩니다. 또한 __doc__, __package__, __path__등을 비롯한 모든 특수 모듈 속성이 누락된 “모듈” 객체가 생성됩니다.
또는 모듈 작성자는 호출 가능 객체 인터페이스를 제공하는 사용자 정의 타입으로 모듈의 __class__ 속성을 재정의할 수 있습니다:
# fancy.py
def fancy(...):
...
class FancyModule(types.ModuleType):
def __call__(self, ...):
return fancy(...)
sys.modules[__name__].__class__ = FancyModule
두 접근 방식 모두 보일러플레이트가 추가될 뿐만 아니라, 모듈이 런타임에 호출 가능 객체라는 사실을 타입 검사기가 인식하지 못하기 때문에 타입 검사기 오류도 발생한다는 단점이 있습니다:
$ mypy user.py
user.py:3: error: Module not callable [operator]
Found 1 error in 1 file (checked 1 source file)
사양
모듈 객체가 호출되고 __call__ 객체가 (__getattr__또는 __dict__조회의 결과로) 발견되면 해당 객체가 주어진 인자와 함께 호출됩니다.
__call__ 객체를 찾을 수 없으면 기존 동작과 동일하게 TypeError가 발생합니다.
이러한 모든 예제는 유효한 호출 가능 객체 모듈로 간주됩니다:
# hello.py
def __call__(...):
pass
# hello.py
class Hello:
pass
__call__ = Hello
# hello.py
def hello():
pass
def __getattr__(name):
if name == "__call__":
return hello
일반적으로 처음 두 방식을 선호해야 합니다. 타입 검사기와 같은 도구를 사용하여 정적 분석을 더 쉽게 수행할 수 있기 때문입니다. 다만 구현을 더 일관되게 만들기 위해 세 번째 형식도 허용합니다.
모듈의 __call__속성에 임의의 호출 가능 객체를 할당하거나 모듈의 __getattr__메서드가 이를 반환하도록 하여, 모듈 작성자가 사용자가 모듈을 호출 가능하게 만드는 데 가장 적합한 메커니즘을 선택할 수 있도록 하는 것이 목적입니다.
하위 호환성과 성능에 미치는 영향
이 PEP는 하위 호환성 문제를 일으키지 않을 것으로 예상됩니다. 이미 __call__ 객체를 포함하는 모든 모듈은 이전과 동일하게 계속 작동하지만, 직접 호출할 수 있는 기능이 추가됩니다. 기존 __call__ 객체를 포함하는 모듈이 호출될 때 TypeError를 발생시키는 기존 동작에 의존할 가능성은 낮다고 간주됩니다.
이 PEP는 새로운 인터페이스를 정의하므로 성능에 미치는 영향은 최소화됩니다. 모듈을 호출하면 모듈 객체에서 __call__이라는 이름을 조회하게 됩니다. 호출 가능 객체 모듈을 생성하기 위한 기존 우회 방법은 이미 일반 객체에 대해 이 동작에 의존하므로, 이러한 호출 가능 객체 모듈에서도 유사한 성능이 발생합니다.
타입 검사기는 __call__ 객체를 포함하는 모듈을 호출 가능 객체로 처리하도록 그에 맞게 업데이트해야 할 가능성이 높습니다. 호출 가능 객체 모듈을 지원하지 않는 이전 Python 버전을 대상으로 하는 코드를 검사할 때 타입 검사기에서 이를 지원할 수 있어야 하며, 이러한 모듈에는 모듈을 호출 가능 객체로 만들기 위해 앞서 언급한 우회 방법 중 하나도 포함되어 있을 것으로 예상됩니다.
이를 가르치는 방법
관련 호출 가능 타입 문서에서는 목록에 모듈을 포함하며, __call__()로 연결되는 링크를 함께 제공합니다. 관련 Emulating callable objects 문서에는 모듈 속성 접근 사용자 정의 섹션과 유사하게 예제 코드와 함께 호출 가능 객체인 모듈을 다루는 섹션이 포함됩니다.
참조 구현
호출 가능 객체 모듈의 제안된 구현은 CPython PR #103742에서 확인할 수 있습니다.
거부된 아이디어
__getattr__ 및 __dir__의 도입과 __call__의 사용을 활성화하자는 제안이 있었으므로, __or__와 __iter__ 같은 모든 특수 메서드 이름의 모듈 사용을 허용할 가치가 있는지 검토했습니다. 이것이 완전히 바람직하지 않은 것은 아니지만, 하위 호환성에 대한 우려가 발생할 가능성이 커지며, 이러한 다른 특수 메서드는 __call__보다 라이브러리 작성자에게 제공하는 유용성이 낮을 가능성이 큽니다.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.