如何导入一个.pyc编译的Python文件并使用它?

5

我正在尝试弄清楚如何在Python脚本中包含一个 .pyc 文件。

例如,我的脚本名为:

myscript.py

我想要包含的脚本名叫:

included_script.pyc

所以,我只需要使用以下内容吗:
import included_script

那么这个操作会自动执行 included_script.pyc 吗?还是说我需要做一些额外的工作,才能让 included_script.pycmyscript.py 中运行?

我需要传递在 included_script.pyc 中使用的变量吗?如果是,如何实现?


1
不,那不会自动执行。请查看 - https://dev59.com/rHNA5IYBdhLWcg3wSrma - Nikhil Parmar
1个回答

14

很遗憾,不能自动完成这项操作。你当然可以手动完成,但是这样做的话会很麻烦且难看。


设置:

为了演示目的,我将首先生成一个 .pyc 文件。为此,我们首先需要一个相应的 .py 文件。我们的示例 test.py 文件如下:

def foo():
    print("In foo")

if __name__ == "__main__":
    print("Hello World")

非常简单。使用标准库中的py_compile模块可以生成.pyc文件。我们只需按照以下方式传入.py文件的名称和我们的.pyc文件的名称即可:

 py_compile.compile('test.py', 'mypyc.pyc')

这将把mypyc.pyc放置在我们当前的工作目录中。


.pyc文件获取代码:

.pyc文件包含按以下方式结构化的字节:

  • 前4个字节表示“魔数”
  • 接下来的4个字节保存修改时间戳
  • 剩余的内容是一个编组的 code对象。

我们想要的是那个编组的code对象,因此我们需要import marshal对其进行解编组并执行。此外,我们真的不关心/不需要前8个字节,并且使用它们对.pyc文件进行解编组被禁止,因此我们将忽略它们(seek越过它们):

import marshal

s = open('mypyc.pyc', 'rb')
s.seek(8)  # go past first eight bytes
code_obj = marshal.load(s)

现在我们有了一个漂亮的code对象,可供test.py使用,并且已经准备好按我们的意愿执行。这里我们有两个选择:

  1. 在当前的global命名空间中执行它。这将绑定我们.pyc文件内所有定义到当前命名空间中,并充当一种类似于:from file import *语句的作用。

  2. 创建一个新的模块对象并在模块内执行代码。这将类似于import file语句。


模拟from file import *的行为:

要执行此操作非常简单,只需执行以下操作:

exec(code_obj)

这将在当前命名空间中执行code_obj中包含的代码并绑定所有变量。调用后,我们可以像调用其他函数一样调用foo

foo()
# prints: In foo!

注意: exec() 是一个内置函数。


模拟 import file 行为:

这需要另一个要求,即使用 types 模块。该模块包含类型ModuleType,我们可以用它来创建一个新的模块对象。它接受两个参数,模块的名称(必填)和文档(可选):

m = types.ModuleType("Fancy Name", "Fancy Documentation")

print(m)
<module 'Fancy Name' (built-in)>

现在我们有了模块对象,我们可以再次使用 exec 来执行 code_obj 中包含的代码,执行的命名空间是模块命名空间(即 m.__dict__):

exec(code_obj, m.__dict__)

现在,我们的模块m中拥有了code_obj中定义的所有内容,您可以通过运行以下代码进行确认:

m.foo() 
# prints: In foo

以下是包含 .pyc 文件到您的模块中的方式。至少,这是我能想到的几种方式。我并不真正看到这样做的实用性,但是嘿,我不在这里评判。


2
虽然我不是很有信心,但Python 3.3+似乎需要在头部魔数中再增加4个字节,因此s.seek(8)需要改为s.seek(12)。https://qiita.com/amedama/items/698a7c4dbdd34b03b427 - dkato
我有一个.pyc文件,内容如下: def myfunc(in_str = "Hello World"): print(in_str)我使用另一个脚本运行它,代码如下: import marshal s = open('script2.cpython-37.pyc', 'rb') s.seek(12) code_obj = marshal.load(s) exec(code_obj) myfunc() myfunc("Testing from script4")但是出现了错误: (base) C:\Users\ashish\Desktop\code_20210128_1200\2>python script4.py Traceback (most recent call last): File "script4.py", line 5, in <module> code_obj = marshal.load(s) ValueError: bad marshal data (unknown type code) - Ashish Jain
3
在Python 3.7及以上版本中,我认为应该是s.seek(16)。https://www.python.org/dev/peps/pep-0552/ - Starbuck5

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