跳转到主要内容

实时更新Python函数

项目描述

jurigged

Jurigged 允许您在运行时更新代码。使用它很简单

  1. jurigged your_script.py
  2. 使用您喜欢的编辑器更改某些函数或方法并保存文件
  3. Jurigged 会将新函数热补丁到正在运行的脚本中

Jurigged 智能地实时更新代码:更改函数或方法将更改代码指针,以便所有现有实例同时修改以实现新行为。当修改模块时,只有更改的行将被重新运行。

demo

您还可以选择安装基于终端的实时开发环境 develoop

develoop2

如上所示,jurigged --loop <function_name> script.py 将在脚本的特定函数上“循环”。每次源代码更改时,该函数都会重新运行,并将更改热补丁到正在运行的过程中。程序的其他部分 不会 重新运行,因此预处理将得到保留,并且不需要重新加载重模块!

安装

Jurigged 需要 Python 版本 >= 3.8。

pip install jurigged

要安装develoop功能,它允许您交互式地开发函数

pip install jurigged[develoop]

命令行

使用 jurigged 最简单的方法是在脚本调用中添加 -m jurigged,或使用 jurigged 而不是 python。您可以使用 -v 获取有关监视哪些文件以及更改文件时会发生什么的反馈。

python -m jurigged -v script.py

OR

jurigged -v script.py

如果没有提供参数,它将启动一个实时REPL

python -m jurigged

OR

jurigged

完整帮助

usage: jurigged [-h] [--interactive] [--watch PATH] [--debounce DEBOUNCE] [--poll POLL] [-m MODULE] [--dev] [--verbose] [--version]
                [SCRIPT] ...

Run a Python script so that it is live-editable.

positional arguments:
  SCRIPT                Path to the script to run
  ...                   Script arguments

optional arguments:
  -h, --help            show this help message and exit
  --interactive, -i     Run an interactive session after the program ends
  --watch PATH, -w PATH
                        Wildcard path/directory for which files to watch
  --debounce DEBOUNCE, -d DEBOUNCE
                        Interval to wait for to refresh a modified file, in seconds
  --poll POLL           Poll for changes using the given interval
  -m MODULE             Module or module:function to run
  --dev                 Inject jurigged.loop.__ in builtins
  --verbose, -v         Show watched files and changes as they happen
  --version             Print version

Develoop

用法

# Loop over a function
jurigged --loop function_name script.py
jurigged --loop module_name:function_name script.py

# Only stop on exceptions
jurigged --xloop function_name script.py

"develoop" 是 Jurigged 的可选功能,为函数提供一种类似实时开发环境的功能。如果您运行 jurigged --loop <function_name> <script>,脚本中该名称的函数将成为 "develoop" 的一部分。当它被输入时,它将运行,其输出将被捕获并显示,程序将等待输入。如果源代码更改,函数将再次运行。

--xloop-x 标志作用相同,但循环只有在函数引发异常时才会执行。如果没有引发异常,它将像正常一样运行。如果需要遍历多个函数,--loop--xloop 都可以多次使用。

默认接口允许一些命令

  • r 用于手动重新运行循环。这可以在运行过程中进行。
  • a 用于终止当前运行(例如,如果您陷入无限循环)。
  • c 用于退出循环并正常继续程序。
  • q 用于完全退出程序。

与 stdin 一起使用

默认的 develoop 接口与 stdin 不太兼容。如果您想从 stdin 读取或设置 breakpoint(),请使用装饰器 @__.loop(interface="basic")。接口会更原始,但 stdin/pdb 将会工作。

故障排除

首先,如果有问题,使用详细模式标志(jurigged -v)来获取更多信息。它将为它监视的每个文件输出一个 Watch <file> 语句,并在您在原始文件中更新、添加或删除函数并保存时输出 Update/Add/Delete <function> 语句。

文件没有被监视。

默认情况下,脚本在当前工作目录中被监视。尝试 jurigged -w <file> 来监视特定文件,或 jurigged -w / 来监视所有文件。

文件被监视,但当我更改函数时没有发生任何事情。

您可以尝试使用 --poll <INTERVAL> 标志来使用轮询而不是操作系统的原生机制。如果不起作用,尝试使用不同的编辑器查看是否可行:这可能与编辑器保存的方式有关。例如,一些编辑器如 vi 会将数据保存到一个临时交换文件中,然后将其移动到正确位置,这曾经导致问题(从 v0.3.5 开始应该已修复)。

Jurigged 表示已更新函数,但它仍在运行旧代码。

如果您正在编辑当前正在运行的功能内的 for 循环的主体,更改将只在下次调用该函数时生效。一种解决方案是将 for 循环的主体提取为其自己的辅助函数,然后您可以对其进行编辑。或者,您可以使用 reloading 与 Jurigged 一起使用。

同样,更新生成器或异步函数不会更改已运行的生成器或异步函数的行为。

我可以更新一些函数,但不能更新其他函数。

在将函数装饰或将其存储在 Jurigged 不理解的数据结构中时,更新某些函数可能会出现问题。不幸的是,Jurigged 必须找到它们才能更新它们。

API

您可以使用 jurigged.watch() 以编程方式启动监视更改。这也应该在 IPython 或 Jupyter 中作为 %autoreload 魔法的替代方案使用。

import jurigged
jurigged.watch()

默认情况下,当前目录中的所有文件都将被监视,但您可以使用 jurigged.watch("script.py") 仅监视单个文件,或 jurigged.watch("/") 来监视所有模块。

Recoders

函数可以使用 Recoder 进行编程更改。使用 jurigged.make_recoder 创建一个。这可以用于实现热修补或模拟。更改还可以写回文件系统。

from jurigged import make_recoder

def f(x):
    return x * x

assert f(2) == 4

# Change the behavior of the function, but not in the original file
recoder = make_recoder(f)
recoder.patch("def f(x): return x * x * x")
assert f(2) == 8

# Revert changes
recoder.revert()
assert f(2) == 4

# OR: write the patch to the original file itself
recoder.commit()

revert 只会回滚到最后一个 commit,或者如果没有提交,则回滚到原始内容。

Recoder 还允许您将导入、辅助函数等添加到补丁中,但您必须在这种情况下使用 recoder.patch_module(...)

注意事项

Jurigged 在许多情况下都能正常工作,但也有一些情况它无法工作或可能出现问题。

  • 已运行的函数将继续使用现有代码运行。只有下一次调用才会使用新代码。
    • 当使用断点进行调试时,当前位于堆栈中的函数不能更改。
    • 正在运行的生成器或异步函数不会更改。
    • 如果您想修改正在运行的 for 循环,除了 Jurigged,您还可以使用 reloading
  • 更改初始化器或属性名称可能会导致现有实例出现错误。
    • Jurigged 会修改一个类的所有现有实例,但它不会重新运行 __init__ 或更改现有实例的属性名称,因此您可能会轻易地得到损坏的对象(新方法,但旧数据)。
  • 更新装饰器或闭包的代码可能有效也可能无效。 Jurigged 会尽力而为,但有些闭包可能会更新,而有些则不会。
  • 查看/调整函数代码的装饰器可能无法正确更新。
    • 尝试编译/JIT Python 代码的包装器将不知道 Jurigged,并且无法为新代码重新执行其工作。
    • 它们可以使用 __conform__ 方法(见下文)使其工作。

自定义行为

为了更新 Python 函数的转换,例如根据原始源代码生成新代码对象的转换,您需要做类似以下操作

class Custom:
    __slots__ = ("code",)

    def __init__(self, transformed_fn, code):
        self.code = code
        self.transformed_fn = transformed_fn

    def __conform__(self, new_code):
        if new_code is None:
            # Function is being deleted
            ...

        if isinstance(new_code, types.FunctionType):
            new_code = new_code.__code__

        do_something(new_code)
        self.code = new_code

...
transformed_fn.somefield = Custom(transformed_fn, orig_fn.__code__)

基本上,当原始代码更改时,Jurigged 将使用 gc 模块来查找指向它的对象,并且如果它们有 __conform__ 方法,它将使用新代码调用它,以便更新的转换函数。原始代码必须位于该对象的槽上(它必须在 __slots__ 中,否则引用者是字典)。可能存在多个转换函数。

它是如何工作的

简而言之,Jurigged 的工作方式如下

  1. 列出现有模块和函数:a. 插入一个导入钩子,收集和监视源文件。b. 使用 gc.get_objects() 查看所有现有函数。c. 添加一个审计钩子,监视对 exec 的调用,以便列出任何新函数。
  2. 将源文件解析为一系列定义。
  3. 当文件被修改时,将其重新解析为一系列定义,并与原始定义匹配,得到一系列更改、添加和删除。
  4. 当函数的代码更改时:a. 移除装饰器 b. 执行新代码 c. 使用 gc.get_referrers() 查找所有使用旧代码的函数 d. 替换它们的内部 __code__ 指针
  5. 如果替换失败或添加了全新代码,则在模块命名空间中执行新代码。

比较

我能找到的与 Jurigged 功能集最相似的两个实现(但找到所有可比的东西可能有些困难)是 IPython 中的 %autoreloadlimeade。以下是主要区别

  • 它们都会在代码更改时重新执行整个模块。相比之下,Jurigged 会从解析树中外科地提取更改的函数,并且仅替换这些函数。它只会在模块中执行新或更改的语句。

    哪种更好取决于具体情况:一方面,重新执行模块将捕获更多更改。另一方面,它将重新初始化模块变量和状态,因此某些东西可能会损坏。Jurigged 的方法更为谨慎,只会关注修改过的函数,但不会触及其他内容,因此我相信它不太可能产生意外的副作用。它还会告诉你它在做什么 :)

  • 它们将重新执行装饰器,而Jurigged将深入挖掘并更新其内部找到的函数。

    再次强调,没有客观上更好的方法。 %autoreload 将正确地重新执行已更改的装饰器,但这些装饰器将返回新的对象,因此如果模块导入了已装饰的函数,它不会更新到新版本。然而,如果您只修改了函数的代码而没有修改装饰器,Jurigged通常能够在装饰器内部修改它,因此所有旧实例都将使用新的行为。

  • 它们依赖于同步点,而Jurigged可以在自己的线程中运行。

    这是一把双刃剑,因为尽管Jurigged可以以零行额外代码向现有脚本添加实时更新,但它根本不是线程安全的(代码可能在更新过程中执行,这可能是不一致的状态)。

其他类似尝试

  • reloading 可以包裹迭代器以使其可修改的循环。Jurigged不能做到这一点,但您可以同时使用这两个包。

项目详细信息


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于 安装包 的信息。

源分布

jurigged-0.6.0.tar.gz (31.1 MB 查看散列)

上传时间

构建分布

jurigged-0.6.0-py3-none-any.whl (37.4 kB 查看散列)

上传时间 Python 3

支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误日志 StatusPage StatusPage 状态页面