关于模块上的python:__ getattr__

__getattr__ on a module

如何在类和模块上实现等效的__getattr__

例子

当调用模块静态定义的属性中不存在的函数时,我希望在该模块中创建类的实例,并使用与模块的属性查找失败时相同的名称对其调用方法。

1
2
3
4
5
6
7
8
9
10
11
12
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")

它给出:

1
2
3
4
5
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


这里有两个基本问题:

  • __xxx__方法只在类上查找
  • 江户十一〔11〕。
  • (1)意味着任何解决方案都必须跟踪正在检查的模块,否则每个模块都将具有实例替换行为;以及(2)意味着(1)甚至不可能……至少不是直接的。

    幸运的是,sys.modules并不是很挑剔,所以包装器可以工作,但只适用于模块访问(即import somemodule; somemodule.salutation('world');对于相同的模块访问,您几乎必须从替换类中提取方法,并将它们添加到globals()eiher中,使用类上的自定义方法(如使用.export())或使用通用函数i打开(如已列为答案的)。要记住的一点是:如果包装器每次都在创建一个新实例,而全局解决方案不是,那么最终会出现细微的不同行为。哦,而且你不能同时使用这两个——这是一个或另一个。

    更新

    来自Guido van Rossum:

    There is actually a hack that is occasionally used and recommended: a
    module can define a class with the desired functionality, and then at
    the end, replace itself in sys.modules with an instance of that class
    (or with the class, if you insist, but that's generally less useful).
    E.g.:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # module foo.py

    import sys

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

    sys.modules[__name__] = Foo()

    This works because the import machinery is actively enabling this
    hack, and as its final step pulls the actual module out of
    sys.modules, after loading it. (This is no accident. The hack was
    proposed long ago and we decided we liked enough to support it in the
    import machinery.)

    因此,实现您想要的目标的既定方法是在模块中创建一个类,作为模块的最后一个操作,用您的类的实例替换sys.modules[__name__],现在您可以根据需要使用__getattr____setattr____getattribute__

    请注意,如果使用此功能,则在执行sys.modules分配时,模块中的任何其他功能(如全局函数、其他函数等)都将丢失,因此请确保所需的一切都在替换类中。


    这是一个黑客,但您可以用类包装模块:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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__])


    不久前,guido声明所有特殊的方法查找新型类绕过了__getattr____getattribute__。邓德方法以前曾在模块上工作过——例如,您可以在这些技巧失效之前,通过定义__enter____exit__,将模块用作上下文管理器。

    最近,一些历史特性又卷土重来,其中包括模块__getattr__,因此,现有的黑客(在导入时用sys.modules中的类替换自己的模块)不再是必要的。

    在Python3.7+中,您只需使用一种明显的方法。要自定义模块上的属性访问,请在模块级别定义一个__getattr__函数,该函数应接受一个参数(属性名称),并返回计算值或引发AttributeError

    1
    2
    3
    4
    # my_module.py

    def __getattr__(name: str) -> Any:
        ...

    这还允许钩住"from"导入,即,您可以为诸如from my_module import whatever之类的语句返回动态生成的对象。

    在相关注释中,除了模块getattr,您还可以在模块级别定义一个__dir__函数来响应dir(my_module)。详见PEP 562。


    我们通常不会那样做。

    我们要做的就是这个。

    1
    2
    3
    4
    5
    6
    7
    8
    class A(object):
    ....

    # The implicit global instance
    a= A()

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

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

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


    类似于什么@h?vard建议,在我需要在一个模块上实现一些魔力的情况下(比如__getattr__,我将定义一个继承types.ModuleType的新类,并将其放入sys.modules中(可能会替换我的自定义ModuleType定义的模块)。

    请参阅Werkzeug的主要__init__.py文件,了解这一功能的相当强大的实现。


    这有点陈词滥调,但是…

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    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")

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

    它忽略所有包含"uuuu"的属性。

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


    这是我自己谦虚的贡献,@h的一点点缀?vard的答案评价很高,但更明确一点(因此@s.lott可能可以接受,尽管可能不足以满足OP要求):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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")


    在某些情况下,globals()字典就足够了,例如,您可以从全局范围中按名称实例化一个类:

    1
    2
    3
    from somemodule import * # imports SomeClass

    someclass_instance = globals()['SomeClass']()


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

    这是您的模块some_module.py

    1
    2
    3
    4
    5
    class Foo(object):
        pass

    class Bar(object):
        pass

    在另一个模块中:

    1
    2
    3
    import some_module

    Foo = getattr(some_module, 'Foo')

    动态执行此操作:

    1
    2
    3
    4
    5
    import sys

    __import__('some_module')
    mod = sys.modules['some_module']
    Foo = getattr(mod, 'Foo')