问题:模块可以具有与对象相同的属性吗?
使用python属性,我可以做到
obj.y 调用一个函数,而不只是返回一个值。
有没有办法用模块来做到这一点?我有一个想要的情况
module.y 调用一个函数,而不仅仅是返回存储在那里的值。
回答 0
只有新式类的实例才能具有属性。通过将其存储在中,可以使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
我这样做是为了正确继承模块的所有属性,并由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
由于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。
回答 3
根据约翰·林的回答:
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不会知道该属性存在,并且会显示红色波形。
回答 4
一个典型的用例是:使用一些动态属性来丰富(庞大的)现有模块-而无需将所有模块内容转换为类布局。不幸的是,最简单的模块类补丁如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
回答 5
一个简单的答案:使用 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
