如何将字节码从 stdin 提供给 python 解释器?

How to feed bytecode to python interpreter from stdin?

我知道我可以 运行 来自 stdin 的 python 脚本,如下所示:

python - < script.py

我也可以运行编译的python文件:

python script.pyc

但我无法从标准输入 运行 编译 python 文件:

python - < script.pyc
SyntaxError: Non-UTF-8 code starting with '\xee' in file <stdin> on line 1, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

很明显我必须告诉解释器这是字节码。但是怎么办?

字节码不应作为标准输入传递。它包含仅由 pyhon 解释器 运行 执行的代码,在它之外,它只是胡言乱语。基本上,当您尝试将文件作为标准输入传递时,它会被视为文本,因此会出现错误。 .py 文件是文本格式,因此可以像文本一样安全地解析并正确执行。

根据您的代码示例,我假设您正在尝试从 bash 控制台(Linux 中的行)运行 文件,因此正确的方法确实是:

python script.pyc

这也是你试过的。

TL/DR

有可能

python -c "import sys;import marshal;exec(marshal.loads(sys.stdin.buffer.read()[16:]))" < script.pyc

长版

之前的回答不正确。可以通过标准输入实现这一点。 Python 解释器有一个 -c 标志,允许它解释代码。 例如,

python -c "print('Hello, world!')"

会输出Hello, world! 可以使用内置库 sys,特别是 sys.stdin.buffer.read() 函数在 python 程序中读取标准输入。但是,这只能读取一次,并且 returns 一个类似字节的对象。

.pyc 文件有一个特殊的结构 - 4 个魔法字节、一个时间戳和一个编组代码对象。 魔术字节和时间戳加在一起是 16 个字节。根据我的发现,时间戳并不重要,但不同版本之间的魔法字节会发生变化。删除它,我们有一个编组代码对象。这就是 [16:] 所做的——从标准输入中的类字节对象中删除魔法字节和时间戳。

Python使用marshal库压缩编译后的代码对象,并提供marshal.loads(bytes)函数将bytes-like对象转换为unmarshalled对象,这在这种情况下是 types.CodeType 的一个实例 - 一个代码对象。

最后,虽然 Python 的 exec() 函数通常接受字符串,但它也可以接受代码对象。我们将代码对象传递给它,它会执行它。

一句警告: 将字节码直接传递到标准输入然后执行是一个巨大的安全问题和糟糕的做法,但考虑到您首先尝试这样做,您可能不在乎。

参考文献:

sys — System-specific parameters and functions — Python 3.9.6 documentation

The structure of .pyc files | Ned Batchelder

Built-in Types — Python 3.9.6 documentation

marshal — Internal Python object serialization — Python 3.9.6 documentation

Built-in Functions — Python 3.9.6 documentation