代码之家  ›  专栏  ›  技术社区  ›  my_question

CPython:为什么一个三行脚本需要在解释器中执行3个以上的循环?

  •  2
  • my_question  · 技术社区  · 6 年前

    我刚刚看了 this Youtube lecture 关于郭飞利浦的《飞龙内部》,我有一件事感到困惑。

    在25:55,他通过插入来修改cpython的c源 printf(“hello\n”) 在运行所有字节码指令的无休止循环开始时,您可以通过以下方法来完成相同的操作:

    • 下载python 2.7c源代码
    • 打开文件 Python/ceval.c
    • 找到无休止的评估循环的开始, for (;;) {
    • 添加行 printf('hello\n'); 作为无限循环的第一行。
    • configure make 构建python二进制文件。

    他写了一个三行测试.py:

    X = 1
    Y = 2
    print X + Y
    

    难题是,当他用修改过的解释器运行test.py时,为什么在我们看到3之前会有这么多hello?

    所以编译器实际上在编译外部python脚本之前生成了许多内部字节代码指令?

    1 回复  |  直到 6 年前
        1
  •  2
  •   Martijn Pieters    6 年前

    你看到这么多有两个原因 hello S印刷体:

    • 对于每个可能的python语句,python没有特殊的字节码。相反,语句将使用 结合 字节码的。
    • python解释器导入一系列python模块 只是为了开始跑步 . 您可以使用 -v 切换以查看每次导入的内容。每个模块都由多个语句组成,所以在您得到正在运行的小脚本之前,有相当多的字节码需要遍历。

    如果我把这三条线 test.py 并使用我未修改的python 2.7二进制文件来运行它, - V 开关,我明白了:

    $ python2.7 -v test.py
    # installing zipimport hook
    import zipimport # builtin
    # installed zipimport hook
    # /..../lib/python2.7/site.pyc matches /..../lib/python2.7/site.py
    import site # precompiled from /..../lib/python2.7/site.pyc
    # /..../lib/python2.7/os.pyc matches /..../lib/python2.7/os.py
    import os # precompiled from /..../lib/python2.7/os.pyc
    import errno # builtin
    import posix # builtin
    # /..../lib/python2.7/posixpath.pyc matches /..../lib/python2.7/posixpath.py
    import posixpath # precompiled from /..../lib/python2.7/posixpath.pyc
    # /..../lib/python2.7/stat.pyc matches /..../lib/python2.7/stat.py
    import stat # precompiled from /..../lib/python2.7/stat.pyc
    # /..../lib/python2.7/genericpath.pyc matches /..../lib/python2.7/genericpath.py
    import genericpath # precompiled from /..../lib/python2.7/genericpath.pyc
    # /..../lib/python2.7/warnings.pyc matches /..../lib/python2.7/warnings.py
    import warnings # precompiled from /..../lib/python2.7/warnings.pyc
    # /..../lib/python2.7/linecache.pyc matches /..../lib/python2.7/linecache.py
    import linecache # precompiled from /..../lib/python2.7/linecache.pyc
    # /..../lib/python2.7/types.pyc matches /..../lib/python2.7/types.py
    import types # precompiled from /..../lib/python2.7/types.pyc
    # /..../lib/python2.7/UserDict.pyc matches /..../lib/python2.7/UserDict.py
    import UserDict # precompiled from /..../lib/python2.7/UserDict.pyc
    # /..../lib/python2.7/_abcoll.pyc matches /..../lib/python2.7/_abcoll.py
    import _abcoll # precompiled from /..../lib/python2.7/_abcoll.pyc
    # /..../lib/python2.7/abc.pyc matches /..../lib/python2.7/abc.py
    import abc # precompiled from /..../lib/python2.7/abc.pyc
    # /..../lib/python2.7/_weakrefset.pyc matches /..../lib/python2.7/_weakrefset.py
    import _weakrefset # precompiled from /..../lib/python2.7/_weakrefset.pyc
    import _weakref # builtin
    # /..../lib/python2.7/copy_reg.pyc matches /..../lib/python2.7/copy_reg.py
    import copy_reg # precompiled from /..../lib/python2.7/copy_reg.pyc
    import encodings # directory /..../lib/python2.7/encodings
    # /..../lib/python2.7/encodings/__init__.pyc matches /..../lib/python2.7/encodings/__init__.py
    import encodings # precompiled from /..../lib/python2.7/encodings/__init__.pyc
    # /..../lib/python2.7/codecs.pyc matches /..../lib/python2.7/codecs.py
    import codecs # precompiled from /..../lib/python2.7/codecs.pyc
    import _codecs # builtin
    # /..../lib/python2.7/encodings/aliases.pyc matches /..../lib/python2.7/encodings/aliases.py
    import encodings.aliases # precompiled from /..../lib/python2.7/encodings/aliases.pyc
    # /..../lib/python2.7/encodings/utf_8.pyc matches /..../lib/python2.7/encodings/utf_8.py
    import encodings.utf_8 # precompiled from /..../lib/python2.7/encodings/utf_8.pyc
    Python 2.7.15 (default, May  7 2018, 17:08:03)
    [GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.1)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    3
    # -- clean-up output omitted --
    

    import ... 其中的行引用了内置模块(Python二进制文件的一部分,在C中实现)或 .pyc 字节码缓存文件。有17个这样的文件被导入 甚至在脚本代码运行之前 .

    主脚本中的3行代码将进一步转换为9个字节码指令:

    >>> import dis
    >>> dis.dis(compile(r'''\
    ... X = 1
    ... Y = 2
    ... print X + Y
    ... ''', '', 'exec'))
      2           0 LOAD_CONST               0 (1)
                  3 STORE_NAME               0 (X)
    
      3           6 LOAD_CONST               1 (2)
                  9 STORE_NAME               1 (Y)
    
      4          12 LOAD_NAME                0 (X)
                 15 LOAD_NAME                1 (Y)
                 18 BINARY_ADD
                 19 PRINT_ITEM
                 20 PRINT_NEWLINE
                 21 LOAD_CONST               2 (None)
                 24 RETURN_VALUE
    

    (我忽略了结尾的2个字节码,编码了一个额外的 return None 这不适用于模块)。