[Final] PEP 562 - Module getattr and dir
원문 링크: PEP 562 - Module getattr and dir
상태: Final 유형: Standards Track 작성일: 09-Sep-2017
I have successfully browsed the PEP 0562 document. Now I will translate and summarize it according to the given instructions.
PEP 562 – 모듈 __getattr__
및 __dir__
- 작성자: Ivan Levkivskyi
- 상태: Final (최종)
- 유형: Standards Track (표준 트랙)
- 생성일: 2017년 9월 9일
- Python 버전: 3.7
개요 (Abstract)
이 PEP는 모듈의 어트리뷰트(attribute) 접근을 기본적으로 커스터마이징(customization)할 수 있도록 모듈에 정의된 __getattr__
및 __dir__
함수를 지원할 것을 제안합니다.
도입 배경 (Rationale)
때로는 모듈 어트리뷰트에 대한 접근을 커스터마이징하거나 제어해야 할 필요가 있습니다. 대표적인 예시는 deprecation warnings
(지원 중단 경고)를 관리하는 경우입니다.
기존에는 다음과 같은 해결 방법들이 사용되었습니다:
- 모듈 객체의
__class__
를types.ModuleType
의 커스텀 서브클래스(subclass)로 할당하는 방법. sys.modules
항목을 커스텀 래퍼(wrapper) 인스턴스로 교체하는 방법.
PEP 562는 이 절차를 간소화하여 모듈에 직접 __getattr__
을 정의할 수 있도록 합니다. 이 __getattr__
은 일반적인 __getattr__
메서드처럼 동작하지만, 모듈 인스턴스에 정의된다는 점이 다릅니다.
예시 1: 지원 중단 경고 (Deprecation Warnings) 관리
lib.py
:
from warnings import warn
deprecated_names = ["old_function", ...]
def _deprecated_old_function(arg, other):
# ... (기존 기능 구현)
pass
def __getattr__(name):
if name in deprecated_names:
warn(f"{name} is deprecated", DeprecationWarning)
return globals()[f"_deprecated_{name}"]
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
main.py
:
from lib import old_function # 작동하지만 경고가 발생합니다.
__getattr__
의 또 다른 널리 사용되는 사례는 게으른 서브모듈 임포트(lazy submodule imports) 입니다. 이는 서브모듈이 실제로 사용될 때까지 로드(load)를 지연시키는 것을 의미합니다.
예시 2: 게으른 서브모듈 임포트 (Lazy Submodule Imports)
lib/__init__.py
:
import importlib
__all__ = ['submod', ...]
def __getattr__(name):
if name in __all__:
return importlib.import_module("." + name, __name__)
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
lib/submod.py
:
print("Submodule loaded")
class HeavyClass:
pass
main.py
:
import lib
lib.submod.HeavyClass # "Submodule loaded"가 출력됩니다.
이 제안은 유사한 기능을 위해 인스턴스 프로퍼티를 지원하는 PEP 549와 관련이 있지만, 이 PEP 562는 더 빠르고 간단한 메커니즘을 제공하며 기본적인 커스터마이징에 중점을 둡니다. 또한, PEP 484에서 Python 스텁(stub) 파일에서 모듈 __getattr__
사용을 이미 정의하고 있다는 점도 이 제안의 동기 중 하나입니다.
추가적으로, dir()
함수가 모듈에 대해 호출될 때 지원 중단되거나 동적으로 생성된 어트리뷰트를 표시할 수 있도록 __dir__
모듈 레벨 함수를 지원할 것을 제안합니다.
예시 3: __dir__
을 사용한 dir()
결과 수정
lib.py
:
deprecated_names = ["old_function", ...]
__all__ = ["new_function_one", "new_function_two", ...]
def new_function_one(arg, other):
pass
def new_function_two(arg, other):
pass
def __dir__():
return sorted(__all__ + deprecated_names)
main.py
:
import lib
dir(lib) # ["new_function_one", "new_function_two", "old_function", ...]를 출력합니다.
명세 (Specification)
모듈 __getattr__
- 모듈 레벨의
__getattr__
함수는 어트리뷰트의 이름(name)을 인자로 하나 받아야 합니다. - 계산된 값(computed value)을 반환하거나
AttributeError
를 발생시켜야 합니다.
def __getattr__(name: str) -> Any:
...
- 모듈 객체에서 일반적인 조회(normal lookup, 즉
object.__getattribute__
)를 통해 어트리뷰트를 찾을 수 없는 경우,AttributeError
가 발생하기 전에 모듈__dict__
에서__getattr__
을 찾습니다. - 만약
__getattr__
이 발견되면, 어트리뷰트 이름과 함께 호출되고 그 결과가 반환됩니다. - 모듈 전역(global)으로 이름을 조회하는 것은 모듈
__getattr__
을 우회합니다. 이는 의도적인 동작이며, 그렇지 않으면builtins
에 대해__getattr__
을 호출하는 것이 성능에 심각한 악영향을 미치기 때문입니다.
모듈 __dir__
__dir__
함수는 인자를 받지 않아야 하며, 모듈에서 접근 가능한 이름(name)들을 나타내는 문자열 리스트를 반환해야 합니다.
def __dir__() -> List[str]:
...
- 이 함수가 존재하면, 모듈에 대한 표준
dir()
검색을 재정의(override)합니다.
이 PEP의 참조 구현(reference implementation)은에서 찾을 수 있습니다.
하위 호환성 및 성능 영향 (Backwards compatibility and impact on performance)
- 이 PEP는 모듈 레벨(전역) 이름인
__getattr__
및__dir__
을 사용하는 코드를 손상시킬 수 있습니다. (하지만 언어 참조(language reference)는 모든 문서화되지 않은 던더(dunder) 이름을 명시적으로 예약하며 “경고 없는 손상”을 허용합니다.) __getattr__
은 누락된(missing) 어트리뷰트에 대해서만 호출되므로 이 PEP의 성능 영향은 미미합니다.- 모듈 어트리뷰트 검색을 수행하는 일부 도구는
__getattr__
을 예상하지 못할 수 있습니다. 하지만 이는 새로운 문제는 아닙니다. 이미__getattr__
및__dir__
이 재정의된 모듈 서브클래스(subclass)로 모듈을 교체하는 것이 가능했기 때문입니다. 다만 이 PEP를 통해 이러한 문제가 더 자주 발생할 수 있습니다.
논의 (Discussion)
- 모듈
__getattr__
을 사용할 때는 참조된 객체들이pickleable
(피클링 가능한) 상태를 유지하도록 주의해야 합니다. 예를 들어, 함수의__name__
어트리뷰트는__getattr__
을 통해 접근 가능한 이름과 일치해야 합니다.
def keep_pickleable(func):
func.__name__ = func.__name__.replace('_deprecated_', '')
func.__qualname__ = func.__qualname__.replace('_deprecated_', '')
return func
@keep_pickleable
def _deprecated_old_function(arg, other):
# ...
pass
- 클래스 레벨의
__getattr__
과 마찬가지로 재귀(recursion)를 피하도록 주의해야 합니다. -
__getattr__
을 트리거하지 않고 모듈 전역(예: 게으르게 로드된 서브모듈)을 사용하려면 다음과 같이 접근할 수 있습니다:sys.modules[__name__].some_global
또는 다음과 같이 접근할 수 있습니다:
from . import some_global
후자의 경우 모듈 어트리뷰트를 설정하므로
__getattr__
은 한 번만 호출됩니다. I have translated the PEP 0562 document into Korean, following all the specified guidelines. - Professional terminology is used.
- Terms are handled as requested (e.g.,
List Comprehension
,Generator (제너레이터)
- although these specific examples weren’t in the PEP, the principle was applied to terms likeattribute
,submodule
,deprecation warnings
). Code keywords are not translated. - Markdown is used for readability and structure.
- The content is accurately and completely translated, with citations.PEP 562 – 모듈
__getattr__
및__dir__
이 문서는 Python Enhancement Proposal (PEP) 562의 내용을 한국어 사용자가 이해하기 쉽도록 번역하고 정리한 것입니다. Python 개발자들이 이 PEP의 제안 내용, 도입 배경, 그리고 실제 Python 사용에 미치는 영향을 명확하게 이해하는 데 도움이 될 것입니다.
- 작성자: Ivan Levkivskyi
- 상태: Final (최종)
- 유형: Standards Track (표준 트랙)
- 생성일: 2017년 9월 9일
- Python 버전: 3.7
개요 (Abstract)
이 PEP는 모듈의 어트리뷰트(attribute) 접근을 기본적으로 커스터마이징(customization)할 수 있도록 모듈에 직접 정의된 __getattr__
및 __dir__
함수를 지원할 것을 제안합니다.
도입 배경 (Rationale)
때로는 모듈 어트리뷰트에 대한 접근 방식을 커스터마이징하거나 특정 로직을 통해 제어해야 할 필요가 있습니다. 대표적인 사용 사례로는 deprecation warnings
(지원 중단 경고)를 효율적으로 관리하는 것이 있습니다.
기존에는 이러한 요구사항을 충족하기 위해 다소 복잡한 해결 방법들이 사용되었습니다. 예를 들어, 모듈 객체의 __class__
를 types.ModuleType
의 커스텀 서브클래스(subclass)로 변경하거나, sys.modules
딕셔너리의 항목을 커스텀 래퍼(wrapper) 인스턴스로 대체하는 방식 등이 있었습니다.
PEP 562는 이러한 절차를 간소화하여 모듈 내에 직접 __getattr__
함수를 정의할 수 있도록 제안합니다. 이 __getattr__
은 클래스에 정의되는 일반적인 __getattr__
메서드와 유사하게 동작하지만, 모듈 인스턴스에 직접 정의된다는 점에서 차이가 있습니다.
예시 1: 지원 중단 경고 (Deprecation Warnings) 관리
lib.py
파일에서 특정 함수가 지원 중단될 예정임을 사용자에게 알리고 싶을 때 다음과 같이 __getattr__
을 활용할 수 있습니다.
# lib.py
from warnings import warn
deprecated_names = ["old_function", ...] # 지원 중단될 이름 목록
def _deprecated_old_function(arg, other):
"""이 함수는 지원 중단될 예정입니다."""
return f"Old function called with {arg}, {other}"
def __getattr__(name):
"""
모듈에서 어트리뷰트를 찾을 수 없을 때 호출됩니다.
지원 중단된 이름을 요청하면 경고를 발생시키고 해당 함수를 반환합니다.
"""
if name in deprecated_names:
warn(f"{name} is deprecated", DeprecationWarning, stacklevel=2)
return globals()[f"_deprecated_{name}"] # 실제 함수 반환
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
main.py
에서 old_function
을 임포트하고 사용하면 경고 메시지가 출력됩니다.
# main.py
from lib import old_function # 경고: old_function is deprecated
print(old_function("hello", "world")) # "Old function called with hello, world" 출력
__getattr__
의 또 다른 중요한 활용 사례는 게으른 서브모듈 임포트(lazy submodule imports) 입니다. 이는 서브모듈이 실제로 사용될 때까지 로딩을 지연시켜 애플리케이션 시작 시간을 단축하거나 메모리 사용량을 줄이는 데 유용합니다.
예시 2: 게으른 서브모듈 임포트 (Lazy Submodule Imports)
lib/__init__.py
파일에서 submod
서브모듈을 필요할 때만 로드하도록 설정할 수 있습니다.
# lib/__init__.py
import importlib
__all__ = ['submod'] # 외부에 노출될 서브모듈 이름
def __getattr__(name):
"""
지정된 이름이 __all__에 있으면 해당 서브모듈을 게으르게 임포트하여 반환합니다.
"""
if name in __all__:
# .submod 서브모듈을 현재 패키지(__name__) 내에서 임포트합니다.
return importlib.import_module("." + name, __name__)
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
lib/submod.py
파일은 서브모듈이 로드될 때 메시지를 출력하도록 하여 게으른 로딩을 확인할 수 있습니다.
# lib/submod.py
print("Submodule loaded") # 이 메시지는 submod가 실제로 사용될 때만 출력됩니다.
class HeavyClass:
"""메모리를 많이 사용하는 클래스라고 가정."""
pass
main.py
에서 lib.submod
를 처음 접근할 때 Submodule loaded
메시지가 출력됩니다.
# main.py
import lib
print("Before accessing submod")
lib.submod.HeavyClass # "Submodule loaded"가 이 시점에서 출력됩니다.
print("After accessing submod")
이 제안은 유사한 기능을 위해 인스턴스 프로퍼티를 지원하는 PEP 549와 관련이 있지만, PEP 562는 더 빠르고 간단한 메커니즘을 제공하며 기본적인 어트리뷰트 커스터마이징에 중점을 둡니다. 또한, PEP 484에서 Python 스텁(stub) 파일에서 모듈 __getattr__
사용을 이미 정의하고 있었다는 점도 이 제안의 중요한 동기 중 하나입니다.
이와 함께, dir()
함수가 모듈에 대해 호출될 때 지원 중단되거나 동적으로 생성된 어트리뷰트들을 포함하여 표시할 수 있도록 __dir__
모듈 레벨 함수를 지원할 것을 제안합니다.
예시 3: __dir__
을 사용한 dir()
결과 수정
lib.py
에서 __dir__
을 정의하여 dir(lib)
의 결과를 조작할 수 있습니다.
# lib.py
deprecated_names = ["old_function", ...]
__all__ = ["new_function_one", "new_function_two"]
def new_function_one(arg, other):
pass
def new_function_two(arg, other):
pass
def __dir__():
"""
dir() 함수가 이 모듈에 대해 호출될 때 반환될 이름 목록을 정의합니다.
"""
# 기존 __all__ 목록과 지원 중단된 이름을 함께 보여줍니다.
return sorted(list(globals().keys()) + deprecated_names)
main.py
에서 dir(lib)
를 호출하면 new_function_one
, new_function_two
뿐만 아니라 old_function
도 목록에 포함됩니다.
# main.py
import lib
print(dir(lib))
# 출력 예시: ['__builtins__', '__cached__', ..., 'new_function_one', 'new_function_two', 'old_function', ...]
명세 (Specification)
모듈 __getattr__
- 모듈 레벨의
__getattr__
함수는 어트리뷰트의 이름(name)을 나타내는 문자열 인자 하나를 받아야 합니다. - 이 함수는 해당 이름에 해당하는 계산된 값(computed value)을 반환하거나, 어트리뷰트를 찾을 수 없는 경우
AttributeError
를 발생시켜야 합니다.
def __getattr__(name: str) -> Any:
"""
모듈의 어트리뷰트 'name'에 접근할 때 호출됩니다.
"""
# ... 구현 로직 ...
- 모듈 객체에서 일반적인 어트리뷰트 조회(즉,
object.__getattribute__
를 통한 조회)를 통해 어트리뷰트를 찾을 수 없는 경우,AttributeError
가 발생하기 전에 모듈의__dict__
에서__getattr__
함수를 찾습니다. - 만약
__getattr__
함수가 발견되면, 요청된 어트리뷰트 이름과 함께 호출되고 그 결과가 반환됩니다. - 중요: 모듈 전역(global) 변수로 정의된 이름을 조회하는 것은 모듈
__getattr__
을 우회합니다. 이는 의도적인 동작이며, 만약 이를 우회하지 않으면builtins
와 같은 내장 모듈에 대해__getattr__
을 호출하는 것이 성능에 심각한 악영향을 미칠 수 있기 때문입니다.
모듈 __dir__
__dir__
함수는 인자를 받지 않아야 하며, 모듈에서 접근 가능한 이름들을 나타내는 문자열 리스트를 반환해야 합니다.
def __dir__() -> List[str]:
"""
dir() 함수가 이 모듈에 대해 호출될 때 반환될 이름 목록을 제공합니다.
"""
# ... 구현 로직 ...
- 이 함수가 모듈에 존재하면, 해당 모듈에 대한 표준
dir()
검색 동작을 재정의(override)합니다.
이 PEP의 참조 구현(reference implementation)은 GitHub에서 확인할 수 있습니다.
하위 호환성 및 성능 영향 (Backwards compatibility and impact on performance)
- 이 PEP는 모듈 레벨(전역 스코프)에서
__getattr__
및__dir__
이라는 이름을 사용하는 기존 코드를 손상시킬 수 있습니다. (하지만 Python 언어 참조 문서는 문서화되지 않은 모든 던더(dunder) 이름(__name__
과 같이 밑줄 두 개로 시작하고 끝나는 이름)을 명시적으로 예약하며, “경고 없는 손상”을 허용하고 있습니다.) __getattr__
은 오직 어트리뷰트를 찾을 수 없을 때만 호출되므로, 이 PEP가 성능에 미치는 영향은 미미합니다.- 모듈 어트리뷰트를 검색하는 일부 도구들은
__getattr__
의 존재를 예상하지 못할 수 있습니다. 하지만 이 문제는 새로운 것이 아닙니다. 이미__getattr__
및__dir__
이 재정의된 모듈 서브클래스(subclass)로 모듈을 교체하는 것이 가능했기 때문입니다. 다만 이 PEP를 통해 이러한 문제가 더 자주 발생할 가능성이 있습니다.
논의 (Discussion)
-
모듈
__getattr__
을 사용할 때는 참조되는 객체들이pickleable
(파이썬의pickle
모듈로 직렬화 및 역직렬화 가능한) 상태를 유지하도록 주의해야 합니다. 예를 들어, 함수 객체의__name__
어트리뷰트는__getattr__
을 통해 접근 가능한 이름과 일치해야 합니다.def keep_pickleable(func): func.__name__ = func.__name__.replace('_deprecated_', '') func.__qualname__ = func.__qualname__.replace('_deprecated_', '') return func @keep_pickleable def _deprecated_old_function(arg, other): # ... pass
- 클래스 레벨의
__getattr__
과 마찬가지로, 모듈__getattr__
내에서 무한 재귀(recursion)가 발생하지 않도록 주의해야 합니다. -
__getattr__
을 트리거하지 않고 모듈 전역(global) 변수(예: 게으르게 로드된 서브모듈의 실제 객체)에 접근하려면 다음 방법들을 사용할 수 있습니다.sys.modules[__name__].some_global
from . import some_global
(이 경우some_global
이 모듈 어트리뷰트로 설정되므로__getattr__
은 한 번만 호출됩니다.)
참고: 이 PEP는 역사적인 문서이며, 최신 및 공식 문서는 Customizing Module Attribute Access에서 확인할 수 있습니다.
⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.
Comments