我计划创建一个巨大的可执行目录,并将其安装在一些设备上。
想象一下,后来我在我的python模块中发现了一个bug。是否有任何方法只传输/复制修改过的字节码并用新的字节码替换原始字节码。
我想这么做的原因是,在我的上下文中,带宽非常昂贵,我想远程修补代码。
示例:我有一个带有两个文件的项目:prog.py:(有以下三行)
import mod1
if __name__ == "__main__":
mod1.hello()mod1.py:(以下两行)
def hello():
print("hello old world")现在我使用PYTHONHASHSEED=2 pyinstaller prog.py创建我的目录,然后复制到我的设备上。
现在我修改mod1.py
def hello():
print("hello new world")我用PYTHONHASHSEED=2 pyinstaller prog.py重新编译了完整的目录,其大小约为10M,dist/prog/prog文件的大小约为1M。
使用pyi-archive_viewer,我可以在PYZ-00.pyz中从可执行的dist/prog/prog中提取PYZ-00.pyz,我可以找到并提取只使用133个字节的mod1。
现在,如果我将该文件复制到我的设备上,如何才能更新旧的dist/prog/prog,使其具有新的PYZ-00.pyz:mod1字节码。
我可以使用什么代码来分解,在替换了一个特定的文件(模块)之后,我可以使用什么代码来重新组装?
替代方案:将pyc文件移动到zip文件中,启动性能并不那么关键。我还可以使用另一种解决方案,即不创建PYZ文件并将其添加到可执行文件中,但是dist目录包含一个包含所有.pyc文件的zip文件。
另一种选择:将.pyc文件复制到应用程序目录中,这将导致__file__具有与PYZ模式完全相同的值。性能方面可能没有那么好,并且创建了大量的文件,但是如果增量更新是关键的话,可能是处理它的一个选项。
发布于 2021-08-12 09:23:01
这种解决方案既不能“修补”.PYZ文件,也不能将所有.pyc文件放入zip文件。
但到目前为止,这是我找到的唯一可行的解决方案,它适用于具有大量第三方依赖项的大型项目。
其想法是删除所有(或.PYZ文件中的大多数文件),并将相应的.pyc文件复制到工作目录中。
随着时间的推移,我将加强和详细阐述这一答案。我还在做实验:
我通过修改规范文件来实现这一点:
MYDIR创建目录,MYDIR/src将a.pure的所有文件复制到MYDIR/src。(子目录对应于模块的名称。例如,模块mypackage.mod.common将通过文件存储在afterwards..pyc文件,并删除.py文件afterwards.PYZ文件)。(在我的测试用例中,在PYZ)PYZMYDIR/src的所有文件(例如,使用a.datas + Tree("src"))。
文件更改:在开头
import os
MYDIR = os.path.realpath(SPECPATH)
sys.path.append(MYDIR)
import mypyinsthelpers # allows to reuse the code in multiple projects然后,在(未修改的) a = Analysis(...部分之后添加。
to_rmv_from_pyc = mypyinsthelpers.mk_copy_n_compile(a.pure, MYDIR)
# modified creation of pyz`
pyz = PYZ(a.pure - to_rmv_from_pyc, a.zipped_data,
cipher=block_cipher)我将进一步详细介绍函数mypyinsthelpers.mk_copy_n_compile。
更改收集阶段:
而不是
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
...我写道:
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas + Tree("src"),
...这里是mypyinsthelpers.mk_copy_n_compile()的声明
import compileall
import os
import shutil
from pathlib import Path
def mk_copy_n_compile(toc, src_tree):
"""
- copy source files to a destination directory
- compile them as pyc
- delete source
"""
dst_base_path = os.path.join(src_tree, "src")
to_rm = []
# copy files to destination tree
for entry in toc:
modname, src, typ = entry
assert typ == "PYMODULE"
assert src.endswith(".py") or src.endswith(".pyw")
# TODO: might add logic to skip some files (keep them in PYC)
to_rm.append(entry)
if src.endswith("__init__.py"):
modname += ".__init__"
m_split = modname.split(".")
m_split[-1] += ".py"
dst_dir = os.path.join(dst_base_path, *m_split[:-1])
dst_path = os.path.join(dst_dir, m_split[-1])
if not os.path.isdir(dst_dir):
os.makedirs(dst_dir)
print(entry[:2], dst_path)
shutil.copy(src, dst_path)
# now compile all files and rmv src
top_tree = src_tree
src_tree = os.path.join(src_tree, "src")
curdir = os.getcwd()
os.chdir(dst_base_path)
for path in Path(dst_base_path).glob("**/*.py"):
# TODO: might add code to keep some files as source
compileall.compile_file(
str(path.relative_to(dst_base_path)), quiet=1, legacy=True)
path.unlink()
os.chdir(curdir)
return to_rm发布于 2021-08-05 05:24:35
这是一个相当复杂的问题,但我认为这可能至少是你正在寻找的一部分。
根据您的示例,我更改了prog.py,以便它在从源代码运行时非常正常地导入,但是当使用pyinstaller冻结时,它直接从pyc文件中运行。
import sys
def import_pyc(name):
import py_compile
import types
import marshal
pyversion = f"{sys.version_info.major}{sys.version_info.minor}"
filename = f"{name}.cpython-{pyversion}.pyc"
with open(filename, "rb") as pyc_file:
# pyc files have 16 bytes reserved at the start in python 3.7+
# due to https://www.python.org/dev/peps/pep-0552/
# may change again in the future
pyc_file.seek(16)
code_obj = marshal.load(pyc_file)
module = types.ModuleType(name)
exec(code_obj, module.__dict__)
globals()[name] = module
def import_py(name):
import importlib
globals()[name] = importlib.import_module("mod1")
def import2(name):
if getattr(sys, "frozen", False):
import_pyc(name)
else:
import_py(name)
import2("mod1")
if __name__ == "__main__":
mod1.hello()这在很大程度上是基于奇妙的答案here。
这意味着mod.py不是由PyInstaller打包的,您必须将mod1.cpython-38.pyc作为一个数据文件。
一种方便的方法是使用命令PyInstaller --add-data "__pycache__/*;." prog.py (如果您不在Windows上,可以切换冒号的分号)。这会将__pycache__文件夹中的所有内容,所有导入的模块,放入结束的dist/prog文件夹中。请注意,如果您多次运行,PyInstaller将为__pycache__中的主要python文件夹放置一个pyc,以便在以后的运行中绑定。
根据打包和运行项目的方式,您可能会遇到当前工作目录关闭的问题,当您试图加载pyc的时候,这可能会导致一个pyc。我不能给您一个找到所需路径的灵丹妙药,因为这取决于您是如何完成任务的,但是我通常用来查找当前工作目录的绝对路径的方法是os.path.dirname(sys.executable)和os.path.dirname(os.path.abspath(__file__))。
https://stackoverflow.com/questions/67019608
复制相似问题