Python解释器小小的解读

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/lzy98/article/details/88819406

title: Python解释器
copyright: true
top: 0
date: 2018-11-17 10:43:22
tags: PyCodeObject
categories: Python进阶笔记
permalink:
password:
keywords: python解释器
description: 想知道Java的JVM为啥能叫虚拟机但是python.exe只能叫解释器吗?不服?

所爱隔山海,山海不可平。

代码运行

不管是什么语言(当然是除了vbs,shell这种),想要执行的话,必须先变成成机器可以运行的字节码,有两种方式可以这么做:

  1. 编译,像C一样,字节编译成机器语言,效率高
  2. 解释,省去了编译的过程,一行一行解释直接运行

编译的意思就是把所有的代码先全部变成机器可以执行的机器语言,以前我使用nuitka打包python的时候,就是使用gcc编译,一般来说编译很吃cpu,等全部编译好了就可以字节刷刷刷的运行了。

解释就是上面说的一样,先把第一行代码解释成机器语言,然后执行,然后第二行解释成机器语言,然后执行…这样免去了编译的过程,但是会比较慢(这就是为啥在应用程序上python慢的原因)

python 代码运行

简诉

python就是属于后者,使用直接解释成机器语言。既然是先解释然后执行,Python中使用的是PyInterpreterState和PyThreadState来表示的,解释器启动后会执行一些Initsettings,最后进入PyEvalFrame函数,它的作用就是不断的读取PyCodeObject对象(这段话是我复制过来的,作用就是不断翻译)

那么解释后的文件就是你经常看到的pyc文件,字节码在解释器中的形式为PyCodeObject,在磁盘中表示为.pyc文件。

运行过程

  1. python.exe解释器先把.py文件中的code编译为字节码。
  2. 字节码文件再被python.exe执行

这个时候解释器接收到的字节码文件都是PyCodeObject为单位的,一个一个执行。什么是PyCodeObject,举个例子好比,你有一个孩子(对象),你的孩子的乐园就是家+你家花园(作用域),那么这就是一个PyCodeObject对象,解释器就以这样的对象为单位来划分。但是如果有一段代码表示的是:你的孩子去别人家的后花园玩耍,那么一个PyCodeObject就不够描述这一件事情了,所以就必须在怼一个PyCodeObject出来来描述这样的代码,所以就成了翻译两个PyCodeObject对对象。 PyCodeObject中装的什么?装的不是空气,也不是充气的,装的是所有的静态变量的信息和字节码,不包括环境(PyFrameObject),我之前的例子只是想说明白PyCodeObject划分的原则,就是一个对象不够描述一件事,所以就会划分另一个对象。这些PyCodeObject,Base在PyFrameObject上运行,每一个PyFrameObject对象中都维护了一个PyCodeObject表示每一个PyFrameObject的动态内存空间和源码的一段Code相对应。所以解释器会根据上下文(PyFrameObject)来执行。

PyCodeObject实际上是,python编译器在内存中编译成的结果,pyc文件可以看成是PyCodeObject在磁盘上的持久化,没有出pyc文件的原因是因为python解释器认为重用的代码块才值得搞pyc执行前在内存中看看是否在PyCodeObject中,如果在就不需要读取磁盘本地的pyc文件了。

Code Block

Python编译器在对Python源码进行编译的时候,对代码中的一个Code Block,会创建一个PyCodeObject对象与这段代码对应。
如何确定多少代码算一个Code Block?
Python中确定Code Block的规则:当进入一个新的名字空间或作用域时,就算进入了一个新的Code Block了。
即:一个名字空间对应一个Code Block,它会对应一个PyCodeObject。
现在暂且认为名字空间就是符号的上下文环境,是名字到对象的映射。
在Python中,类、函数和module都对应着一个独立的名字空间,因此都会对应一个PyCodeObject对象。

Python内存管理

为了提高效率,python引入了内存池机制,将不用的存放在内存池而不是返回给OS,Pymalloc用于管理小块的内存申请和释放(对象小于256k),大于256的大块内存由底层的malloc、free等函数内存管理和分配的函数进行操作

JVM虚拟机

首先Java是静态语言内部的编译器进行翻译JavaCode为字节码文件,然后由执行器执行字节码文件。而Python的编译器,美其名曰打工皇帝,身兼数职,一遍翻译一边执行,这叫动态语言,并且居然翻译成字节码Python的字节码都比Java的短,虽然是打工皇帝但是一个人的力量还是不如JVM分工的快。不过他俩都是跨平台的就是因为虚拟机的机制,就像平头插座插不了三角头,但是装个转换器啦。使用转换器的原因就是要提供一个和宿主平台无关的编译环境,所以java不是解释语言也不是编译语言,它属于二合一,但是python作为动态语言也提供了各种优化机制保留.pyc文件等等,所以现在编译语言跟解释语言的界限越来越不明显,这么来看她确实该叫解释器,翻译加运行,好比我给你说这句话不是说而已还要你懂

Cpython Jpython PYPY

cpython

目前的主流python解释器是使用C语言实现的,你下载的python.msi其实就是用C语言实现Pyhon,也叫Cpython,CPython是官方版本加上对于C/Python API的全面支持,很多内置库和第三方库都是使用cpython写的。

python代码运行的时候CPython编译你的Python源代码,生成字节码,字节码随后在CPython虚拟机上执行。

但是cpython存在两个缺点:

  1. gil全局解释器锁,他没办法像java和c一样把多线程映射到多个CPU上执行,在多线程上表现不太好,在消耗cpu操作上使用多进程优于多线程,它能够谨慎控制线程的执行。无论有多少的线程,解释器每次只能执行一个操作。
  2. 由于Python是动态编译的语言,和C/C++、Java或者Kotlin等静态语言不同,它是在运行时一句一句代码地边编译边执行的,而Java是提前将高级语言编译成了JVM字节码,运行时直接通过JVM和机器打交道,所以进行密集计算时运行速度远高于动态编译语言。

另外Cython和cpython不一样,cython是一个Python的超集,能够调用C语言的函数,允许你为你的Python代码写C扩展,为你的Python代码加入静态类型,运行编译并达到接近C语言的性能。

pypy

pypy是用Python自身实现的解释器,他使用Rpython(python的一个子集)实现,他与cpython的区别在于Pypy集成了JIT(即时编译)技术,据说运行速度是cpython的6倍以上。

另外python大部分代码可以直接使用pypy运行,但是有些第三方库使用cpython写的,所以会报错。

本地机器码的速度比字节码的速度快很多。那么,如果我们能将一些字节码直接编译成本地机器码再去运行它会怎样呢?我们必须花费一些代价(比如时间)在编译字节码到本地机器码上,如果最终的运行时间更快,那么这个代价就是值得的。这就是JIT编译器的动机,一种混合了解释器和编译器好处的技术。简单来讲,JIT就是想通过编译技术提升脚本解释器系统的速度。

总而言之,JIT技术混合了动态编译和静态编译的特性,仍然是一句一句编译源代码,但是会将翻译过的代码缓存起来以降低性能损耗。相对于静态编译代码,即时编译的代码可以处理延迟绑定并增强安全性。

pypy的缺点:

  1. Pypy无法支持官方的C/Python API,导致无法使用例如Numpy,Scipy等重要的第三方库。

jython

jython使用java实现的虚拟机,采用了JVM的实现。CPython生成在CPython虚拟机上运行的字节码,而Jython生成在JVM上运行的java字节码(这同编译Java程序生成java字节码的过程是一样的)。

参考链接

参考链接

参考链接

参考链接

展开阅读全文

没有更多推荐了,返回首页