问题:__getattr__在模块上
如何实现等效的 __getattr__
在类,模块上于a?
例
当调用模块的静态定义的属性中不存在的函数时,我希望在该模块中创建一个类的实例,并使用与该模块上的属性查找失败相同的名称调用该方法。
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
回答 0
__enter__
和__exit__
,这些技巧之前爆发。
最近,一些历史功能已经卷土重来,其中的一个模块已被卷土重来,__getattr__
因此,sys.modules
不再需要现有的hack(在导入时将一个模块替换为一个类)。
在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。
回答 1
您在这里遇到两个基本问题:
__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出发:
实际上,偶尔会使用并推荐一种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__
进行操作。
注意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()
如果不需要Python 2兼容性,可以省略。
回答 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__])
回答 3
我们通常不那样做。
我们要做的就是这个。
class A(object):
....
# The implicit global instance
a= A()
def salutation( *arg, **kw ):
a.salutation( *arg, **kw )
为什么?使隐式全局实例可见。
例如,查看random
模块,该模块创建一个隐式全局实例,以稍微简化您需要“简单”随机数生成器的用例。
回答 4
与@HåvardS提出的类似,在我需要在模块上实现一些魔术的情况下(例如__getattr__
),我将定义一个继承types.ModuleType
并放入其中的新类sys.modules
(可能替换自定义模块ModuleType
定义了定义)。
请参阅Werkzeug的主__init__.py
文件,以实现此功能的强大功能。
回答 5
这有点黑,但是…
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")
通过遍历全局命名空间中的所有对象来工作。如果该项目是一个类,则在类属性上进行迭代。如果该属性是可调用的,则将其作为函数添加到全局命名空间中。
它忽略所有包含“ __”的属性。
我不会在生产代码中使用它,但是它应该可以帮助您入门。
回答 6
这是我自己的不起眼的贡献-@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")
回答 7
创建包含您的类的模块文件。导入模块。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')