模块上的 __getattr__

2025-01-10 08:47:00
admin
原创
99
摘要:问题描述:__getattr__如何在类、模块上实现等效功能?例子当调用模块的静态定义属性中不存在的函数时,我希望在该模块中创建一个类的实例,并使用与模块上的属性查找失败相同的名称调用该方法。class A(object): def salutation(self, accusative): ...

问题描述:

__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

解决方案 1:

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

  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()类上的自定义方法(我喜欢使用.export())或通用函数(例如已经列出的答案)。有一件事要记住:如果包装器每次都创建一个新实例,而全局解决方案不是,那么您最终会得到略有不同的行为。哦,你不能同时使用两者 - 只能选择其中之一。


更新

来自Guido van Rossum:

实际上,有一种偶尔使用并推荐的技巧:模块可以定义一个具有所需功能的类,然后在最后用该类的实例(或者,如果您坚持的话,用该类替换 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 中拉出。(这并非偶然。此 hack 很久以前就被提出,我们决定在导入机制中支持它。)

因此,实现您想要的目标的既定方法是在模块中创建一个单独的类,并将其作为模块的最后一项操作替换为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:

不久前,Guido 宣布新式类中的所有特殊方法查找都会绕过__getattr____getattribute__。Dunder 方法之前可以在模块上使用 - 例如,在这些技巧失效__enter__之前,您只需定义和即可将模块用作上下文管理器。__exit__

最近,一些历史特性又重新回归,__getattr__其中包括模块,因此现有的 hack(模块在sys.modules导入时用类替换自身)已经不再必要。

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

# my_module.py

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

这还将允许挂钩到“来自”导入,即,您可以返回诸如这样的语句的动态生成的对象from my_module import whatever

相关提示:除了模块 getattr 之外,您还可以__dir__在模块级别定义一个函数来响应dir(my_module)。有关详细信息,请参阅PEP 562。

解决方案 3:

这是一个 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__])

解决方案 4:

我们通常不这么做。

我们做的就是这个。

class A(object):
....

# The implicit global instance
a= A()

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

为什么?这样隐式的全局实例才是可见的。

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

解决方案 5:

与@Håvard S 提出的类似,如果我需要在模块上实现一些魔法(比如__getattr__),我会定义一个继承自的新类types.ModuleType并将其放入sys.modules(可能替换定义我的自定义的模块ModuleType)。

请参阅Werkzeug__init__.py的主文件来了解相当强大的实现。

解决方案 6:

这很黑客,但是...

# 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

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

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

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

解决方案 7:

这是我自己的一些微薄贡献 - 对@Håvard S 的高度评价的答案进行了稍微的修饰,但更加明确(因此它可能被@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")

解决方案 8:

对于模块,请参阅PEP-562,自 3.7 版开始存在:


(注:写完这篇文章后,我意识到Wim 的回答就是这么说的,但当然,我不知怎么地错过了翻阅所有现已过时的答案的机会 :-(

尽管从收听精彩的 Python Bytes 播客中得知有此功能,但仍然如此。

我们确实,确实需要在答案上加一个弃用标志。

尽管如此,我还是希望有人会发现实际的工作代码很有用。


我无法理解 PEP,因此进行了一些测试来使其发挥作用:

模块1.py
from datetime import date, datetime

_OBSOLETES = {
    "oldanswer" : 24
}

datenow = date.today()

def __getattr__(name):
    if name == "timenow":
        return datetime.now()
    if (v:=_OBSOLETES.get(name)):
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用