模块是否可以像对象一样具有属性?

2025-03-04 08:28:00
admin
原创
5
摘要:问题描述:使用 python 属性,我可以做到obj.y 调用一个函数而不是仅仅返回一个值。有没有办法用模块来实现这一点?我有一个案例,我需要module.y 调用一个函数,而不是仅仅返回存储在那里的值。解决方案 1:由于PEP 562已在 Python >= 3.7 中实现,现在我们可以这样做文件...

问题描述:

使用 python 属性,我可以做到

obj.y 

调用一个函数而不是仅仅返回一个值。

有没有办法用模块来实现这一点?我有一个案例,我需要

module.y 

调用一个函数,而不是仅仅返回存储在那里的值。


解决方案 1:

由于PEP 562已在 Python >= 3.7 中实现,现在我们可以这样做

文件:module.py

def __getattr__(name):
    if name == 'y':
        return 3
    raise AttributeError(f"module '{__name__}' has no attribute '{name}'")

other = 4

演示:

>>> import module
>>> module.y
3
>>> module.other
4
>>> module.nosuch
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "module.py", line 4, in __getattr__
    raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
AttributeError: module 'module' has no attribute 'nosuch'

raise AttributeError注意,如果在函数中省略__getattr__,则意味着函数以 结尾return None,则module.nosuch将获得 的值None

编辑

我的答案不能有setter和deleter。如果你需要,可以采用kxr的答案。

创建 的子类<class 'module'>,在该类中定义属性,然后将模块类更改为该类。

文件:mymodule.py

import sys

class This(sys.__class__):  # sys.__class__ is <class 'module'>
    _y = 3

    @property
    def y(self):          # do the property things in this class
        return self._y

    @y.setter
    def y(self, value):   # setter is also OK
        self._y = value

other = 4

sys.modules[__name__].__class__ = This  # change module class into This

演示:

>>> import mymodule
>>> mymodule.y
3
>>> mymodule.other
4
>>> mymodule.y = 5
>>> mymodule.y
5
>>> mymodule._y
5    # to prove that setter works

我太菜了,不知道它为什么有效。所以功劳应该归于 kxr。

解决方案 2:

只有新式类的实例才可以具有属性。你可以将这样的实例存储在 中,让 Python 相信它是一个模块sys.modules[thename] = theinstance。因此,例如,你的 m.py 模块文件可以是:

import sys

class _M(object):
    def __init__(self):
        self.c = 0
    def afunction(self):
        self.c += 1
        return self.c
    y = property(afunction)

sys.modules[__name__] = _M()

解决方案 3:

我这样做是为了正确继承模块的所有属性,并被 isinstance() 正确识别

import types

class MyModule(types.ModuleType):
    @property
    def y(self):
        return 5


>>> a=MyModule("test")
>>> a
<module 'test' (built-in)>
>>> a.y
5

然后您可以将其插入到 sys.modules 中:

sys.modules[__name__] = MyModule(__name__)  # remember to instantiate the class

解决方案 4:

根据John Lin 的回答:

def module_property(func):
    """Decorator to turn module functions into properties.
    Function names must be prefixed with an underscore."""
    module = sys.modules[func.__module__]

    def base_getattr(name):
        raise AttributeError(
            f"module '{module.__name__}' has no attribute '{name}'")

    old_getattr = getattr(module, '__getattr__', base_getattr)

    def new_getattr(name):
        if f'_{name}' == func.__name__:
            return func()
        else:
            return old_getattr(name)

    module.__getattr__ = new_getattr
    return func

用法(请注意前导下划线),在the_module.py

@module_property
def _thing():
    return 'hello'

然后:

import the_module

print(the_module.thing)  # prints 'hello'

前导下划线是必要的,以区分属性化函数和原始函数。我想不出重新分配标识符的方法,因为在装饰器执行期间,它尚未被分配。

请注意,IDE 不会知道该属性的存在并且会显示红色波浪。

解决方案 5:

更新 Python 3

在 Python 3 中,至少从 3.7 开始,模块的类可以更改为子类,因此真正的模块属性(或描述符)现在很容易实现 - 比 PEP 562 模块更可靠、更强大__getattr__

# mymodule.py

class ThisMod(sys.modules[__name__].__class__):
    y = property(lambda self: "Hi this is module %s." % __name__)
    const = property(lambda self: _const)  # block setting
sys.modules[__name__].__class__ = ThisMod

_const = 77

# rest of module code ...

兼容 Python 2

一个典型的用例是:用一些(少量)动态属性丰富(巨大的)现有模块 - 而不将所有模块内容转换为类布局。不幸的是,最简单的模块类修补程序(如)会sys.modules[__name__].__class__ = MyPropertyModule失败TypeError: __class__ assignment: only for heap types。因此需要重新连接模块创建。

这种方法不需要 Python 导入钩子,只需在模块代码顶部添加一些序言即可:

# propertymodule.py
""" Module property example """

if '__orgmod__' not in globals():
    
    # constant prolog for having module properties / supports reload()
    
    print "PropertyModule stub execution", __name__
    import sys, types
    class PropertyModule(types.ModuleType):
        def __str__(self):
            return "<PropertyModule %r from %r>" % (self.__name__, self.__file__)
    modnew = PropertyModule(__name__, __doc__)
    modnew.__modclass__ = PropertyModule        
    modnew.__file__ = __file__
    modnew.__orgmod__ = sys.modules[__name__]
    sys.modules[__name__] = modnew
    exec sys._getframe().f_code in modnew.__dict__

else:
    
    # normal module code (usually vast) ..
    
    print "regular module execution"
    a = 7
    
    def get_dynval(module):
        return "property function returns %s in module %r" % (a * 4, module.__name__)    
    __modclass__.dynval = property(get_dynval)

用法:

>>> import propertymodule
PropertyModule stub execution propertymodule
regular module execution
>>> propertymodule.dynval
"property function returns 28 in module 'propertymodule'"
>>> reload(propertymodule)   # AFTER EDITS
regular module execution
<module 'propertymodule' from 'propertymodule.pyc'>
>>> propertymodule.dynval
"property function returns 36 in module 'propertymodule'"

注意:类似的东西from propertymodule import dynval当然会产生冻结的副本 - 对应于dynval = someobject.dynval

解决方案 6:

简短的回答:使用proxy_tools

proxy_tools包尝试提供@module_property功能。

它安装有

pip install proxy_tools

对@Marein 的例子稍作修改,the_module.py我们输入

from proxy_tools import module_property

@module_property
def thing():
    print(". ", end='')  # Prints ". " on each invocation
    return 'hello'

现在从另一个脚本我可以做

import the_module

print(the_module.thing)
# . hello

意外行为

这个解决方案并非毫无问题。也就是说,the_module.thing不是字符串!它是一个proxy_tools.Proxy对象,其特殊方法已被覆盖,因此它模仿字符串。以下是一些说明这一点的基本测试:

res = the_module.thing
# [No output!!! Evaluation doesn't occur yet.]

print(type(res))
# <class 'proxy_tools.Proxy'>

print(isinstance(res, str))
# False

print(res)
# . hello

print(res + " there")
# . hello there

print(isinstance(res + "", str))
# . True

print(res.split('e'))
# . ['h', 'llo']

在内部,原始函数存储到 the_module.thing._Proxy__local

print(res._Proxy__local)
# <function thing at 0x7f729c3bf680>

进一步思考

说实话,我很困惑为什么模块没有内置此功能。我认为问题的关键在于它the_module是类的一个实例。设置“模块属性”相当于在该类的实例types.ModuleType上设置属性,而不是在类本身上设置属性。有关更多详细信息,请参阅此答案。types.ModuleType

我们实际上可以按如下方式实现属性types.ModuleType,尽管结果并不理想。我们不能直接修改内置类型,但我们可以诅咒它们:

# python -m pip install forbiddenfruit
from forbiddenfruit import curse
from types import ModuleType
# curse has the same signature as setattr.
curse(ModuleType, "thing2", property(lambda module: f'hi from {module.__name__}'))

这给了我们一个存在于所有模块中的属性。这有点不方便,因为我们打破了所有模块的设置行为:

import sys

print(sys.thing2)
# hi from sys

sys.thing2 = 5
# AttributeError: can't set attribute

解决方案 7:

根据用户2124834的回答:

import sys
class AttrGeter:
    def __new__(cls, gt):
        if isinstance(gt, cls):
            return gt
        else:
            o = super().__new__(cls)
            o.oldgetattr = gt
            o.funcmap = {}
            return o

    def __call__(self, name):
        name2 = "_" + name
        if name2 in self.funcmap:
            return self.funcmap[name2]()
        else:
            return self.oldgetattr(name)

    def add(self, func):
        self.funcmap[func.__name__] = func


def module_property(func):
    """Decorator to turn module functions into properties.
    Function names must be prefixed with an underscore."""
    module = sys.modules[func.__module__]
    def base_getattr(name):
        raise AttributeError(
            f"module '{module.__name__}' has no attribute '{name}'")
    ag = AttrGeter(getattr(module, '__getattr__', base_getattr))
    module.__getattr__ = ag
    ag.add(func)
    return func

在 the_module.py 中的用法(请注意前导下划线):

@module_property
def _thing():
    return 'hello'

然后:

import the_module

print(the_module.thing)  # prints 'hello'

我在原始解决方案中使用了装饰器dict,而不是嵌套装饰器function。当在一个模块中多次使用装饰器时,这可能会更有效率。

解决方案 8:

Google 向我介绍了这个gist,它提供了一对装饰器(mod_propertycached_mod_property)及其简单的实现。我试了一下,效果很好。

我从那个 gist 中获取了代码,并从dickens中获取了一些代码,并将其全部组合成一个实用程序模块,并在此处提供完整的演示。

显现:

  • 缓存(用于函数)

  • cached_property (例如方法)

  • 模块属性

  • cached_module_property

  • 类属性

  • cached_class_property

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1317  
  IPD(Integrated Product Development)项目管理作为一种先进的产品开发管理模式,在众多企业中得到了广泛应用。它旨在通过整合企业的各种资源,实现产品的快速、高质量开发,从而提升企业的市场竞争力。IPD项目管理涵盖多个方面,其中五大核心要素尤为关键,它们相互关联、相互影响,共同构成了IPD项目...
IPD流程中PDCP是什么意思   5  
  IPD(Integrated Product Development)即集成产品开发,是一套先进的、成熟的产品开发管理思想、模式和方法。它强调将产品开发视为一个完整的流程,涵盖从概念产生到产品退市的全生命周期,通过跨部门团队的协同合作,实现缩短产品上市时间、提高产品质量、降低成本等目标。IPD测试流程作为IPD体系的重...
华为IPD流程   7  
  华为 IPD 产品开发流程是一套先进且成熟的产品开发管理体系,在提升企业产品竞争力、促进团队协作等方面发挥着重要作用。它以市场为导向,强调跨部门团队的协同合作,旨在实现产品的快速、高质量交付,满足客户需求并提升企业的经济效益。通过深入了解和应用 IPD 产品开发流程,企业能够优化内部资源配置,提高团队协作效率,从而在激...
IPD管理流程   8  
  IPD(Integrated Product Development)即集成产品开发,是一套先进的、成熟的产品开发管理思想、模式和方法。它强调将产品开发视为一个完整的流程,涵盖从概念产生到产品退市的全生命周期,涉及市场、研发、生产、销售、售后等多个部门的协同合作。构建高效的项目管理文化对于企业的发展至关重要,而IPD培...
IPD开发流程管理   5  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

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

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

内置subversion和git源码管理

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

免费试用