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

jv4diomz  于 2023-01-08  发布在  Python
关注(0)|答案(1)|浏览(185)

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

    • 文件_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()
    • 文件_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这样的东西,如果你没有意识到这一点,如果你不将缓存的函数保存在与使用它们的文件不同的文件中,你可能会在你的进程/线程中填充两个缓存。
我的问题是,这是一个python让你注意的吗?或者是有文档在哪里,我只是从来没有读过,更深入地解释这一点?我看了lru_cache文档,这是没有调用那里作为任何需要注意的。

mftmpeh8

mftmpeh81#

当我在定义函数的文件中使用函数时,函数对象与另一个文件导入它时的对象不同。
是的,这里有两个独立的缓存,因为每个缓存都修饰了一个独立的函数对象,之所以有两个独立的函数对象,是因为有两个独立的模块是从同一个源代码创建的。
其中一个模块是由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__在正常情况下作为全局变量存在。

这是一个python需要注意的地方吗?或者有文档在哪里,我只是从来没有读过,更深入地解释这一点?我看了lru_cache文档,这是没有调用那里作为任何需要注意的事情。
lru_cache文档中没有解释,因为这不是lru_cache的错,任何装饰器都会发生这种情况,事实上,任何使函数对象的独立标识相关的代码都会发生这种情况,例如,如果我们创建module_example.py

def example():
    print(example.module)
example.module = __name__

if __name__ == '__main__':
    example()

(The __name__的再次出现应该会使正在发生的事情变得显而易见-当然,我们甚至可以直接在函数中使用__name__
现在我们在交互模式下测试代码-运行它,使用全局函数,然后导入模块并使用导入的函数:

$ python -i module_example.py 
__main__
>>> example()
__main__
>>> import module_example
>>> module_example.example()
module_example
>>> quit()
$

这只是一个陷阱,因为期望一个模块既能作为顶层代码工作,又能作为可导入的东西工作,这强加了一些设计考虑。通常,如果代码打算导入,“驱动程序”代码块(如果有的话)只会做一个非正式的测试;或者为模块的功能提供一个简单的一次性UI,它 * 不关心 * 与相同代码的导入模块版本的一致性。
换句话说:这里真实的的问题是循环导入file_1间接地导入自身以获得其自身的功能,并且它仅“工作”,因为第一次将模块隐式重命名为__main__

相关问题