__getattr__在模块上


127

如何实现等效的 __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

2
我可能会怀着悲伤的回答,因为它在所有情况下都可以工作(尽管有点混乱,可以做得更好)。哈佛大学S·洛特(S Lott)给出了很好的答案,但这不是实际的解决方案。
马特·乔纳

1
在您的情况下,您甚至都没有进行属性访问的权限,因此您一次要请求两个不同的东西。因此,主要问题是您想要哪个。您是否想salutation存在于全局或本地命名空间中(上面的代码正在尝试这样做),还是要在模块上进行点访问时动态查找名称?这是两件事。
Lennart Regebro

有趣的问题,您是怎么想到的?
克里斯·韦瑟琳


1
__getattr__Python 3.7支持模块上的on
Fumito Hamamura

Answers:


41

不久前,Guido宣布对新型类的所有特殊方法查找都绕过__getattr__and__getattribute__。Dunder方法曾经工作的模块-你可以,例如,使用一个模块作为一个上下文管理器简单地通过定义__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
如果我通过m = ModuleType("mod")和set 动态创建模块m.__getattr__ = lambda attr: return "foo";但是,当我跑步时from mod import foo,我得到了TypeError: 'module' object is not iterable
weberc2

@ weberc2:做到这一点m.__getattr__ = lambda attr: "foo",此外,您需要使用定义模块的条目sys.modules['mod'] = m。之后,不会有错误from mod import foo
martineau

威姆:您还可以获取动态计算的值(例如具有模块级属性),从而允许一个人写my_module.whatever来调用它(在之后import my_module)。
martineau

115

您在这里遇到两个基本问题:

  1. __xxx__ 方法只在类上查找
  2. 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兼容性,可以省略。


3
之所以可行,是因为导入机制正在积极地启用此hack,并且在加载后,最后一步将实际模块从sys.modules中拉出了。文档中是否提到了该位置?
Piotr Dobrogost 2012年

4
现在,考虑到它是“半批准”的,我可以更轻松地使用此黑客了:)
Mark Nunberg 2013年

3
这是做扭曲的东西,像制作import sys给予Nonesys。我猜这个技巧是不是在Python 2.批准
asmeurer

3
@asmeurer:要了解其原因(和解决方案),请参阅以下问题:分配给sys.modules [__ name__]后,为什么__name__的值会发生变化?
martineau 2014年

1
@qarma:我似乎回想起正在谈论的一些增强功能,这些增强功能将允许python模块更直接地参与类继承模型,但是即使如此,此方法仍然有效并且受支持。
伊桑·弗曼

48

这是一个技巧,但是您可以使用一个类包装模块:

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__])

10
好又脏:D
Matt Joiner 2010年

那可能行得通,但可能无法解决作者的实际问题。
DasIch 2010年

12
“可能有效”和“可能无效”不是很有帮助。这是一个技巧,但可以解决问题所带来的问题。
哈佛小号

6
虽然这将在导入模块并访问其不存在的属性的其他模块中起作用,但不适用于此处的实际代码示例。访问globals()不会通过sys.modules。
Marius Gedminas 2010年

5
不幸的是,这不适用于当前模块,或者可能不适用于在之后访问的内容import *
马特·乔纳

19

我们通常不那样做。

我们要做的就是这个。

class A(object):
....

# The implicit global instance
a= A()

def salutation( *arg, **kw ):
    a.salutation( *arg, **kw )

为什么?使隐式全局实例可见。

例如,查看random模块,该模块创建一个隐式全局实例,以稍微简化您需要“简单”随机数生成器的用例。


如果您真的很雄心勃勃,则可以创建该类,并遍历其所有方法,并为每个方法创建模块级函数。
保罗·费舍尔

@Paul Fisher:每个问题,该类已经存在。公开类的所有方法可能不是一个好主意。通常,这些公开方法是“便利”方法。并非所有都适用于隐式全局实例。
S.Lott

13

与@HåvardS提出的类似,在我需要在模块上实现一些魔术的情况下(例如__getattr__),我将定义一个继承types.ModuleType并放入其中的新类sys.modules(可能替换自定义模块ModuleType定义了定义)。

请参阅Werkzeug的主__init__.py文件,以实现此功能的强大功能。


8

这有点黑,但是...

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

通过遍历全局名称空间中的所有对象来工作。如果该项目是一个类,则在类属性上进行迭代。如果该属性是可调用的,则将其作为函数添加到全局名称空间中。

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

我不会在生产代码中使用它,但是它应该可以帮助您入门。


2
我更喜欢HåvardS的回答,因为它看起来更加干净,但这直接回答了所问的问题。
感到悲伤

这与我最终使用的工具非常接近。有点混乱,但是可以在同一模块中正确使用globals()。
马特·乔纳

1
在我看来,这样的答案与“在调用模块的静态定义的属性中不存在的函数时”的要求不完全相同,因为这样做是无条件的,并添加了所有可能的类方法。可以通过使用仅AddGlobalAttribute()在存在模块级别时才执行的模块包装程序来解决此问题,这AttributeError与@HåvardS的逻辑相反。如果有机会,我将进行测试并添加我自己的混合答案,即使OP已经接受了该答案。
martineau 2010年

更新到我之前的评论。我现在知道,很难(难以解决?)拦截NameError全局(模块)名称空间的异常-这解释了为什么此答案会在当前全局名称空间中为发现的每种可能性添加可调用对象,以提前覆盖所有可能的情况。
martineau 2010年

5

这是我自己的不起眼的贡献-@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")

-3

创建包含您的类的模块文件。导入模块。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')

1
您在这里回答另一个问题。
Marius Gedminas 2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.