Применение декоратора ко всем функциям в пакете Python

Мой вопрос очень похож на этот, но в моем случае принятый ответ не украшает все функции в пакете, когда они используются внутри пакета, и я не уверен, почему.

Например, у меня есть такой проект:

project/
    package/ 
        __init__.py
        module_a.py
        module_b.py
    main.py

__init__.py

from .module_a import *
from .module_b import *

import types
# This is the decorator that will be used
from functools import lru_cache

for name, obj in list(globals().items()):
    if isinstance(obj, types.FunctionType):
        globals()[name] = lru_cache(maxsize=100)(obj)

module_a.py

from .module_b import b

def a(arg):
    return b

module_b.py

def b(arg):
    return arg

main.py

import package

print(package.a.cache_info())
print(package.a(None).cache_info())
print(package.b.cache_info())

Когда пакет импортируется, __init__.py прекрасно украшает функции в globals при пошаговом выполнении кода. Однако, если я выполняю main.py, я получаю следующую ошибку:

C:\Users\pbreach\Anaconda3\python.exe C:/Users/pbreach/PycharmProjects/project/main.py
Traceback (most recent call last):
CacheInfo(hits=0, misses=0, maxsize=100, currsize=0)
  File "C:/Users/pbreach/PycharmProjects/project/main.py", line 4, in <module>
    print(package.a(None).cache_info())
AttributeError: 'function' object has no attribute 'cache_info'

Это означало бы, что b не украшается при импорте из module_b в module_a.

Почему это происходит только во второй строке? Как можно этого добиться?

Я нормально выполняю декорирование либо во время импорта в __init__.py, либо в main.py, но я бы предпочел не применять декоратор внутри каждого модуля в package, поскольку в моем случае их довольно много.

РЕДАКТИРОВАТЬ:

Я думаю, проблема в том, что globals в __init__.py - это другое пространство имен, чем когда b импортируется в module_a, что означает наличие двух разных экземпляров одной и той же функции. Это можно обойти?


person pbreach    schedule 06.12.2016    source источник


Ответы (1)


Вы импортируете b из module_b в module_a, прежде чем у вас появится возможность украсить его functools.lru_cache.

Единственная жизнеспособная альтернатива, которую я вижу, - это сначала явное декорирование функций, которые импортируются и используются в других подмодулях, а затем применение декоратора ко всем другим функциям.

Используя ваш пример, сначала украсьте b из module_b, а затем украсьте остальные:

from package import module_b

import types
# This is the decorator that will be used
from functools import lru_cache
module_b.b = lru_cache(maxsize=100)(module_b.b)

from .module_a import *
from .module_b import *

for name, obj in list(globals().items()):
    if isinstance(obj, types.FunctionType):
        globals()[name] = lru_cache(maxsize=100)(obj)

Другой вариант, как я сказал в комментарии, заключался бы в использовании предложения if внутри модулей, которые содержат функции, которые включаются в другие модули, если бы там происходила упаковка.

Итак, в module_b.py вы можете сделать что-то вроде этого:

if __name__ != '__main__':
    from functools import lru_cache
    b = lru_cache(b)

это только улавливает случай, когда модуль не запущен как __main__. Теперь, когда другой модуль включает этот модуль и его тело выполняется, упаковка будет выполняться здесь вместо __init__.py.

person Dimitris Fasarakis Hilliard    schedule 06.12.2016
comment
Теперь я вижу, что это имеет смысл ... единственная проблема в том, что в моем случае есть 12 модулей с более чем 400 различными функциями и импортом между ними, но только в форме from . import module_x - person pbreach; 06.12.2016
comment
Можно ли выполнить итерацию по всем функциям в каждом модуле в __init.py__ и применить декоратор перед импортом *? - person pbreach; 06.12.2016
comment
Если бы вы могли упорядочить их таким образом, чтобы гарантировать, что функция из module_X уже была упакована перед импортом в module_Y и использованием, да. Другой вариант может заключаться в использовании только if __name__ != __main__: в каждом подмодуле, который импортируется в другой подмодуль и украшении внутри этого предложения. - person Dimitris Fasarakis Hilliard; 06.12.2016
comment
Хорошо, я думаю, что это может сработать, попробую на рабочем ПК - person pbreach; 06.12.2016
comment
Первый вариант был невозможен (если бы импорт был *, был бы циклический импорт), но второй работает! Это был хороший познавательный опыт. Спасибо! - person pbreach; 06.12.2016