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

Posted

技术标签:

【中文标题】将装饰器应用于python包中的所有函数【英文标题】:Applying decorator to all functions in a python package 【发布时间】:2017-04-20 18:28:08 【问题描述】:

我的问题与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 的每个模块中应用装饰器,因为在我的情况下有很多.

编辑:

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

【问题讨论】:

【参考方案1】:

在您有机会用functools.lru_cache 装饰它之前,您正在将module_bmodule_b 导入module_a

我看到的唯一可行的替代方案是首先显式装饰导入并在其他子模块中使用的函数,然后将装饰器应用于所有其他函数。

使用您的示例,首先从module_b 装饰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

【讨论】:

我现在觉得这很有意义......唯一的问题是,在我的用例中,有 12 个模块具有超过 400 个不同的功能和它们之间的导入,但只有from . import module_x 是否可以遍历__init.py__中每个模块中的所有函数并在*导入之前应用装饰器? 如果您可以以确保来自module_X 的函数在导入module_Y 并使用之前已经被包装的方式订购它们,是的。另一种选择只能在每个子模块中使用if __name__ != __main__:,该子模块被导入另一个子模块并在该子句中进行装饰。 好的,我认为这可能会在工作 PC 上尝试一下 第一个选项是不可能的(如果导入是 * 会有一个循环导入),但第二个可行!这是一次很好的学习经历。谢谢!

以上是关于将装饰器应用于python包中的所有函数的主要内容,如果未能解决你的问题,请参考以下文章

python装饰器

装饰器何时以及如何应用到 @angular 包中的装饰类

Python装饰器详解

python语言中的装饰器详解

第33天pytho学习装饰器:高级函数部分演示

Python装饰器