Чи можуть модулі мати властивості так само, як і об’єкти?


98

За допомогою властивостей python я можу зробити це таким, що

obj.y 

викликає функцію, а не просто повертає значення.

Чи є спосіб зробити це за допомогою модулів? У мене є справа, де я хочу

module.y 

викликати функцію, а не просто повертати значення, що там зберігається.


2
Дивіться __getattr__модуль для більш сучасного рішення.
Вім

Відповіді:


56

Лише екземпляри класів нового стилю можуть мати властивості. Ви можете змусити 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
Хтось ще пробував це? Коли я поміщаю цей код в один файл x.py та імпортую його з іншого, тоді виклик xy призводить до AttributeError: 'NoneType' об'єкт не має атрибута 'c', оскільки _M якось має значення None ...
Stephan202

3
Справді, код працює на інтерпретаторі. Але коли я поміщаю його у файл (скажімо, bowwow.py) та імпортую з іншого файлу (otherfile.py), він більше не працює ...
Stephan202,

4
З: Чи були б якісь особливі переваги у виведенні класу екземпляра з того, types.ModuleTypeяк показано у @ Unknown, інакше дуже подібній відповіді?
мартіно

11
Лише екземпляри класів нового стилю можуть мати властивості. Це не причина: модулі є екземплярами класів нового стилю, оскільки вони є екземплярами builtins.module, що саме є екземпляром type(що є визначенням класу нового стилю). Проблема полягає в тому, що властивості повинні бути в класі, а не скажеш: якщо ви це зробите f = Foo(), f.some_property = property(...)він буде не в змозі точно так же , як якщо б ви наївний поставити його в модулі. Рішення полягає в тому, щоб помістити його в клас, але оскільки ви не хочете, щоб усі модулі мали властивість, ви підклас (див. Відповідь Unknown).
Танатос

3
@Joe, зміна globals()(збереження ключів цілими, але скидання значень на None) при перев’язуванні імені sys.modules- це проблема Python 2 - Python 3.4 працює за призначенням. Якщо вам потрібен доступ до об'єкта класу в Py2, додайте, наприклад, _M._cls = _Mвідразу після classоператора (або сховайте його еквівалентно в якомусь іншому просторі імен) і отримайте доступ до нього, як self._clsу методах, що вимагають цього ( type(self)може бути нормально, але ні, якщо ви також виконуєте будь-який підклас _M) .
Алекс Мартеллі,

54

Я б зробив це для того, щоб належним чином успадкувати всі атрибути модуля і бути правильно визначеним за допомогою 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

Здається, це працює лише в найпростіших випадках. Можливі проблеми: (1) деякі помічники імпорту можуть також очікувати інших атрибутів, таких як такі, __file__що повинні бути визначені вручну, (2) імпорт, зроблений у модулі, що містить клас, не буде "видимим" під час роботи тощо ...
tutuDajuju

1
Необов’язково отримувати підклас types.ModuleType, будь-який (новий стиль) клас підійде. Які саме атрибути спеціального модуля ви бажаєте успадкувати?
martineau

Що робити, якщо оригінальний модуль - це пакет, і я хочу отримати доступ до модулів під оригінальним модулем?
kawing-chiu

2
@martineau У вас буде виставлення модуля, ви можете вказати ім'я модуля під __init__час екземпляру, і ви отримаєте правильну поведінку під час використання isinstance.
Вім

@wim: Набрані бали, хоча, чесно кажучи, жоден, здається, не є настільки важливим IMO.
Мартіно

34

Оскільки 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.


2
Виходячи з цього, я додав ще один відповідь: stackoverflow.com/a/58526852/2124834

3
Це лише половина власності. Немає сетера.
wim

На жаль, не представляється важким повідомити інструментарій про такі атрибути (?) ( Getattr викликається, лише якщо не знайдено жодного регулярного члена)
olejorgenb

9

На основі відповіді Джона Ліна :

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 не знатимуть, що властивість існує, і відображатиме червоні хвилясті хвилі.


Чудово! У порівнянні з властивістю класу, @property def x(self): return self._xя думаю, def thing()без підкреслення є більш звичним. І чи можете ви також створити декоратор "властивості модуля" у своїй відповіді?
Джон Лін

2
@JohnLin, я спробував реалізувати вашу def thing()пропозицію. Проблема в тому, що викликається __getattr__лише для відсутніх атрибутів . Але після @module_property def thing(): …прогонів the_module.thingвизначається, тому getattr ніколи не буде викликатися. Нам потрібно якось зареєструватися thingв декораторі, а потім видалити його з простору імен модуля. Я спробував повернутися Noneз декоратора, але потім thingвизначається як None. Можна було б зробити, @module_property def thing(): … del thingале я вважаю це гіршим, ніж використання thing()як функції
Бен Марес

Добре, я бачу, що немає "модуля встановлення властивостей", ні "модуля __getattribute__". Дякую.
Джон Лін

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


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

1
Як це краще, ніж просто зробити модуль екземпляром реального класу, як показано у відповіді @Alex Martelli?
Мартіно

1
Ви сказали щось інше, що для мене не має сенсу. Візьміть цю справу за те, щоб мати @module_propertyдекоратора. Взагалі кажучи, вбудований @propertyдекоратор використовується, коли клас визначений, а не після того, як його екземпляр був створений, тому я б припустив, що те саме стосується властивості модуля, і це з відповіддю Алекса - нагадаємо, це питання задає Msgstr "Чи можуть модулі мати властивості так само, як і об'єкти?". Однак це можна добавити це після цього і я змінив мій попередній фрагмент коду , щоб проілюструвати спосіб , який може бути зроблено.
мартіно

1
Бен: Ознайомившись із кодом у вашому конкретному прикладі, я думаю, я розумію, на що ви зараз прийшли. Я також думаю, що нещодавно натрапив на техніку реалізації чогось подібного до властивостей модуля, який не вимагає заміни модуля на екземпляр класу, як у відповіді Алекса, хоча на даний момент я не впевнений, чи є спосіб зробити це це через декоратор - зв’яжеться з вами, якщо я досягну прогресу.
Мартіно

1
Добре, ось посилання на відповідь на інше питання, яке містить основну ідею.
Мартіно

1
Ну, принаймні у випадку з a cached_module_property, __getattr__()корисний той факт, що його більше не буде викликано, якщо визначено атрибут. (схоже на те, що робить functools.cached_property).
мартіно
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.