模块是否可以像对象一样具有属性?
- 2025-03-04 08:28:00
- admin 原创
- 5
问题描述:
使用 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_property
,cached_mod_property
)及其简单的实现。我试了一下,效果很好。
我从那个 gist 中获取了代码,并从dickens中获取了一些代码,并将其全部组合成一个实用程序模块,并在此处提供完整的演示。
显现:
缓存(用于函数)
cached_property (例如方法)
模块属性
cached_module_property
类属性
cached_class_property
- 2025年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理必备:盘点2024年13款好用的项目管理软件
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)