Python函数的实时更新
项目描述
有管辖权的
Jurigged 允许您在代码运行时更新代码。使用它很简单:
jurigged your_script.py
- 使用您喜欢的编辑器更改某些功能或方法并保存文件
- Jurigged 会将新功能热补丁到运行脚本中
Jurigged 智能地更新实时代码:更改函数或方法将伪造代码指针,以便同时修改所有现有实例以实现新行为。修改模块时,只会重新运行更改的行。
您还可以选择安装devloop,这是一个基于终端的实时开发环境:
如上所示,jurigged --loop <function_name> script.py
将在脚本的特定功能上“循环”。每次修改源代码时都会重新运行该功能,并将更改热修补到正在运行的进程中。程序的其余部分不会重新运行,因此保留了预处理并且不必重新加载繁重的模块!
安装
Jurigged 需要 Python 版本 >= 3.8。
pip install jurigged
还要安装 devloop 功能,它可以让您交互式地开发功能:
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
开发循环
用法:
# 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
“开发循环”是 Jurigged 的一个可选功能,它为函数提供了一种实时开发环境。如果您运行jurigged --loop <function_name> <script>
,脚本中该名称的函数将成为“开发循环”的一部分。当输入时,它会运行,它的输出会被捕获并显示,程序会等待输入。如果源代码被更改,该函数将再次运行。
--xloop
or标志的-x
工作方式相同,但只有在函数引发异常时才会执行循环。如果它没有引发异常,它将像往常一样运行。如果要循环多个函数,则两者都--loop
可以--xloop
多次使用。
默认界面允许一些命令:
r
手动重新运行循环。这可以在跑步中间完成。a
中止当前运行(例如,如果您陷入无限循环)。c
退出循环并正常继续程序。q
完全退出该程序。
与标准输入一起使用
默认的 devloop 接口不能很好地与 stdin 配合使用。如果要从标准输入读取或设置 a 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 循环的主体提取到它自己的辅助函数中,然后您可以对其进行编辑。或者,您可以在 Jurigged 旁边使用重新加载。
同样,更新生成器或异步函数不会改变已经运行的生成器或异步函数的行为。
我可以更新一些功能,但不能更新其他功能。
当某些函数被修饰或隐藏在 Jurigged 不理解的某些数据结构中时,更新它们可能会出现问题。不幸的是,Jurigged 确实必须找到它们来更新它们。
API
您可以调用jurigged.watch()
以编程方式开始监视更改。这也应该在 IPython 或 Jupyter 中工作,作为魔术的替代品%autoreload
。
import jurigged
jurigged.watch()
默认情况下,当前目录中的所有文件都将被监视,但您可以使用jurigged.watch("script.py")
仅监视单个文件或jurigged.watch("/")
监视所有模块。
重新编码器
可以使用 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.patch_module(...)
在这种情况下使用。
注意事项
Jurigged 在数量惊人的情况下工作,但在某些情况下它不起作用,或者可能出现问题:
- 已经运行的函数将继续使用现有代码运行。只有下一次调用将使用新代码。
- 使用断点进行调试时,当前位于堆栈上的函数无法更改。
- 正在运行的生成器或异步函数不会改变。
- 如果您希望能够修改正在运行的 for 循环,则可以使用除 Jurigged 之外的重新加载。
- 更改初始化程序或属性名称可能会导致现有实例出错。
- Jurigged 修改一个类的所有现有实例,但它不会重新运行
__init__
或重命名现有实例上的属性,因此您很容易以损坏的对象结束(新方法,但旧数据)。
- Jurigged 修改一个类的所有现有实例,但它不会重新运行
- 更新装饰器或闭包的代码可能有效,也可能无效。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 的工作方式如下:
- 盘点现有的模块和功能:插入一个收集和监视源文件的导入钩子。湾。使用 . 查看所有现有功能
gc.get_objects()
。C。添加一个审计钩子来监视调用exec
以清点任何新功能。 - 将源文件解析为定义集。
- 当文件被修改时,将其重新解析为一组定义并将它们与原始文件进行匹配,从而产生一组更改、添加和删除。
- 当函数的代码发生变化时:去掉装饰器 b. 执行新代码 c. 用于
gc.get_referrers()
查找所有使用旧代码的函数 d. 替换它们的内部__code__
指针 - 如果替换失败或添加了全新的代码,请在模块命名空间中执行新代码。
比较
我能找到的 Jurigged 功能集的两个最具可比性的实现(但要找到所有可比较的东西可能有点困难)是IPython中的%autoreload和limeade。以下是主要区别:
-
当其代码更改时,它们都重新执行整个模块。相比之下,Jurigged 通过手术从解析树中提取更改的函数并仅替换这些函数。它仅在模块中执行新的或更改的语句。
哪个更好取决于情况:一方面,重新执行模块将获得更多更改。另一方面,它将重新初始化模块变量和状态,因此某些事情可能会中断。Jurigged 的做法比较保守,只会拾取修改后的功能,但不会触及其他任何东西,所以我相信它不太可能产生意想不到的副作用。它还告诉你它在做什么:)
-
他们将重新执行装饰器,而 Jurigged 将深入研究它们并更新它在内部找到的函数。
同样,没有客观上更好的方法。
%autoreload
将正确地重新执行更改的装饰器,但这些装饰器将返回新对象,因此如果模块导入已装饰的函数,它不会更新到新版本。但是,如果您只修改函数的代码而不修改装饰器,那么 Jurigged 通常能够在装饰器内更改它,因此所有旧实例都将使用新行为。 -
它们依赖于同步点,而 Jurigged 可以在自己的线程中运行。
这是一把双刃剑,因为即使 Jurigged 可以使用零行附加代码向现有脚本添加实时更新,它根本不是线程安全的(代码可以在更新中间执行,这可能是不一致的状态)。
其他类似的努力:
- 重新加载可以包装一个迭代器以使 for 循环可修改。Jurigged 无法做到这一点,但您可以同时使用这两个软件包。
项目详情
下载文件
下载适用于您平台的文件。如果您不确定要选择哪个,请了解有关安装包的更多信息。