ホームページ  >  記事  >  バックエンド開発  >  Python プログラムの実行プロセスには、ソース コードをバイトコードに変換 (つまりコンパイル) し、バイトコードを実行することが含まれます。

Python プログラムの実行プロセスには、ソース コードをバイトコードに変換 (つまりコンパイル) し、バイトコードを実行することが含まれます。

WBOY
WBOY転載
2023-05-09 16:37:091684ブラウズ

質問:

私たちは、テキストを処理したり、システム管理作業を行ったりするために、毎日いくつかの Python プログラムを作成する必要があります。プログラムを作成した後は、Python コマンドを入力してプログラムを起動し、実行を開始するだけです。

$ python some-program.py

では、テキスト形式の .py ファイルを、どのようにして段階的に変換できるものに変換するのでしょうか。 CPU によって実行されますか? 機械語命令はどうですか?また、プログラムの実行中に .pyc ファイルが生成されることがありますが、これらのファイルの機能は何ですか?

1. 実行プロセス

Python は動作的にはシェル スクリプトのようなインタプリタ言語に似ていますが、実際には、Python プログラムの実行原理は Java や Java と本質的に同じです。 C# と 仮想マシン および バイトコード について要約できます。 Python は 2 つのステップでプログラムを実行します。まずプログラム コードをバイトコードにコンパイルし、次に仮想マシンを起動してバイトコードを実行します。

Python プログラムの実行プロセスには、ソース コードをバイトコードに変換 (つまりコンパイル) し、バイトコードを実行することが含まれます。

Python コマンドは、 Python インタープリターですが、他のスクリプト言語インタープリターとは根本的に異なります。実際、Python インタプリタは コンパイラ 仮想マシン で構成されます。 Python インタプリタが開始されると、主に次の 2 つのステップが実行されます:

コンパイラは、.py ファイル内の Python ソース コードをバイトコードにコンパイルし、仮想マシンはコンパイラによって生成されたバイトコードを 1 行ずつ実行します。

したがって、.py ファイル内の Python ステートメントは、機械語命令に直接変換されるのではなく、Python バイトコードに変換されます。

2. バイトコード

Python プログラムのコンパイル結果はバイトコードであり、Python の動作に関連する多くの内容が含まれています。したがって、Python 仮想マシンの動作メカニズムを深く理解する場合でも、Python プログラムの動作効率を最適化する場合でも、バイトコードは重要なコンテンツです。では、Python のバイトコードはどのようなものでしょうか? Python プログラムのバイトコードはどのように取得できますか? Python には、ソース コードを即座にコンパイルするための組み込み関数 apply が用意されています。コンパイル対象のソースコードをパラメータとしてcompile関数を呼び出すだけで、ソースコードのコンパイル結果を取得できます。

3. ソース コードのコンパイル

以下では、compile 関数を使用してプログラムをコンパイルします:

ソース コードは、demo.py ファイルに保存されます:

PI = 3.14

def circle_area(r):
    return PI * r ** 2

class Person(object):
    def __init__(self, name):
        self.name = name

    def say(self):
        print('i am', self.name)

コンパイル 以前は、ソース コードをファイルから読み取る必要がありました:

>>> text = open('D:\myspace\code\pythonCode\mix\demo.py').read()
>>> print(text)
PI = 3.14

def circle_area(r):
    return PI * r ** 2

class Person(object):
    def __init__(self, name):
        self.name = name

    def say(self):
        print('i am', self.name)

次に、コンパイル関数を呼び出してソース コードをコンパイルします:

>>> result = compile(text,'D:\myspace\code\pythonCode\mix\demo.py', 'exec')

コンパイルには 3 つの必須パラメーターがあります。 function:

source : コンパイルするソース コード

filename: ソース コードが配置されているファイル名

mode: コンパイル モード。exec は、ソース コードをコンパイルすることを意味します。 module

3 つのコンパイル モード:

exec: モジュールのソース コードをコンパイルするために使用されます

single: 単一の Python ステートメントを (対話的に) コンパイルするために使用されます

eval : eval 式のコンパイルに使用されます

4. PyCodeObject

コンパイル関数を通じて、最終的なソース コードのコンパイル結果結果を取得します:

>>> result
<code object <module> at 0x000001DEC2FCF680, file "D:\myspace\code\pythonCode\mix\demo.py", line 1>
>>> result.__class__
<class &#39;code&#39;>

最後にコード タイプを取得しますオブジェクトであり、その対応する基礎構造は PyCodeObject です。

PyCodeObject のソース コードは次のとおりです。

/* Bytecode object */
struct PyCodeObject {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */
    int co_posonlyargcount;     /* #positional only arguments */
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list (constants used) */
    PyObject *co_names;         /* list of strings (names used) */
    PyObject *co_varnames;      /* tuple of strings (local variable names) */
    PyObject *co_freevars;      /* tuple of strings (free variable names) */
    PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
    /* The rest aren&#39;t used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode (where it was loaded from) */
    PyObject *co_name;          /* unicode (name, for reference) */
    PyObject *co_linetable;     /* string (encoding addr<->lineno mapping) See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only (see frameobject.c) */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by (next_instr - first_instr).
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
};

コード オブジェクト PyCodeObject は、バイトコードと定数、名前、コードに関係するものなど。主要なフィールドは次のとおりです:

#FieldPurpose##co_argcountco_kwonlyargcountco_nlocalsco_stacksizeco_flagsco_firstlinenoco_code#co_consts定数リストco_names名前リストco_varnamesローカル変数名のリスト

下面打印看一下这些字段对应的数据:

通过co_code字段获得字节码:

>>> result.co_code
b&#39;d\x00Z\x00d\x01d\x02\x84\x00Z\x01G\x00d\x03d\x04\x84\x00d\x04e\x02\x83\x03Z\x03d\x05S\x00&#39;

通过co_names字段获得代码对象涉及的所有名字:

>>> result.co_names
(&#39;PI&#39;, &#39;circle_area&#39;, &#39;object&#39;, &#39;Person&#39;)

通过co_consts字段获得代码对象涉及的所有常量:

>>> result.co_consts
(3.14, <code object circle_area at 0x0000023D04D3F310, file "D:\myspace\code\pythonCode\mix\demo.py", line 3>, &#39;circle_area&#39;, <code object Person at 0x0000023D04D3F5D0, file "D:\myspace\code\pythonCode\mix\demo.py", line 6>, &#39;Person&#39;, None)

可以看到,常量列表中还有两个代码对象,其中一个是circle_area函数体,另一个是Person类定义体。对应Python中作用域的划分方式,可以自然联想到:每个作用域对应一个代码对象。如果这个假设成立,那么Person代码对象的常量列表中应该还包括两个代码对象:init函数体和say函数体。下面取出Person类代码对象来看一下:

>>> person_code = result.co_consts[3]
>>> person_code
<code object Person at 0x0000023D04D3F5D0, file "D:\myspace\code\pythonCode\mix\demo.py", line 6>
>>> person_code.co_consts
(&#39;Person&#39;, <code object __init__ at 0x0000023D04D3F470, file "D:\myspace\code\pythonCode\mix\demo.py", line 7>, &#39;Person.__init__&#39;, <code object say at 0x0000023D04D3F520, file "D:\myspace\code\pythonCode\mix\demo.py", line 10>, &#39;Person.say&#39;, None)

因此,我们得出结论:Python源码编译后,每个作用域都对应着一个代码对象,子作用域代码对象位于父作用域代码对象的常量列表里,层级一一对应。

Python プログラムの実行プロセスには、ソース コードをバイトコードに変換 (つまりコンパイル) し、バイトコードを実行することが含まれます。

至此,我们对Python源码的编译结果——代码对象PyCodeObject有了最基本的认识,后续会在虚拟机、函数机制、类机制中进一步学习。

5. 反编译

字节码是一串不可读的字节序列,跟二进制机器码一样。如果想读懂机器码,可以将其反汇编,那么字节码可以反编译吗?

通过dis模块可以将字节码反编译:

>>> import dis
>>> dis.dis(result.co_code)
 0 LOAD_CONST               0 (0)
 2 STORE_NAME               0 (0)
 4 LOAD_CONST               1 (1)
 6 LOAD_CONST               2 (2)
 8 MAKE_FUNCTION            0
10 STORE_NAME               1 (1)
12 LOAD_BUILD_CLASS
14 LOAD_CONST               3 (3)
16 LOAD_CONST               4 (4)
18 MAKE_FUNCTION            0
20 LOAD_CONST               4 (4)
22 LOAD_NAME                2 (2)
24 CALL_FUNCTION            3
26 STORE_NAME               3 (3)
28 LOAD_CONST               5 (5)
30 RETURN_VALUE

字节码反编译后的结果和汇编语言很类似。其中,第一列是字节码的偏移量,第二列是指令,第三列是操作数。以第一条字节码为例,LOAD_CONST指令将常量加载进栈,常量下标由操作数给出,而下标为0的常量是:

>>> result.co_consts[0]3.14

这样,第一条字节码的意义就明确了:将常量3.14加载到栈。

由于代码对象保存了字节码、常量、名字等上下文信息,因此直接对代码对象进行反编译可以得到更清晰的结果:

>>>dis.dis(result)
  1           0 LOAD_CONST               0 (3.14)
              2 STORE_NAME               0 (PI)

  3           4 LOAD_CONST               1 (<code object circle_area at 0x0000023D04D3F310, file "D:\myspace\code\pythonCode\mix\demo.py", line 3>)
              6 LOAD_CONST               2 (&#39;circle_area&#39;)
              8 MAKE_FUNCTION            0
             10 STORE_NAME               1 (circle_area)

  6          12 LOAD_BUILD_CLASS
             14 LOAD_CONST               3 (<code object Person at 0x0000023D04D3F5D0, file "D:\myspace\code\pythonCode\mix\demo.py", line 6>)
             16 LOAD_CONST               4 (&#39;Person&#39;)
             18 MAKE_FUNCTION            0
             20 LOAD_CONST               4 (&#39;Person&#39;)
             22 LOAD_NAME                2 (object)
             24 CALL_FUNCTION            3
             26 STORE_NAME               3 (Person)
             28 LOAD_CONST               5 (None)
             30 RETURN_VALUE

Disassembly of <code object circle_area at 0x0000023D04D3F310, file "D:\myspace\code\pythonCode\mix\demo.py", line 3>:
  4           0 LOAD_GLOBAL              0 (PI)
              2 LOAD_FAST                0 (r)
              4 LOAD_CONST               1 (2)
              6 BINARY_POWER
              8 BINARY_MULTIPLY
             10 RETURN_VALUE

Disassembly of <code object Person at 0x0000023D04D3F5D0, file "D:\myspace\code\pythonCode\mix\demo.py", line 6>:
  6           0 LOAD_NAME                0 (__name__)
              2 STORE_NAME               1 (__module__)
              4 LOAD_CONST               0 (&#39;Person&#39;)
              6 STORE_NAME               2 (__qualname__)

  7           8 LOAD_CONST               1 (<code object __init__ at 0x0000023D04D3F470, file "D:\myspace\code\pythonCode\mix\demo.py", line 7>)
             10 LOAD_CONST               2 (&#39;Person.__init__&#39;)
             12 MAKE_FUNCTION            0
             14 STORE_NAME               3 (__init__)

 10          16 LOAD_CONST               3 (<code object say at 0x0000023D04D3F520, file "D:\myspace\code\pythonCode\mix\demo.py", line 10>)
             18 LOAD_CONST               4 (&#39;Person.say&#39;)
             20 MAKE_FUNCTION            0
             22 STORE_NAME               4 (say)
             24 LOAD_CONST               5 (None)
             26 RETURN_VALUE

Disassembly of <code object __init__ at 0x0000023D04D3F470, file "D:\myspace\code\pythonCode\mix\demo.py", line 7>:
  8           0 LOAD_FAST                1 (name)
              2 LOAD_FAST                0 (self)
              4 STORE_ATTR               0 (name)
              6 LOAD_CONST               0 (None)
              8 RETURN_VALUE

Disassembly of <code object say at 0x0000023D04D3F520, file "D:\myspace\code\pythonCode\mix\demo.py", line 10>:
 11           0 LOAD_GLOBAL              0 (print)
              2 LOAD_CONST               1 (&#39;i am&#39;)
              4 LOAD_FAST                0 (self)
              6 LOAD_ATTR                1 (name)
              8 CALL_FUNCTION            2
             10 POP_TOP
             12 LOAD_CONST               0 (None)
             14 RETURN_VALUE

操作数指定的常量或名字的实际值在旁边的括号内列出,此外,字节码以语句为单位进行了分组,中间以空行隔开,语句的行号在字节码前面给出。例如PI = 3.14这个语句就被会变成了两条字节码:

  1           0 LOAD_CONST               0 (3.14)
              2 STORE_NAME               0 (PI)

6. pyc

如果将demo作为模块导入,Python将在demo.py文件所在目录下生成.pyc文件:

>>> import demo

Python プログラムの実行プロセスには、ソース コードをバイトコードに変換 (つまりコンパイル) し、バイトコードを実行することが含まれます。

pyc文件会保存经过序列化处理的代码对象PyCodeObject。这样一来,Python后续导入demo模块时,直接读取pyc文件并反序列化即可得到代码对象,避免了重复编译导致的开销。只有demo.py有新修改(时间戳比.pyc文件新),Python才会重新编译。

因此,对比Java而言:Python中的.py文件可以类比Java中的.java文件,都是源码文件;而.pyc文件可以类比.class文件,都是编译结果。只不过Java程序需要先用编译器javac命令来编译,再用虚拟机java命令来执行;而Python解释器把这两个过程都完成了。

パラメータの数
キーワードパラメータの数
変数の部分的な数
コードの実行に必要なスタック領域
識別
コードブロックの最初の行番号
命令オペレーションコード、つまりバイトコード

以上がPython プログラムの実行プロセスには、ソース コードをバイトコードに変換 (つまりコンパイル) し、バイトコードを実行することが含まれます。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。