Python 能否识别正在交互运行的文件的更改?

jsl*_*ane 5 python interactive

我正在做一些故障排除,我很好奇是否可以交互式运行 python 脚本,更改脚本中定义的函数,保存文件,然后让交互式 shell 识别更改。这是我目前正在做的一个例子:

my_script.py:

def dummy_func():
    print('Something')
def main():
    dummy_func()
if __name__ == '__main__':
    main()
Run Code Online (Sandbox Code Playgroud)

我去我的终端并运行:

>python -i my_script.py
Something
>>>
Run Code Online (Sandbox Code Playgroud)

如果我在编辑器中返回 my_script.py 并进行以下更改:

def dummy_func():
    print('Something else')
Run Code Online (Sandbox Code Playgroud)

然后返回终端(仍处于打开状态)并重新运行更新后的函数:

>>>dummy_func()
Something
>>>
Run Code Online (Sandbox Code Playgroud)

是否有可能做一些事情来代替获得以下行为?:

>>>dummy_func()
Something else
>>>
Run Code Online (Sandbox Code Playgroud)

我知道可以使用 importlib 和 reload 重新加载模块,但据我所知,这不适用于这里,因为我没有导入任何东西。我对 stackoverflow 还很陌生,所以如果我需要提供更多详细信息或以不同的方式提问,请告诉我。谢谢。

编辑; 我认为这可能与如何卸载(重新加载)Python 模块不同?.
我在问是否有办法重新加载您正在通过 python shell 交互运行的当前文件,而那个问题是关于重新加载您已导入另一个 python 脚本的模块。我编辑了我的答案以包含对这个问题的引用。

jsl*_*ane 7

据我所知,简短的回答是:
不,一旦文件被解析、分析并输入解释器,Python 解释器通常不会识别对文件的更改。

显然,您应该做的是将 .py 文件用作模块,将其作为模块导入另一个 .py 文件,然后运行该新文件。这允许您通过交互式解释器重新加载您的第一个文件。这是一个例子

from importlib import reload  # Python 3.4+ only.
import foo

while True:
    # Do some things.
    if is_changed(foo):
        foo = reload(foo)
Run Code Online (Sandbox Code Playgroud)

我对细节仍然有点模糊,但也许有人可以帮助填写。据我从下面链接的来源中可以看出,解释器基本上采取了一些步骤将您的程序从保存的 python 文件加载到内存中(掩盖了很多细节)。一旦执行了这个过程,解释器就不会再次执行它,除非您明确要求它这样做,例如通过使用 importlib 的 reload() 函数再次执行该过程。

资料来源:

如何卸载(重新加载)Python 模块?(以上引述)

用 Python 编写的 Python 解释器
此链接提供了有关解释器如何工作的更多信息,我发现本节特别有用:

真正的 Python 字节码
在这一点上,我们将放弃我们的玩具指令集并切换到真正的 Python 字节码。字节码的结构类似于我们玩具解释器的冗长指令集,除了它使用一个字节而不是一个长名称来标识每条指令。为了理解这个结构,我们将遍历一个简短函数的字节码。考虑下面的例子:

>>> def cond():  
...     x = 3  
...     if x < 5:  
...         return 'yes'  
...     else:  
...         return 'no'  
...  
Run Code Online (Sandbox Code Playgroud)

Python 在运行时公开了大量内部结构,我们可以直接从 REPL 访问它们。对于函数对象 cond,cond。code是与其关联的代码对象,并且 cond. code .co_code 是字节码。在编写 Python 代码时,几乎没有充分的理由直接使用这些属性,但它们确实允许我们处理各种恶作剧——并查看内部结构以理解它们。

>>> cond.__code__.co_code  # the bytecode as raw bytes  
 b'd\x01\x00}\x00\x00|\x00\x00d\x02\x00k\x00\x00r\x16\x00d\x03\x00Sd\x04\x00Sd\x00\x00S'
>>> list(cond.__code__.co_code)  # the bytecode as numbers  
[100, 1, 0, 125, 0, 0, 124, 0, 0, 100, 2, 0, 107, 0, 0, 114, 22, 0, 100, 3, 0, 83,
100, 4, 0, 83, 100, 0, 0, 83]  
Run Code Online (Sandbox Code Playgroud)

当我们只打印字节码时,它看起来难以理解——我们只能说它是一系列字节。幸运的是,我们可以使用一个强大的工具来理解它:Python 标准库中的 dis 模块。

dis 是一个字节码反汇编器。反汇编器采用为机器编写的低级代码,如汇编代码或字节码,并以人类可读的方式打印出来。当我们运行 dis.dis 时,它会输出它所传递的字节码的解释。

>>> dis.dis(cond)   
  2           0 LOAD_CONST               1 (3)
              3 STORE_FAST               0 (x)
  3           6 LOAD_FAST                0 (x)
              9 LOAD_CONST               2 (5)
             12 COMPARE_OP               0 (<)
             15 POP_JUMP_IF_FALSE       22

  4          18 LOAD_CONST               3 ('yes')
             21 RETURN_VALUE

  6     >>   22 LOAD_CONST               4 ('no')
             25 RETURN_VALUE
             26 LOAD_CONST               0 (None)
             29 RETURN_VALUE  
Run Code Online (Sandbox Code Playgroud)

这是什么意思呢?我们以第一条指令 LOAD_CONST 为例。第一列 (2) 中的数字显示了 Python 源代码中的行号。第二列是字节码的索引,告诉我们 LOAD_CONST 指令出现在位置零。第三列是指令本身,映射到其人类可读的名称。第四列(如果存在)是该指令的参数。第五列(如果存在)是有关参数含义的提示。

Python 运行时实际上是如何工作的?

对于 Python,它使用解释器而不是编译器。解释器的工作方式与编译器完全相同,但有一个区别:它不是生成代码,而是将输出加载到内存中并直接在您的系统上执行。(这种情况如何发生的确切细节在不同语言和不同解释器之间可能会有很大差异。)

importlib — import 的实现

执行 reload() 时:

Python 模块的代码被重新编译并重新执行模块级代码,通过重用最初加载模块的加载器来定义一组新的对象,这些对象绑定到模块字典中的名称。扩展模块的 init 函数不会被第二次调用。

同样,如果我需要编辑此答案以遵循礼节,请告诉我。

  • 您的回答绝对符合礼仪!链接到正确的文档,在链接失效的情况下引用答案中的重要部分,总结重要部分。A+回答你自己的问题。 (2认同)