为什么在文件中使用全局变量与导入全局变量时创建不同的对象?



下面是一个简单的代码示例,可能有助于解释我的问题。

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__

相关内容

  • 没有找到相关文章

最新更新