116

使用 python 属性,我可以做到这一点

obj.y 

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

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

module.y 

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

4

7 回答 7

61

只有新式类的实例才能具有属性。你可以让 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()
于 2009-05-19T01:09:42.430 回答
59

我这样做是为了正确继承模块的所有属性,并被 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
于 2009-05-19T02:40:12.230 回答
58

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

文件:模块.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

于 2019-07-30T02:21:42.207 回答
14

基于约翰林的回答

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 不会知道该属性存在并且会显示红色波浪。

于 2019-10-23T15:58:19.067 回答
5

一个典型的用例是:用一些(很少)动态属性来丰富一个(巨大的)现有模块——而不是将所有模块的东西变成一个类布局。不幸的是,一个最简单的模块类补丁(sys.modules[__name__].__class__ = MyPropertyModuleTypeError: __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

于 2016-01-16T17:13:19.393 回答
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。设置“模块属性”相当于在此类的实例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
于 2020-02-07T19:57:16.517 回答
0

基于user2124834的回答

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'

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

于 2021-04-08T08:37:51.663 回答