如何在模块上的类上实现__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
A while ago, Guido declared that all special method lookups on
new-style classes bypass __getattr__
and __getattribute__
。 Dunder方法以前曾在模块上工作过 - 例如,你可以在模仿__enter__
之前简单地通过定义__exit__
和broke来使用模块作为上下文管理器。
最近一些历史特征卷土重来,其中包括__getattr__
模块,因此不再需要现有的hack(在导入时用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()
eher使用类上的自定义方法(我喜欢使用.export()
)或使用泛型函数(例如已经列为答案的那些)。要记住一件事:如果包装器每次都创建一个新实例,那么全局解决方案不是,你最终会有微妙的不同行为。哦,你不能同时使用它们 - 它是一个或另一个。
更新
实际上有一个偶尔使用和推荐的hack:一个模块可以定义一个具有所需功能的类,然后最后在sys.modules中用该类的实例替换它自己(或者如果你坚持的话,用类替换它,但这通常不太有用)。例如。:
# module foo.py
import sys
class Foo:
def funct1(self, <args>): <code>
def funct2(self, <args>): <code>
sys.modules[__name__] = Foo()
这是有效的,因为导入机制正在积极地启用此hack,并且在加载后,最后一步将实际模块从sys.modules中拉出。 (这不是偶然的。很久以前就提出了黑客攻击,我们决定在进口机械中足够支持它。)
因此,实现所需内容的既定方法是在模块中创建单个类,并且作为模块的最后一个行为将sys.modules[__name__]
替换为您的类的实例 - 现在您可以根据需要使用__getattr__
/ __setattr__
/ __getattribute__
。
请注意,如果您使用此功能,那么在进行sys.modules
赋值时,模块中的任何其他内容(如全局变量,其他函数等)都将丢失 - 因此请确保所需的所有内容都在替换类中。
这是一个hack,但你可以用一个类包装模块:
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åvardS提出的,在我需要在模块上实现一些魔法(如__getattr__
)的情况下,我会定义一个继承自types.ModuleType
的新类并将其放入sys.modules
(可能替换我的自定义ModuleType
的模块)已定义)。
请参阅__init__.py
的主要Werkzeug文件,以获得相当强大的实现。
这是hackish,但......
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åvardS高度评价答案的轻微修饰,但更明确(因此@ S.Lott可能会接受,尽管可能对OP来说不够好):
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")
创建包含类的模块文件。导入模块。在刚刚导入的模块上运行getattr
。您可以使用__import__
进行动态导入,并从sys.modules中提取模块。
这是你的模块some_module.py
:
class Foo(object):
pass
class Bar(object):
pass
在另一个模块中:
import some_module
Foo = getattr(some_module, 'Foo')
动态执行此操作:
import sys
__import__('some_module')
mod = sys.modules['some_module']
Foo = getattr(mod, 'Foo')
在某些情况下,globals()
字典就足够了,例如,你可以从全局范围中按名称实例化一个类:
from somemodule import * # imports SomeClass
someclass_instance = globals()['SomeClass']()