使用python属性,我可以这样做
obj.y
调用函数而不是仅返回值。
有没有办法用模块做到这一点?我有一个我想要的案例
module.y
调用函数,而不是只返回存储在那里的值。
答案 0 :(得分:55)
只有新式类的实例才能拥有属性。你可以让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()
答案 1 :(得分:49)
我会这样做是为了正确地继承模块的所有属性,并通过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
答案 2 :(得分:12)
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
的值将为{{1 }}。
答案 3 :(得分:5)
一个典型的用例是:使用一些(少数)动态属性丰富(巨大的)现有模块 - 而不将所有模块内容转换为类布局。
不幸的是,像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
答案 4 :(得分:2)
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 :(得分:1)
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
类的实例。设置“模块属性”等于在此类的 instance 上而不是在types.ModuleType
类本身上设置属性。有关更多详细信息,请参见this answer。
实际上,我们可以在types.ModuleType
上实现属性,如下所示,尽管效果并不理想。我们无法直接修改内置类型,但是我们可以curse:
# 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
答案 6 :(得分:0)
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
。在一个模块中多次使用装饰器可能会更有效。