有趣...我做的第一件事就是要求--help
$ python --help
usage: python [option] ... [-c cmd | -m mod | file | -] [arg] ...
Options and arguments (and corresponding environment variables):
-B : don't write .py[co] files on import; also PYTHONDONTWRITEBYTECODE=x
...
我看到的第一个选项是在导入时禁用自动 pyc 和 pyo 文件生成,尽管这可能导致其按字母顺序排列。
让我们运行一些测试
$ echo "print 'hello world'" > test.py
$ python test.py
hello world
$ ls test.py*
test.py
$ python -c "import test"
hello world
$ ls test.py*
test.py test.pyc
所以它只在导入时生成pyc文件。
现在为了检查正在使用哪些文件,我将使用类似于 linux truss 的 OS X dtruss 进行完整跟踪...
$ echo '#!/bin/sh
python test.py' > test.sh
$ chmod a+x test.sh
$ sudo dtruss -a ./test.sh 2>&1 | grep "test.py*"
975/0x5713: 244829 6 3 read(0x3, "#!/bin/sh \npython test.py\n\b\0", 0x50) = 26 0
975/0x5713: 244874 4 2 read(0xFF, "#!/bin/sh \npython test.py\n\b\0", 0x1A) = 26 0
977/0x5729: 658694 6 2 readlink("test.py\0", 0x7FFF5636E360, 0x400) = -1 Err#22
977/0x5729: 658726 10 6 getattrlist("/Users/samyvilar/test.py\0", 0x7FFF7C0EE510, 0x7FFF5636C6E0 = 0 0
977/0x5729: 658732 3 1 stat64("test.py\0", 0x7FFF5636DCB8, 0x0) = 0 0
977/0x5729: 658737 5 3 open_nocancel("test.py\0", 0x0, 0x1B6) = 3 0
977/0x5729: 658760 4 2 stat64("test.py\0", 0x7FFF5636E930, 0x1) = 0 0
977/0x5729: 658764 5 2 open_nocancel("test.py\0", 0x0, 0x1B6) = 3 0
从外观上看,python 甚至根本没有触及 test.pyc 文件!
$ echo '#!/bin/sh
python -c "import test"' > test.sh
$ chmod a+x test.sh
$ sudo dtruss -a ./test.sh 2>&1 | grep "test.py*"
$ sudo dtruss -a ./test.sh 2>&1 | grep "test.py*"
1028/0x5d74: 654642 8 5 open_nocancel("test.py\0", 0x0, 0x1B6) = 3 0
1028/0x5d74: 654683 8 5 open_nocancel("test.pyc\0", 0x0, 0x1B6) = 4 0
$
好吧,这很有趣,它看起来像是打开了 test.py 然后 test.pyc
当我们删除 pyc 文件时会发生什么。
$ rm test.pyc
$ sudo dtruss -a ./test.sh 2>&1 | grep "test.py*"
1058/0x5fd6: 654151 7 4 open_nocancel("/Users/samyvilar/test.py\0", 0x0, 0x1B6) = 3 0
1058/0x5fd6: 654191 6 3 open_nocancel("/Users/samyvilar/test.pyc\0", 0x0, 0x1B6) = -1 Err#2
1058/0x5fd6: 654234 7 3 unlink("/Users/samyvilar/test.pyc\0", 0x1012B456F, 0x1012B45E0) = -1 Err#2
1058/0x5fd6: 654400 171 163 open("/Users/samyvilar/test.pyc\0", 0xE01, 0x81A4) = 4 0
它首先打开 test.py 然后它“尝试”打开 test.pyc 返回错误然后它调用 unlink 并再次生成 pyc 文件......有趣,我认为它会检查。
如果我们删除原始的 py 文件会怎样。
$ sudo dtruss -a ./test.sh 2>&1 | grep "test.py*"
1107/0x670d: 655064 4 1 open_nocancel("test.py\0", 0x0, 0x1B6) = -1 Err#2
1107/0x670d: 655069 8 4 open_nocancel("test.pyc\0", 0x0, 0x1B6) = 3 0
毫不奇怪,它无法打开 test.py 但它仍然继续,直到今天我不确定这是否真的“好的” python 应该发出某种警告,我被这个烧了几次,不小心删除了我的文件,运行了我的测试并在它们通过时松了一口气,但当我似乎无法找到源代码时开始大汗淋漓!
在此测试之后,我们假设 python 仅在调用时直接使用 pyc 文件, python test.pyc
或者在导入时间接使用,否则它似乎不使用它们。
据说 CPythons 编译器被设计得相当快,它不做太多类型检查,它可能会生成非常高级的字节码,所以大部分工作负载实际上是由虚拟机完成的......它可能只通过一次, lexing->compiler->byte-code 一次完成,它每次都会这样做,它从命令行读取一个 python 文件,或者在导入时没有 pyc 文件,在这种情况下它会创建它。
这可能就是为什么其他一些实现更快的原因,因为它们需要更多时间来编译,但会生成可以很好优化的更原始的字节码。
构建一个虚拟机来高效地做纯解释是极其困难的......
这一切都与平衡有关,您的字节码越强大,您的编译器就越简单,但您的虚拟机必须越复杂和越慢,反之亦然......