Могут ли модули иметь свойства так же, как объекты могут?
С свойствами python я могу сделать так, чтобы
obj.y
вызывает функцию, а не просто возвращает значение.
Есть ли способ сделать это с помощью модулей? У меня есть случай, когда я хочу
module.y
чтобы вызвать функцию, а не просто вернуть сохраненное там значение.
Ответы
Ответ 1
Только экземпляры классов нового стиля могут иметь свойства. Вы можете заставить 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()
Ответ 2
Я хотел бы сделать это для того, чтобы правильно наследовать все атрибуты модуля и быть правильно идентифицирован 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
Ответ 3
Типичный пример использования: обогащение (огромного) существующего модуля некоторыми (несколькими) динамическими атрибутами - без превращения всего материала модуля в макет класса.
К сожалению, самый простой патч класса модуля, такой как 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
Поскольку 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
.