模块可以像对象一样拥有属性吗?

使用 python 属性,我可以让它

obj.y

calls a function rather than just returning a value.

有没有一种方法可以做到这一点与模块? 我有一个情况下,我想要的

module.y

to call a function, rather than just returning the value stored there.

35628 次浏览

只有新样式类的实例才能具有属性。通过将这样一个实例存储在 sys.modules[thename] = theinstance中,可以使 Python 相信它是一个模块。例如,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()

这样做是为了正确地继承模块的所有属性,并由 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.module 中:

sys.modules[__name__] = MyModule(__name__)  # remember to instantiate the class

更新 Python 3

在 Python 3中,至少从3.7开始,模块类可以更改为子类,因此现在很容易实现真正的模块属性(或描述符)——比 PEP 562模块 __getattr__更加坚实和强大。

# mymodule.py


class ThisMod(sys.modules[__name__].__class__):
y = property(lambda self: "Hi this is module %s." % __name__)
const = property(lambda self: _const)  # block setting
sys.modules[__name__].__class__ = ThisMod


_const = 77


# rest of module code ...

Python 2兼容

一个典型的用例是: 使用一些(少量)动态属性来丰富(巨大的)现有模块——而不是将所有模块内容转换为类布局。 不幸的是,像 sys.modules[__name__].__class__ = MyPropertyModule这样最简单的模块类补丁在 TypeError: __class__ assignment: only for heap types中失败了,因此需要重新连接模块创建。

这种方法不需要 Python 导入钩子,只需在模块代码之上添加一些 prolog 即可:

# 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

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

File: module.py 文件: 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'

注意,如果在 __getattr__函数中省略了 raise AttributeError,这意味着该函数以 return None结束,那么 module.nosuch将得到一个值 None

剪辑

我的答案不能有设置器和删除器。如果需要,采用 kxr 的答案。

创建 <class 'module'>的子类,定义该类中的属性,然后将模块类更改为该类。

文件: mymodule.py

import sys


class This(sys.__class__):  # sys.__class__ is <class 'module'>
_y = 3


@property
def y(self):          # do the property things in this class
return self._y


@y.setter
def y(self, value):   # setter is also OK
self._y = value


other = 4


sys.modules[__name__].__class__ = This  # change module class into This

演示:

>>> import mymodule
>>> mymodule.y
3
>>> mymodule.other
4
>>> mymodule.y = 5
>>> mymodule.y
5
>>> mymodule._y
5    # to prove that setter works

我太新手,不知道为什么它的工作原理。因此,学分应该去 kxr。

基于 林的回答:

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 不会知道该属性的存在,而是会显示红色波形。

简短的回答: 使用 proxy_tools

The proxy_tools package attempts to provide @module_property functionality.

It installs with

pip install proxy_tools

the_module.py中,我们对@Marein 的例子稍作修改

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>

Further thoughts

Honestly, I'm baffled about why modules don't have this functionality built in. I think the crux of the matter is that the_module is an instance of the types.ModuleType class. Setting a "module property" amounts to setting a property on an 例子 of this class, rather than on the types.ModuleType class itself. For more details, see 这个答案.

我们实际上可以像下面这样在 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

基于 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

_ module. py 中的用法(注意前面的下划线) :

@module_property
def _thing():
return 'hello'

然后:

import the_module


print(the_module.thing)  # prints 'hello'

在原始解决方案中,我使用 dict代替嵌套的 function。如果在一个模块中多次使用装饰器,那么效率可能会更高。