__getattr__ 在一个模块上

如何在类、模块上实现等效的 __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
60998 次浏览

这是一个恶作剧,但是你可以用一个类来包装这个模块:

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的模块)。

请参阅 沃克泽格的主 __init__.py文件以获得相当健壮的实现。

创建包含类的模块文件。导入模块。在刚才导入的模块上运行 getattr。您可以使用 __import__进行动态导入,并从 sys.module 中提取模块。

这是你的模块 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')

这有点恶俗,但是..。

# Python 2.7
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))
# Returns:
# ('Looking at', 'A')
# ('Found Class:', 'A')
# ('Looking at', 'toAdd')
# ('Looking at', '__builtins__')
# ('Looking at', 'AddGlobalAttribute')
# ('Looking at', 'register')
# ('Looking at', '__package__')
# ('Looking at', 'salutation')
# ('Looking at', 'farewell')
# ('Looking at', 'types')
# ('Looking at', 'x')
# ('Looking at', 'y')
# ('Looking at', '__name__')
# ('Looking at', 'isCallableMethod')
# ('Looking at', '__doc__')
# ('Looking at', 'codecs')






for x in toAdd:
AddGlobalAttribute(*x)




if __name__ == "__main__":
salutation("world")
farewell("goodbye", "world")




# Returns:
# hello world
# goodbye world

这是通过迭代全局命名空间中的所有对象来实现的。如果该项是一个类,它将迭代类属性。如果该属性是可调用的,则将其作为函数添加到全局命名空间。

它忽略所有包含“ _ _”的属性。

我不会在产品代码中使用它,但它应该可以让您开始使用。

这里有两个基本问题:

  1. __xxx__方法只能在类上查找
  2. TypeError: can't set attributes of built-in/extension type 'module'

(1)意味着任何解决方案都必须跟踪哪个模块正在被检查,否则 每个模块将具有实例替换行为; (2)意味着(1)甚至是不可能的... 至少不是直接的。

幸运的是,sys.module 并不挑剔哪些方法放在那里,所以包装器只对模块访问起作用(例如,import somemodule; somemodule.salutation('world'); 对于同模块访问,你必须从替换类中提取方法,然后用类上的自定义方法(我喜欢使用 .export())或通用函数(例如那些已经作为答案列出的函数)将它们添加到 globals()中。需要记住的一点是: 如果包装器每次都创建一个新实例,而全局解决方案没有这样做,那么最终的行为会有细微的不同。你不能同时使用两种方法,不是一种就是另一种。


更新

来自 吉多·范罗苏姆:

实际上,有一种黑客技术是偶尔使用和推荐的: a 模块可以定义具有所需功能的类,然后在 最后,用该类的一个实例替换 sys.module 中的自身 (如果你坚持的话,也可以用在课堂上,但这通常没什么用)。 例如:

# module foo.py


import sys


class Foo:
def funct1(self, <args>): <code>
def funct2(self, <args>): <code>


sys.modules[__name__] = Foo()

之所以这样做是因为导入机器正在积极地启用这个 最后一步是将实际的模块从 模块,在加载后。(这不是意外。黑客 很久以前提出,我们决定,我们喜欢足以支持它在 进口机械)

因此,实现目标的既定方法是在模块中创建一个类,在模块的最后一个操作中,用类的一个实例替换 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__中省略。如果不需要 Python2兼容性,可以省略 set()

这里是我自己的一点小小的贡献——稍微修饰了@H åvard S 的高评价答案,但是更加明确了一点(所以@S 可能会接受。洛特,尽管可能还不够好) :

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")

前一段时间,Guido 声明所有特殊的方法查找 新式类绕过 __getattr____getattribute__ 。Dunder 方法以前用于模块——例如,在使用这些技巧 破产了之前,您可以简单地通过定义 __enter____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