__getattr__
모듈에서 클래스 와 동등한 것을 어떻게 구현할 수 있습니까?
예
모듈의 정적으로 정의 된 속성에 존재하지 않는 함수를 호출 할 때 해당 모듈에 클래스의 인스턴스를 생성하고 모듈의 속성 조회에서 실패한 것과 동일한 이름으로 메서드를 호출하고 싶습니다.
class A(object):
def salutation(self, accusative):
print "hello", accusative
# note this function is intentionally on the module, and not the class above
def __getattr__(mod, name):
return getattr(A(), name)
if __name__ == "__main__":
# i hope here to have my __getattr__ function above invoked, since
# salutation does not exist in the current namespace
salutation("world")
다음을 제공합니다.
matt@stanley:~/Desktop$ python getattrmod.py
Traceback (most recent call last):
File "getattrmod.py", line 9, in <module>
salutation("world")
NameError: name 'salutation' is not defined
답변
얼마 전 Guido는 새로운 스타일의 클래스에 대한 모든 특수 메서드 조회가 __getattr__
및__getattribute__
. Dunder 메소드는 이전에 모듈에서 작업 했었습니다. 예를 들어, 그 트릭 이 깨지기 전에 간단히 __enter__
and 를 정의하여 모듈을 컨텍스트 관리자로 사용할 수 있습니다 .__exit__
최근 몇 가지 역사적 기능이 컴백했고, 그 __getattr__
중 모듈 은 기존 해킹 ( sys.modules
임포트시 클래스로 대체되는 모듈 )이 더 이상 필요하지 않습니다.
Python 3.7 이상에서는 한 가지 분명한 방법을 사용합니다. 모듈에 대한 속성 액세스를 사용자 정의하려면 __getattr__
하나의 인수 (속성 이름)를 허용해야하는 모듈 수준에서 함수를 정의 하고 계산 된 값을 반환하거나 다음을 발생시킵니다 AttributeError
.
# my_module.py
def __getattr__(name: str) -> Any:
...
이것은 또한 “from”가져 오기에 대한 후크를 허용합니다. 즉, from my_module import whatever
.
관련 메모에서 모듈 getattr과 함께 __dir__
에 응답 할 모듈 수준에서 함수를 정의 할 수도 있습니다 dir(my_module)
. 자세한 내용은 PEP 562 를 참조하십시오.
답변
여기에는 두 가지 기본 문제가 있습니다.
__xxx__
메서드는 클래스에서만 조회됩니다.TypeError: can't set attributes of built-in/extension type 'module'
(1) 어떤 솔루션이 어떤 모듈이 검사되고 있는지 추적해야 함을 의미합니다. 그렇지 않으면 모든 모듈이 인스턴스 대체 동작을 갖게됩니다. 그리고 (2)는 (1)이 불가능하다는 것을 의미합니다 … 적어도 직접적으로는 아닙니다.
다행스럽게도 sys.modules는 거기에가는 것에 대해 까다 롭지 않으므로 래퍼가 작동하지만 모듈 액세스에만 해당됩니다 (예 : import somemodule; somemodule.salutation('world')
동일한 모듈 액세스의 경우 대체 클래스에서 메서드를 잡아 globals()
당기고 다음을 사용하여 eiher에 추가해야합니다) . 클래스에 대한 사용자 지정 메서드 (사용 .export()
하는 것을 좋아함) 또는 일반 함수 (예 : 이미 답변으로 나열 됨)와 함께. 명심해야 할 한 가지 : 래퍼가 매번 새 인스턴스를 만들고 전역 솔루션이 그렇지 않은 경우 당신은 미묘하게 다른 행동을하게됩니다.
최신 정보
에서 귀도 반 로섬 (Guido van Rossum) :
실제로 가끔 사용 및 권장되는 해킹이 있습니다. 모듈은 원하는 기능을 가진 클래스를 정의한 다음 마지막에 sys.modules에서 해당 클래스의 인스턴스로 자신을 대체 할 수 있습니다. ,하지만 일반적으로 유용하지 않음). 예 :
# module foo.py
import sys
class Foo:
def funct1(self, <args>): <code>
def funct2(self, <args>): <code>
sys.modules[__name__] = Foo()
이는 가져 오기 기계가이 해킹을 적극적으로 활성화하고 최종 단계에서 실제 모듈을로드 한 후 sys.modules에서 꺼내기 때문에 작동합니다. (이것은 우연이 아닙니다. 해킹은 오래 전에 제안되었으며 우리는 수입 기계에서 충분히 지원할 수 있다고 판단했습니다.)
따라서 원하는 것을 달성하기위한 확립 된 방법은 모듈에 단일 클래스를 생성하고 모듈의 마지막 동작이 sys.modules[__name__]
클래스의 인스턴스로 대체되는 것 입니다. 이제 필요에 따라 __getattr__
/ __setattr__
/ __getattribute__
로 플레이 할 수 있습니다 .
참고 1 :이 기능을 사용하면 전역, 기타 기능 등과 같은 모듈의 다른 sys.modules
모든 것이 할당 될 때 손실 되므로 필요한 모든 것이 대체 클래스 내에 있는지 확인하십시오.
참고 2 : 지원하려면 클래스에서 정의 from module import *
해야합니다 __all__
. 예를 들면 :
class Foo:
def funct1(self, <args>): <code>
def funct2(self, <args>): <code>
__all__ = list(set(vars().keys()) - {'__module__', '__qualname__'})
Python 버전에 따라에서 생략 할 다른 이름이있을 수 있습니다 __all__
. 는 set()
파이썬 2와 호환이 필요하지 않을 경우 생략 할 수 있습니다.
답변
이것은 해킹이지만 모듈을 클래스로 래핑 할 수 있습니다.
class Wrapper(object):
def __init__(self, wrapped):
self.wrapped = wrapped
def __getattr__(self, name):
# Perform custom logic here
try:
return getattr(self.wrapped, name)
except AttributeError:
return 'default' # Some sensible default
sys.modules[__name__] = Wrapper(sys.modules[__name__])
답변
우리는 보통 그렇게하지 않습니다.
우리가하는 일은 이것입니다.
class A(object):
....
# The implicit global instance
a= A()
def salutation( *arg, **kw ):
a.salutation( *arg, **kw )
왜? 암시 적 전역 인스턴스가 표시되도록합니다.
예를 들어, random
“간단한”난수 생성기를 원하는 사용 사례를 약간 단순화하기 위해 암시 적 전역 인스턴스를 생성하는 모듈을 살펴보십시오 .
답변
@ Håvard S가 제안한 것과 유사하게, 모듈에 마법을 구현해야하는 경우 (예 :), __getattr__
상속 types.ModuleType
하고 넣는 새 클래스를 정의 합니다.sys.modules
(아마도 내 사용자 지정ModuleType
가 정의 된 ).
이것 의 상당히 강력한 구현은 Werkzeug 의 메인 __init__.py
파일을 참조하십시오 .
답변
이건 끔찍하지만 …
import types
class A(object):
def salutation(self, accusative):
print "hello", accusative
def farewell(self, greeting, accusative):
print greeting, accusative
def AddGlobalAttribute(classname, methodname):
print "Adding " + classname + "." + methodname + "()"
def genericFunction(*args):
return globals()[classname]().__getattribute__(methodname)(*args)
globals()[methodname] = genericFunction
# set up the global namespace
x = 0 # X and Y are here to add them implicitly to globals, so
y = 0 # globals does not change as we iterate over it.
toAdd = []
def isCallableMethod(classname, methodname):
someclass = globals()[classname]()
something = someclass.__getattribute__(methodname)
return callable(something)
for x in globals():
print "Looking at", x
if isinstance(globals()[x], (types.ClassType, type)):
print "Found Class:", x
for y in dir(globals()[x]):
if y.find("__") == -1: # hack to ignore default methods
if isCallableMethod(x,y):
if y not in globals(): # don't override existing global names
toAdd.append((x,y))
for x in toAdd:
AddGlobalAttribute(*x)
if __name__ == "__main__":
salutation("world")
farewell("goodbye", "world")
이는 전역 네임 스페이스의 모든 개체를 반복하여 작동합니다. 항목이 클래스 인 경우 클래스 속성을 반복합니다. 속성이 호출 가능하면 전역 네임 스페이스에 함수로 추가됩니다.
“__”를 포함하는 모든 속성을 무시합니다.
나는 이것을 프로덕션 코드에서 사용하지 않을 것이지만 시작해야합니다.
답변
여기 내 자신의 겸손한 공헌이 있습니다. @ Håvard S의 높은 평가를받은 답변을 약간 꾸미는 것이지만 좀 더 명시 적입니다 (따라서 OP에는 충분하지 않더라도 @ S.Lott가 받아 들일 수 있습니다).
import sys
class A(object):
def salutation(self, accusative):
print "hello", accusative
class Wrapper(object):
def __init__(self, wrapped):
self.wrapped = wrapped
def __getattr__(self, name):
try:
return getattr(self.wrapped, name)
except AttributeError:
return getattr(A(), name)
_globals = sys.modules[__name__] = Wrapper(sys.modules[__name__])
if __name__ == "__main__":
_globals.salutation("world")