如果我在运行时修改Python脚本会发生什么?
想象一下,有一个Python脚本需要很长时间才能完成。如果我在它运行的时候去修改这个脚本,会发生什么呢?结果会不会不一样?
9 个回答
这是个有趣的问题。答案是“这要看情况”。
考虑以下代码:
"Example script showing bad things you can do with python."
import os
print('this is a good script')
with open(__file__, 'w') as fdesc:
fdesc.write('print("this is a bad script")')
import bad
试着把上面的代码保存为“/tmp/bad.py”,然后进入“/tmp”目录,最后运行“python3 bad.py”,看看会发生什么。
在我的Ubuntu 20系统上,我看到的输出是:
this is a good script
this is a bad script
所以,再次强调,你的问题的答案是“这要看情况”。如果你没有做什么奇怪的事情,那么脚本就在内存中,你就没问题。但Python是一种非常灵活的语言,所以有很多方法可以修改你的“脚本”,并影响输出。
如果你不想做什么奇怪的事情,那么需要注意的一点是函数内部的导入。
下面是另一个例子,说明这个概念(保存为“/tmp/modify.py”,然后进入“/tmp”目录,再运行“python3 modify.py”)。下面定义的fiddle
函数模拟了你在脚本运行时修改它的情况(如果你愿意,可以去掉fiddle函数,在倒数第二行加上time.sleep(300)
,然后自己修改文件)。
关键是,因为show
函数在函数内部进行导入,而不是在模块的顶部,所以导入不会发生,直到调用这个函数。如果你在调用show
之前修改了脚本,那么你修改后的版本就会被使用。
如果你在修改正在运行的脚本时看到意外或奇怪的行为,我建议你检查一下函数内部的导入语句。有时候这样做是有道理的,所以你会在一些人的代码和某些库中看到这种情况。
下面是一个演示,说明函数内部的导入如何导致奇怪的效果。你可以尝试直接运行这个代码,或者注释掉对fiddle
函数的调用,看看在脚本运行时修改它的效果。
"Example showing import in a function"
import time
def yell(msg):
"Yell a msg"
return f'#{msg}#'
def show(msg):
"Print a message nicely"
import modify
print(modify.yell(msg))
def fiddle():
orig = open(__file__).read()
with open(__file__, 'w') as fdesc:
modified = orig.replace('{' + 'msg' + '}', '{msg.upper()}')
fdesc.write(modified)
fiddle()
show('What do you think?')
当你运行一个Python程序并启动解释器时,首先会发生以下几件事:
- 模块
sys
和builtins
会被初始化 - 初始化
__main__
模块,也就是你传给解释器的文件;这会导致你的代码开始执行
当一个模块被初始化时,它的代码会被运行,这个过程会定义类、变量和函数。你的模块(也就是主文件)的第一步通常是导入其他模块,这些模块也会以同样的方式被初始化;它们的命名空间会被提供给你的模块使用。导入的结果部分是一个在内存中的模块(Python)对象。这个对象确实有一些字段指向 .py 和 .pyc 的内容,但这些内容不会再被评估:模块对象是被缓存的,它们的源代码不会被运行两次。因此,在磁盘上修改模块后对执行没有影响。不过,当源代码被读取用于检查目的时,比如抛出异常时,或者通过模块 inspect 时,可能会有影响。
这就是为什么在添加不希望在模块导入时运行的代码时,需要检查 if __name__ == "__main__"
。将文件作为主程序运行相当于该文件被导入,唯一的区别是 __name__
的值不同。
来源:
- 模块导入时发生了什么: 导入系统
- 解释器启动时发生了什么: 顶层组件
__main__
模块是什么:__main__
- 顶层代码环境
没什么特别的,因为Python会把你的脚本预先编译成一个PYC文件,然后运行这个文件。
不过,如果发生了某种错误,你可能会得到一个有点误导的解释,因为X行的代码可能和你开始运行脚本之前的代码不一样。