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

Jos*_*son 87 python properties python-module

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

obj.y 
Run Code Online (Sandbox Code Playgroud)

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

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

module.y 
Run Code Online (Sandbox Code Playgroud)

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

Ale*_*lli 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()
Run Code Online (Sandbox Code Playgroud)

编辑:删除了对全局变量的隐式依赖(与示例的要点无关,但是通过使原始代码失败而确实混淆了事情!).

  • *只有新式类的实例才能拥有属性.*这不是原因:modules*是新式类的实例,因为它们是`builtins.module`的实例,它本身就是`的实例. type`(这是新式类的定义).问题是属性必须在类上,而不是实例:如果你执行`f = Foo()`,`f.some_property = property(...)`,它将以与你相同的方式失败天真地把它放在一个模块中.解决方案是将它放在类中,但由于您不希望所有模块都具有该属性,因此您将子类化(请参阅Unknown的答案). (8认同)
  • 问:是否有任何特殊的优势从`types.ModuleType`派生实例的类,如@ Unknown的其他非常相似的答案所示? (4认同)
  • 的确,代码可以在解释器上工作。但是当我将其放入文件(例如bowwow.py)并从另一个文件(otherfile.py)导入时,它不再起作用... (3认同)
  • 有人尝试过吗?当我将此代码放入一个文件x.py并从另一个文件导入时,然后调用xy会导致AttributeError:'NoneType'对象不具有属性'c',因为_M某种程度上具有值None ... (2认同)
  • @Joe,在`sys.modules`中重命名的`globals()`(保持键完好但将值重置为'None`)的改变是Python 2问题 - Python 3.4按预期工作.如果您需要访问Py2中的类对象,请在`class`语句之后添加例如`_M._cls = _M`(或者在其他名称空间中等效地存储它),并在需要的方法中将其作为`self._cls`访问它(`type(self)`可能没问题,但如果你也做`_M`的任何子类,则不行.) (2认同)

Unk*_*own 51

我这样做是为了正确地继承模块的所有属性,并通过isinstance()正确识别

import types

class MyModule(types.ModuleType):
    @property
    def y(self):
        return 5


>>> a=MyModule("test")
>>> a
<module 'test' (built-in)>
>>> a.y
5
Run Code Online (Sandbox Code Playgroud)

然后你可以将它插入到sys.modules中:

sys.modules[__name__] = MyModule(__name__)  # remember to instantiate the class
Run Code Online (Sandbox Code Playgroud)

  • @martineau 您将拥有一个模块代表,您可以在`__init__` 实例时指定模块名称,并且在使用`isinstance` 时您将获得正确的行为。 (2认同)

Joh*_*Lin 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
Run Code Online (Sandbox Code Playgroud)

用法:

>>> 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'
Run Code Online (Sandbox Code Playgroud)

请注意,如果您raise AttributeError__getattr__函数中省略,则表示函数以结束return None,则的module.nosuch值将为None

  • 这只是财产的一半。没有二传手。 (3认同)
  • 基于此,我添加了另一个答案:/sf/answers/4096879671/ (2认同)

小智 11

基于约翰林的回答

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
Run Code Online (Sandbox Code Playgroud)

用法(注意前导下划线),在the_module.py

@module_property
def _thing():
    return 'hello'
Run Code Online (Sandbox Code Playgroud)

然后:

import the_module

print(the_module.thing)  # prints 'hello'
Run Code Online (Sandbox Code Playgroud)

前导下划线是区分属性化函数与原始函数所必需的。我想不出重新分配标识符的方法,因为在装饰器执行期间,它还没有被分配。

请注意,IDE 不会知道该属性存在并会显示红色波浪。

  • @JohnLin,我尝试实现您的“def thing()”建议。问题是`__getattr__`只会被调用[缺少属性](/sf/ask/229465421/)。但是在 `@module_property def thing(): ...` 运行之后,`the_module.thing` 被定义,所以 __getattr__ 永远不会被调用。我们需要以某种方式在装饰器中注册“thing”,然后将其从模块的名称空间中删除。我尝试从装饰器返回“None”,但随后“thing”被定义为“None”。人们可以做 `@module_property def thing(): ... del thing` 但我发现这比使用 `thing()` 作为函数更糟糕 (2认同)

kxr*_*kxr 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)
Run Code Online (Sandbox Code Playgroud)

用法:

>>> 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'"
Run Code Online (Sandbox Code Playgroud)

注意:诸如此类的东西from propertymodule import dynval会产生一个冻结的课程副本-对应于dynval = someobject.dynval