如果我在Python脚本运行时进行修改,会发生什么?

364
想象一下,如果一个Python脚本需要很长时间才能运行完,那么如果我在它运行的过程中进行修改,会发生什么呢?结果会有所不同吗?

20
程序被加载到主存储器中。如果您更改源文件,将不会发生任何事情。想象一下,如果CPU从硬盘读取指令...... - Felix Kling
23
@Felix:这被称为“Execute-in-Place”(XIP)。 - Ignacio Vazquez-Abrams
3
您可以动态重新加载模块的代码,请参阅https://dev59.com/knRC5IYBdhLWcg3wAcXU。 - Iliyan Bobev
8
请注意,Windows批处理文件确实在原地执行,因此这不是一个假设性的问题,存在一些以此方式运行的编程语言。 - yoyo
1
正如@yoyo所述,Windows批处理文件确实在原地执行。在运行时修改批处理文件会导致各种错误,即使你只是添加注释也是如此。这是一个非常合理的问题。 - JG1212
显示剩余2条评论
9个回答

431

没有什么变化,因为Python会将您的脚本预编译成PYC文件并启动它。

但是,如果发生某种异常,您可能会得到一个稍微误导的解释,因为在您启动脚本之前,行X的代码可能已经发生了变化。


16
不一定要保存成文件。 - Ignacio Vazquez-Abrams
76
如果在程序运行时重新启动,并且新的pyc文件覆盖了旧文件,那么会不会导致已经在运行的程序出现问题? - deceleratedcaviar
35
没有任何事情发生。我还在一个小测试中进行了检查。所发生的事情是:pyc只是编译后的代码。这个编译后的代码会被加载到内存中并执行。因此,始终可以更改程序,重新编译并在不同的控制台中运行另一个实例。 - Chris
12
@Chris 我在一个控制台中启动了我的python脚本实例。在脚本运行时,我在另一个控制台中更改了两行代码并启动了另一个实例。过了一会儿,第一个控制台返回了一个关于我修改后启动的那两行代码的错误信息!请帮忙解决。 - double_j
23
@Chris 我认为我知道这里发生了什么。如果你在脚本运行时修改并保存它,而先前的版本出现错误,在回溯读取中,它会打开当前版本的文件,并使回溯看起来与开始时不同。我自己也多次见过这种情况。 - double_j
显示剩余12条评论

23
当你运行一个Python程序并启动解释器时,首先发生的事情如下:
- 模块sysbuiltins被初始化。 - 初始化__main__模块,这是你作为参数给解释器的文件;这将导致你的代码被执行。
当一个模块被初始化时,它的代码会被运行,定义类、变量和函数等。你的模块(即主文件)的第一步可能是导入其他模块,这些模块也会以同样的方式被初始化;它们的命名空间随后可供你的模块使用。导入过程的结果部分是Python内存中的模块对象。该对象确实具有指向.py和.pyc文件的字段内容,但这些内容不再被评估:模块对象被缓存,它们的源代码永远不会再次运行。因此,对磁盘上的模块进行修改不会影响执行。它只有在读取源代码进行内省目的时才会产生影响,例如抛出异常或通过模块inspect
这就是为什么添加不打算在模块导入时运行的代码时需要检查if __name__ == "__main__"。将文件作为主文件运行等效于导入该文件,除了__name__具有不同的值。

来源:


18

这是一个有趣的问题。答案是“取决于情况”。

考虑以下代码:

"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”,然后执行“cd /tmp”,最后执行“python3 bad.py”并查看结果。

在我的Ubuntu 20系统上,我看到输出:

this is a good script
this is a bad script

所以,你问题的答案是“这取决于情况”。如果你没有做任何奇怪的事情,那么脚本就在内存中,一切都好。但是Python是一种非常动态的语言,因此有许多方法可以修改你的“脚本”并影响输出结果。

如果你不想做任何奇怪的事情,那么需要注意的一点是函数内部的导入。

以下是另一个示例,说明了这个想法(将其保存为“/tmp/modify.py”,然后执行“cd /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?')

所有其他答案似乎假设(没有明确说明)在修改发生之前,所有运行的内容都已经被导入。虽然在许多(大多数?)情况下这是正确的,但并不总是如此,而且无论如何都应该明确说明。 - Gerhard

1

就像这个答案所说的一样,没有什么问题。此外,当涉及到multiprocessing时,我做了实验。请将以下脚本保存为x.py

import multiprocessing
import time

def f(x):
    print(x)
    time.sleep(10)

if __name__ == '__main__':
    with multiprocessing.Pool(2) as pool:
        for _ in pool.imap(f, ['hello'] * 5):
            pass

在执行了python3 x.py命令后,前两个“hello”被打印出来后,我将['hello']修改为['world']并观察发生了什么。没有发生有趣的事情。结果仍然是:

hello
hello
hello
hello
hello

1
不,一旦保存更改,结果将不会反映出这些更改。当运行常规的Python文件时,结果也不会改变。您需要保存更改并重新运行程序。

1
如果您运行以下脚本:
from time import sleep

print("Printing hello world in: ")
for i in range(10, 0, -1):
    print(f"{i}...")
    sleep(1)
    
print("Hello World!")

当倒计时时将“Hello World!”更改为“Hello StackOverflow!”,它仍然会输出“Hello World”。


1
目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community
1
不好意思?这个有什么不清楚或者值得被踩的吗?它提供了一个“需要很长时间运行的Python脚本”的例子,并解释了会发生什么。 - ITR

-2

什么也不会发生。一旦脚本被加载到内存中并运行,它将保持这样。

“自动重新加载”功能可以在您的代码中实现,就像Flask和其他框架一样。


-3

这与您在问题中描述的略有不同,但它可以工作:

my_string = "Hello World!"

line = input(">>> ")
exec(line)

print(my_string)

测试运行:

>>> print("Hey")
Hey
Hello World!

>>> my_string = "Goodbye, World"
Goodbye, World

你可以动态地改变你的“已加载”代码的行为。


-5

如果一个Python脚本链接到其他修改过的文件,那么它将加载更新的版本。但是,如果源代码没有指向任何其他文件,只要运行了脚本,它就会从缓存中运行所有脚本。更改将在下一次可见...

如果关于在进行更改时自动应用更改-是的,@pcbacterio是正确的。可以做到这一点,但是执行此操作的脚本只会记住上次操作/正在进行的事情,并在文件被修改时检查以重新运行它(因此几乎不可见)

=]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接