模块的属性可以与对象相同吗?

时间:2009-05-19 01:03:31

标签: python properties python-module

使用python属性,我可以这样做

obj.y 

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

有没有办法用模块做到这一点?我有一个我想要的案例

module.y 

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

7 个答案:

答案 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)

基于John Lin's answer

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_moduletypes.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)

基于user2124834's answer

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。在一个模块中多次使用装饰器可能会更有效。