同样的代码不同地方调用为什么结果会不一致?
包结构:.
├── test_folder
test_folder
├── test_demo
├── test_lib
├── test_run.py
test_demo
├── demo.py
test_lib
├── common.py
test_run的代码如下:
import test_folder.test_demo.demo as demo
demo.test()
demo.py的代码如下:
import test_folder.test_lib.common as common
l1 = {'a': 1}
@common.test_auth
def test():
print('这是函数内的l1:', l1)
print('这是函数内的l1的内存地址:', id(l1['a']))
return l1
if __name__ == '__main__':
res = test()
print(res)
common.py的代码如下:
def test_auth(func):
import test_folder.test_demo.demo as demo
def inner(*args, **kwargs):
if demo.l1['a'] == 1:
demo.l1['a'] = 2
print('这是装饰器内的l1的值:', demo.l1['a'])
print('这是装饰器内的l1:', demo.l1)
print('这是装饰器内的l1的内存地址:', id(demo.l1['a']))
res = func(*args, **kwargs)
return res
else:
res = func(*args, **kwargs)
return res
return inner
直接运行test_run.py的结果如下:
这是装饰器内的l1的值: 2
这是装饰器内的l1: {'a': 2}
这是装饰器内的l1的内存地址: 140730700886304
这是函数内的l1: {'a': 2}
这是函数内的l1的内存地址: 140730700886304
--------装饰器内的l1的值和函数内展示的l1的值一致
直接运行demo.py的结果如下:
这是装饰器内的l1的值: 2
这是装饰器内的l1: {'a': 2}
这是装饰器内的l1的内存地址: 140730700886304
这是函数内的l1: {'a': 1}
这是函数内的l1的内存地址: 140730700886272
{'a': 1}
--------装饰器内的l1的值和函数内展示的l1的值不一致
为什么运行test_run.py的结果和直接运行demo.py跑出来的结果不一致?求各位大佬讲解一下
问题解析:
根据提供的代码,运行test_run.py脚本和直接运行demo.py脚本,会得到不同的结果。原因在于函数test_run.py中导入了装饰器函数test_auth,而直接运行demo.py时并没有导入该装饰器函数。
问题原因:
在Python中,模块的导入是一次性的。当一个模块第一次被导入后,它会被缓存起来,再次导入该模块时,Python会直接使用缓存中的对象,而不会再次执行模块的代码。
在demo.py中,使用了装饰器@common.test_auth修饰函数test()。而装饰器是在函数定义阶段就会被调用的,即在demo.py被导入时就会执行装饰器函数。因此,当直接运行demo.py时,装饰器会修改common.py中的demo.l1['a']的值,导致函数test()内的l1字典发生了变化。
但是,当运行test_run.py时,demo.py是通过模块导入的方式被引入的,此时demo.py中的代码只会在第一次导入时执行一次,并将其缓存。因此,test_run.py中导入的demo.py使用的是缓存中的demo.py模块,而不会再次执行其中的代码。所以在test_run.py中,demo.py的装饰器函数没有被调用,导致common.py中的demo.l1['a']的值没有被修改,从而导致结果不一致。
解决方法:
为了保证结果的一致性,可以考虑将装饰器函数的导入放在函数调用前面,即将import test_folder.test_lib.common as common语句放在test()函数之前。
修改后的demo.py代码如下:
import test_folder.test_lib.common as common
@common.test_auth
def test():
print('这是函数内的l1:', l1)
print('这是函数内的l1的内存地址:', id(l1['a']))
return l1
l1 = {'a': 1}
if __name__ == '__main__':
res = test()
print(res)
这样修改后,无论通过直接运行demo.py还是通过导入方式运行test_run.py,都会得到一致的结果。
希望对你有所帮助!如果还有其他问题,请随时提问。
球一个最佳答案谢谢啦!这对我非常重要!{:10_254:}{:10_254:}{:10_254:}
这个问题的关键在于Python的模块加载机制。当你在Python中导入一个模块时,Python会运行该模块的所有代码,并缓存结果。如果你再次导入同一个模块,Python会使用缓存的模块,而不会再次运行模块的代码。这种行为可以避免不必要的计算,但有时可能会导致混淆,特别是当模块的状态(即全局变量)在两个导入之间发生变化时。
在你的情况下,当你运行test_run.py时,它首先导入了demo模块,然后调用了test()函数。在调用过程中,test_auth装饰器更改了demo模块的全局变量l1。由于l1是全局变量,所以这个变化会影响到整个模块。
然后,当你直接运行demo.py时,你又一次导入了demo模块。然而,由于demo模块已经被Python缓存,因此Python并没有再次运行demo模块的代码,l1的值还是被装饰器更改后的值。
然而,当你直接运行demo.py时,Python没有任何先前的缓存可以使用,因此它会运行demo模块的所有代码,包括设置l1的初始值。因此,在这种情况下,l1的值是它在demo模块内部被初始化时的值,不会受到装饰器的影响。
所以,在这两种情况下,你看到的结果是不一样的。
希望这个解释能帮到你!如果你有其他问题,请随时提问。 本帖最后由 weedstar 于 2023-7-21 16:53 编辑
isdkz 发表于 2023-7-21 16:01
这个问题的关键在于Python的模块加载机制。当你在Python中导入一个模块时,Python会运行该模块的所有代码, ...
“当你直接运行demo.py时,Python没有任何先前的缓存可以使用,因此它会运行demo模块的所有代码,包括设置l1的初始值。因此,在这种情况下,l1的值是它在demo模块内部被初始化时的值,不会受到装饰器的影响。
”------这个不对吧,单独运行demo.py的时候,装饰器应该还是要运行才对吧{:5_99:} 陶远航 发表于 2023-7-21 16:00
在Python中,模块被导入的时候,会运行其中的所有顶层代码。在Python的默认行为中,模块只被导入一次,再次 ...
“当你直接运行demo.py时,全局变量l1初始化为{'a': 1},然后调用test()函数被装饰器test_auth调用,改变了l1的值为{'a': 2}。
然后在装饰器内部,你再次导入了一次demo模块。由于这次导入在一次新的Python进程中,所以demo模块的代码被再次执行,然后在demo模块中的全局变量l1又被初始化为{'a': 1}。因此,你在装饰器中看到的是{'a': 2},而在函数内部看到的却是{'a': 1}。
”-----这个地方不明白的点是,单独执行demo.py,调用装饰器,在装饰器内部在修改l1的值前应该就调用了demo模块,装饰器后续demo下的test代码查找得到的是初始化的l1而不是装饰器修改后的l1值呢? weedstar 发表于 2023-7-21 16:52
“当你直接运行demo.py时,全局变量l1初始化为{'a': 1},然后调用test()函数被装饰器test_auth调用,改变 ...
这个问题的关键在于Python在处理这种情况时的导入机制和名称空间。当Python加载一个模块时,它首先检查sys.modules(这是一个字典,键是之前导入的模块名,值是这些模块对象)来看是否已经加载过这个模块了。如果已经加载过了,Python就会使用sys.modules中的版本,而不会重新加载。
在你的例子中,当你直接运行demo.py时,它首先创建一个全局变量l1并初始化为{'a':1}。然后你的函数test()被test_auth装饰器装饰并调用。在装饰器内部,修改了l1的值为{'a':2}。然后,装饰器内部再次导入了一次demo模块,但是由于Python的这种导入机制,此时demo模块已经在sys.modules中了,所以并不会重新加载demo模块,也不会重新执行demo.py中的代码,全局变量l1不会被重置为初始值{'a':1}。结果就是,在装饰器内部和函数内部看到的l1的值都是{'a':2}。
而在执行test_run.py时,情况就不同了。首先导入并执行demo.py,创建一个全局变量l1并初始化为{'a':1}。然后函数test()被test_auth装饰器装饰并调用。在装饰器内部,修改了l1的值为{'a':2}。然后,装饰器内部再次导入了一次demo模块,但此时由于执行环境已经改变了(是在test_run.py中),于是按照Python的导入机制,又重新加载了一次demo模块,demo.py中的代码被重新执行,全局变量l1被重置为了{'a':1}。因此,在装饰器内部看到的l1的值是{'a':2},而在函数内部看到的值变成了{'a':1}。
对于这个问题,通常的解决方案是避免在全局范围内修改变量,或者尽量避免在模块代码中直接执行动作,而是将它们放在函数或类中。 Mike_python小 发表于 2023-7-21 16:01
问题解析:
根据提供的代码,运行test_run.py脚本和直接运行demo.py脚本,会得到不同的结果。原因在于函数t ...
代码的执行过程是不是写错了?demo.py单独执行时,函数test()返回的结果和装饰器的结果是不一致的,而单独运行test_run.py时,函数test()返回的结果和装饰器的结果是一致的,这时候的装饰器函数应该是被调用才会触发l1['a']的值的修改吧 陶远航 发表于 2023-7-21 17:19
这个问题的关键在于Python在处理这种情况时的导入机制和名称空间。当Python加载一个模块时,它首先检查sy ...
老哥有看我后面执行结果吗?和你分析的结果完全相反啊!!!
直接运行test_run.py的结果如下:
这是装饰器内的l1的值: 2
这是装饰器内的l1: {'a': 2}
这是装饰器内的l1的内存地址: 140730700886304
这是函数内的l1: {'a': 2}
这是函数内的l1的内存地址: 140730700886304
--------装饰器内的l1的值和函数内展示的l1的值一致
直接运行demo.py的结果如下:
这是装饰器内的l1的值: 2
这是装饰器内的l1: {'a': 2}
这是装饰器内的l1的内存地址: 140730700886304
这是函数内的l1: {'a': 1}
这是函数内的l1的内存地址: 140730700886272
{'a': 1}
--------装饰器内的l1的值和函数内展示的l1的值不一致 补充:
修改demo.py的代码如下:
import test_lib.common as common
l1={'a':1}
@common.test_auth
def test1():
print('这是test1函数内的l1:',l1)
print('这是test1函数内的l1的内存地址:',id(l1['a']))
return l1
def test2():
print('这是test2函数内的l1:',l1)
print('这是test2函数内的l1的内存地址:',id(l1['a']))
return l1
if __name__=='__main__':
test1()
test2()
结果:
直接运行demo.py,无论有无装饰器,test1()和test2()都是直接查找的demo.py下的全局变量l1的值
这是装饰器内的l1的值: 2
这是装饰器内的l1: {'a': 2}
这是装饰器内的l1的内存地址: 140730762096928
这是test1函数内的l1: {'a': 1}
这是test1函数内的l1的内存地址: 140730762096896
这是test2函数内的l1: {'a': 1}
这是test2函数内的l1的内存地址: 140730762096896
页:
[1]