将装饰器应用于python包中的所有函数

时间:2016-12-06 07:21:22

标签: python python-3.x python-import memoization python-decorators

我的问题与this one非常相似,但在我的情况下,接受的答案并没有在包中使用时装饰包中的所有功能,而且我不确定原因。

例如,我有一个像这样设置的项目:

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__.pymain.py导入过程中进行装饰很好,但我不想在package中的每个模块中应用装饰器,就像我的情况那样他们中有不少人。

编辑:

我认为问题是因为globals中的__init__.pyb导入module_a时的名称空间不同,这意味着同一个函数有两个不同的实例。有办法解决这个问题吗?

1 个答案:

答案 0 :(得分:1)

您正在bmodule_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