下面是一个简单的代码示例,可能有助于解释我的问题。
file_1.py
from functools import lru_cache
from file_2 import add_stuff, add_stats
@lru_cache()
def add(x, y):
return x + y
if __name__ == "__main__":
add(1, 2)
add(1, 2)
add(3, 4)
print(add.cache_info)
print(add.cache_info())
add_stuff(1, 2)
add_stuff(3, 4)
add_stats()
file_2.py
def add_stuff(x, y):
from file_1 import add
add(x, y)
def add_stats():
from file_1 import add
print(add.cache_info)
print(add.cache_info())
输出如下所示:
<built-in method cache_info of functools._lru_cache_wrapper object at 0x017E9E48>
CacheInfo(hits=1, misses=2, maxsize=128, currsize=2)
<built-in method cache_info of functools._lru_cache_wrapper object at 0x017E9D40>
CacheInfo(hits=0, misses=2, maxsize=128, currsize=2)
当我在定义函数的文件中使用函数时,函数对象与另一个文件导入它时不同。这意味着,对于像lru_cache这样的东西,如果你没有意识到这一点,你可能会在进程/线程中填充两个缓存,如果你不将缓存的函数保存在与使用它们的文件不同的文件中。
我的问题是,这是你要小心的蟒蛇陷阱吗?还是有我从未读过的文档更深入地解释了这一点?我查看了lru_cache文档,并没有将其作为需要注意的事项调用。
当我在定义它的文件中使用函数时,函数对象与另一个文件导入它时不同。
是的;这里有两个独立的缓存,因为每个缓存都在修饰一个单独的函数对象。有两个单独的函数对象的原因是有两个单独的模块从相同的源代码创建。
其中一个模块是由from file_1 import add
创建的,这导致一个模块在sys.modules
中缓存,其密钥为'file_1'
,__name__
属性为file_1
。(后续使用import
将在缓存中查找此模块)。
另一个是通过运行file_1.py
作为主脚本创建的。这会导致创建一个__name__
属性为__main__
的模块。
if __name__ == '__main__':
技巧有效。模块可用的全局变量——也就是通过使用globals()
获得的变量——来自模块对象的属性。。顶层脚本也用模块对象表示。-它们只是不使用import
导入(尽管它们是)使用大部分相同的机制创建,并缓存;'__main__'
键将出现在sys.modules
中)。这就是信息的来源,也是为什么__name__
在正常情况下作为全局变量存在的原因。
这是一个需要注意的蟒蛇陷阱吗?还是有我从未读过的文档更深入地解释了这一点?我查看了lru_cache文档,并没有将其作为需要注意的事项调用。
在lru_cache
文档中没有解释,因为这不是lru_cache
的错误。任何装饰师都会这样。事实上,任何使函数对象的独立标识相关的代码都会发生这种情况。例如,如果我们创建module_example.py
:
def example():
print(example.module)
example.module = __name__
if __name__ == '__main__':
example()
(再次出现的__name__
应该可以很明显地看出发生了什么——当然,我们甚至可以直接在函数中使用__name__
)
现在我们在交互模式下测试代码——运行它,使用全局函数,然后导入模块并使用导入的函数:
$ python -i module_example.py
__main__
>>> example()
__main__
>>> import module_example
>>> module_example.example()
module_example
>>> quit()
$
在期望模块既可以作为顶级代码又可以作为可导入的东西时,这只是一个问题,强加了一些设计考虑。通常,如果要导入代码,则"驱动程序";代码块(如果有的话)只会做一个非正式的测试;或者为模块的功能提供一个简单的一次性UI,不关心与相同代码的导入模块版本的一致性。
换句话说:这里真正的问题是循环导入。file_1
是间接导入自身来获得自身的功能,它只"工作";因为第一次将模块隐式重命名为__main__
。