python编译相关

python编译相关

具体编译步骤

Python代码的编译和执行过程可以更详细地描述如下:

  1. 词法分析(Lexical Analysis)和语法分析(Syntax Analysis)
    Python解释器首先会对源代码进行词法分析和语法分析。词法分析器会将源代码分解成词法单元(tokens),这些单元是语言的基本构建块,例如关键字、标识符、运算符等。语法分析器会根据语言的语法规则将这些词法单元组织成抽象语法树(Abstract Syntax Tree,AST),这是源代码的一种中间表示形式。

    • 抽象语法树是什么样的:(举例)

      抽象语法树(Abstract Syntax Tree,AST)是在编译过程中常用的一种数据结构,用于表示源代码的语法结构。让我们以一个简单的Python代码示例来说明:

      def greet(name):
          return f"Hello, {name}!"
      print(greet("Alice"))
      

      这段代码的抽象语法树可以如下所示(简化表示):

      Module(
          body=[
              FunctionDef(
                  name='greet',
                  args=arguments(
      //arg 表示函数的一个参数,arguments 则表示函数定义中的参数集合。
                      args=[
                          arg(arg='name', annotation=None)
                      ],
                      defaults=[],
      //defaults:这表示位置参数的默认值。它也是一个列表,但在这个例子中是空的。如果函数的某些参数有默认值,那么这里会指定这些默认值。
                      kw_defaults=[],
      //kw_defaults:这表示关键字参数的默认值。与 defaults 类似,它也是一个列表。然而,在这个例子中也是空的,表示函数的关键字参数没有默认值。
                      kwarg=None,
      //kwarg:这表示用于收集未明确定义的额外关键字参数的关键字参数的名称。在这个例子中,它设置为 None,表示函数不接受超出明确定义的关键字参数之外的其他关键字参数。
                      kwonlyargs=[],
      //kwonlyargs:这表示仅关键字参数。关键字参数是一种只能通过关键字传递并且在参数列表中在 * 之后指定的参数。在这个例子中也是一个空列表。
                      kwargannotation=None
      //kwargannotation:这表示 kwarg 的注释,如果有的话。注释是可选的元数据,可以附加到函数参数上,指示参数的预期类型或用途。在这个例子中,它设置为 None,表示未为 kwarg 指定注释。
                  ),
                  body=[
                      Return(
      //Return 表示函数的返回语句,用于从函数中返回一个值。在这里,Return 节点表示函数的返回部分。
                          value=JoinedStr(
      //value 属性指定了要返回的值。在这里,value 是一个 JoinedStr 对象,它表示一个由多个字符串拼接而成的复合字符串。JoinedStr 表示一个由多个字符串组成的复合字符串。
                              values=[
      //values 属性是一个列表,包含了组成复合字符串的各个部分。在这里,复合字符串由 'Hello, '、Name 对象和 '!' 三部分组成。
                                  Str(s='Hello, '),
                                  FormattedValue(
      //FormattedValue 表示格式化值,用于在字符串中插入变量。value 属性是一个 Name 对象,表示要插入的变量名。在这里,Name(id='name', ctx=Load()) 表示要插入的变量是函数参数 name。
                                      value=Name(id='name', ctx=Load()),
                                      conversion=-1,
                                      format_spec=None
                                  ),
                                  Str(s='!')
                              ]
                          )
                      )
                  ],
                  decorator_list=[]
              ),
              Expr(
      //Expr 表示一个表达式语句,用于执行函数调用或其他表达式。
                  value=Call(
      //Call 表示函数调用表达式。func 属性是一个 Name 对象,表示要调用的函数名。在这里,Name(id='print', ctx=Load()) 表示要调用的函数是内置函数 print()。
                      func=Name(id='print', ctx=Load()),
                      args=[
                          Call(
                              func=Name(id='greet', ctx=Load()),
      //id 和 ctx 分别是 Name 对象的两个属性:id 属性:表示标识符的名称。在这里,id='print' 表示标识符的名称是 'print',即函数名或变量名为 print。ctx 属性:表示标识符的上下文(Context)。ctx 可以指定标识符在代码中的使用方式,例如加载(load)、存储(store)等。在这里,ctx=Load() 表示该标识符在这个上下文中是一个加载(load)的标识符,即在代码中引用了名为 'print' 的函数或变量。
                              args=[
                                  Str(s='Alice')
                              ],
                              keywords=[]
                          )
                      ],
                      keywords=[]
                  )
              )
          ]
      )
      

      在这个抽象语法树中,我们可以看到代码中的函数定义、函数调用以及其他表达式的结构。每个节点都表示源代码中的一个语法结构,例如函数定义、表达式等。这种抽象语法树的表示形式使得编译器可以更轻松地分析和处理源代码,进而进行编译和执行。

      1. 位置参数(Positional Arguments):
         位置参数是函数定义中的普通参数,它们按照在函数参数列表中的顺序进行传递,不需要指定参数名称。例如,在函数定义中,`def my_function(a, b):` 中的 `a` 和 `b` 就是位置参数。在调用函数时,传递的参数值会按照位置与位置参数一一对应。
      
      2. 默认值(Default Values):
         默认值是在函数定义中为参数提供的初始值。如果在函数调用时没有提供对应参数的值,那么参数将使用默认值。默认值允许函数在某些情况下以可选方式接受参数。例如,在函数定义中,`def greet(name="Guest"):`, 这里的 `name` 参数有一个默认值 `"Guest"`。如果调用 `greet()` 时没有传递参数,它将使用默认值 `"Guest"`。
      
      3. 仅关键字参数(Keyword-Only Arguments):
         仅关键字参数是只能通过关键字传递的参数,这些参数出现在函数定义中的 `*` 符号之后。这意味着在函数调用时必须使用参数名称来传递这些参数值。这种类型的参数可以增加函数的灵活性和可读性。例如,在函数定义中,`def greet(name, *, prefix="Hello"):` 中的 `prefix` 参数就是一个仅关键字参数。这意味着在调用 `greet()` 时必须通过关键字指定 `prefix` 参数的值,例如 `greet("Alice", prefix="Hi")`。
      
      4. 注释(Annotations):
         注释是Python中的一种元数据,用于为函数参数或变量提供类型信息或其他说明。注释不会影响代码的运行,但可以提供更多的信息,帮助理解代码的含义和用法。例如,在函数定义中,`def greet(name: str):`, 这里的 `str` 就是参数 `name` 的注释,指示其应该是一个字符串类型。注释可以在函数定义时使用冒号(`:`)后面的形式指定。在实际运行中,这些注释并不会被Python解释器使用,但可以被其他工具或IDE用来进行类型检查或提供代码提示。
      
      
  2. 编译成字节码
    接下来,AST会被编译成字节码。这个阶段由Python解释器的编译器组件完成。字节码是一种中间表示形式,类似于汇编语言,它包含一系列的指令,用于执行相应的操作。字节码与具体的硬件和操作系统无关,也就是说生成的字节码可以在任何支持Python解释器的平台上执行,而不需要重新编译。

  3. 执行字节码
    Python虚拟机(Python Virtual Machine,简称为VM)会负责执行字节码。虚拟机是一个解释器,它按照字节码中的指令逐条执行。在执行过程中,虚拟机会将字节码转换为机器代码,并在底层硬件上执行。这个过程是动态的,允许Python代码在运行时进行一些优化和动态调整。

  4. 优化和动态调整
    在执行过程中,Python解释器还会进行一些优化和动态调整。例如,解释器可能会对频繁执行的代码块进行编译优化,将其编译成本地机器码以提高执行效率。此外,解释器还会动态地调整内存分配和管理策略,以最大限度地减少内存占用和提高性能。

  5. pyc是怎么产生的:

    在首次运行Python程序时,解释器会将源代码编译成字节码,并且会将这些字节码缓存到磁盘上的 .pyc 文件中(如果存在对应的 .py 文件)。这样,在下次运行相同的程序时,解释器可以直接加载 .pyc 文件,而无需重新编译源代码。这种缓存的机制可以加快程序的启动速度,特别是对于大型项目或经常被重复执行的脚本来说。需要注意的是,.pyc 文件并不是与特定机器或平台相关的二进制文件。它们是跨平台的中间字节码表示形式,因此可以在不同的平台上被加载和执行。

整个过程中,Python代码并不会直接编译成机器代码,而是先被解释器解析为字节码,然后由解释器执行字节码。这种解释型的方式使得Python具有跨平台性,因为字节码是与硬件无关的,只要有对应的解释器,Python代码就可以在不同平台上执行。。

如何生成pyc文件

  1. 在py程序内部

    使用 Python 自带的 py_compile 模块来将 .py 文件编译成 .pyc 文件。这个模块提供了一个名为 compile 的函数,你可以使用它来编译 Python 源文件。

    import py_compile
    
    # 指定要编译的 Python 源文件路径
    source_file = 'example.py'
    
    # 编译 Python 源文件,并生成对应的 .pyc 文件
    py_compile.compile(source_file)
    

    运行这段代码将会在源文件所在的目录下生成一个与源文件同名的 .pyc 文件。

    compileall 模块也是用于将 Python 源文件编译成字节码文件的工具,它比 py_compile 模块更加灵活,可以批量编译目录下的所有 .py 文件。

    import compileall
    
    # 指定要编译的目录路径
    directory = '/path/to/directory'
    
    # 编译目录下的所有 Python 源文件,并生成对应的 .pyc 文件
    compileall.compile_dir(directory)
    
    

    这将会编译指定目录下的所有 Python 源文件,并在相同目录下生成对应的 .pyc 文件。

  2. 使用命令行工具来编译 Python 源文件,

    例如在命令行中执行以下命令:

    python -m py_compile example.py
    

    这将在同一目录下生成一个名为 example.pyc 的文件。

pyo

.pyo 文件与 .pyc 文件相似,只是在优化模式下生成,并且可能会删除一些调试信息和注释。

在 Python 3.x 中,默认情况下,当你使用优化标志(-O-OO)运行 Python 解释器时,生成的字节码文件将使用 .pyo 扩展名而不是 .pyc。这些优化标志会启用优化编译器标志,从而在生成的字节码中删除一些调试信息或注释,以减小字节码文件的大小并提高执行效率。

  • O:启用优化编译,将会删除 assert 语句。
  • OO:启用更严格的优化,将会删除 assert 语句以及文档字符串(docstrings)。

生成的 .pyo 文件可以被 Python 解释器加载和执行,与 .pyc 文件的使用方式相同。不过需要注意的是,.pyo 文件并不会自动替换 .pyc 文件,而是作为 .pyc 文件的一个替代品。因此,如果你在优化模式下运行了一个 Python 脚本,它会生成一个 .pyo 文件,但如果以普通模式再次运行同一个脚本,它还是会生成 .pyc 文件。

拿到pyc文件后反编译回py文件

利用uncompyle6 这个Python 反编译工具它用于将 Python 字节码文件(.pyc.pyo 文件)反编译回等效的 Python 源代码。

直接在命令行中安装 uncompyle6 就可以用了

pip install uncompyle6

安装完成后,就可以使用以下命令将字节码文件反编译为 Python 源代码:

uncompyle6 sample.pyc > sample.py

或者,如果是一个 .pyo 文件,也可以用相同的方式进行反编译:

uncompyle6 sample.pyo > sample.py

解释和编译

编译器:先整体编译再执行

解释器:边解释边执行

优缺点:

编译方式:运行速度快,但是任何的改动都要整体重新编译。可以脱离编译环境运行。C语言

解释方式:运行速度慢,但是部分的改动不需要整体重新编译。不可以脱离解释器环境运行。python

说是python慢,但是也有优化速度的方式,也就是生成pyc文件的方式。参考了JAVA的字节码做法,但并不完全相同。其作用就是减少重复的解释工作。

Python 有几种不同的解释器

最常见的包括:

  1. CPython:CPython 是 Python 的官方解释器,它是使用 C 语言实现的。CPython 是最常用的 Python 解释器,它执行 Python 代码,并提供了许多标准库和扩展模块。
  2. Jython:Jython 是一个在 Java 平台上运行的 Python 解释器。它将 Python 代码编译成 Java 字节码,然后在 Java 虚拟机(JVM)上执行。Jython 可以直接调用 Java 类库,并且与 Java 代码可以无缝集成。
  3. IronPython:IronPython 是一个在 .NET 平台上运行的 Python 解释器。它将 Python 代码编译成 .NET 中间语言(CIL),然后在 .NET 运行时上执行。IronPython 可以与 .NET 框架和库进行交互,并且可以通过使用 .NET 语言扩展 Python。
  4. PyPy:PyPy 是一个用 Python 实现的解释器,它旨在提供更高的性能。PyPy 使用即时(JIT)编译技术来动态优化 Python 代码,并且通常比 CPython 提供更好的性能。

除了这些常见的解释器之外,还有一些其他的 Python 解释器,例如 MicroPython(针对嵌入式系统)、Stackless Python(用于并发编程)、Pyston(用于性能优化)、Nuitka(将 Python 代码编译成 C 或 C++)、Shed Skin(将 Python 代码转换成 C++)等。

posted on 2024-02-13 18:01  aster_ist  阅读(35)  评论(0编辑  收藏  举报

导航