IPython工作原理
创始人
2024-03-13 14:41:42
0

IPython工作原理

在这里插入图片描述

文章目录

    • IPython是什么?
    • IPython工作原理
      • IPython控制台
      • IPython内核
    • 实现一个简单的包装内核
    • 代码在IPython内核中的执行流程

IPython是什么?

Python最有用的功能之一就是它的交互式解释器。交互式编程允许我们非常快速地执行代码片段、测试验证想法,而无需像大多数其他编程语言那样要先创建项目、创建源文件,然后才能执行。然而,Python自带的解释器对使用交互式解释器有一定的限制。IPython的出现就是为了弥补这些不足。

IPython的目标是为交互式和探索性计算创建一个全面、完整、易用的环境。为此IPython提供了三大组件:

  • 一个加强版交互式Python命令行工具;
  • 一种解耦的双进程通信模型,允许多个客户端连接到计算内核。著名的Jupyter就是基于这个特性开发的;
  • 一个交互式并行计算架构,现在已经融合到ipyparallel 包中。

IPython是完全开源的,大家可以在这里获取到IPython的源代码。

IPython安装也非常简单,只需要执行下面的命令即可:

$ pip install ipython

当然我们也可以只安装IPython内核,供Jupyter使用:

$ python -m pip install ipykernel

IPython工作原理

IPython控制台

当我们在控制台输入ipython命令后,我们就进入了IPython原生的控制台界面,你将看到类似下面的控制台交互界面:

在这里插入图片描述

此时我们就可以直接在控制台中输入要运行的Python代码。

IPython控制台的核心引擎其实非常简单,类似下面的代码功能:

while True:code = input(">>> ")exec(code)

当然,IPython的实际代码要不这复杂,以为它还会处理多行代码、代码补全、历史记录、魔法命令等功能。但核心思路是一样的,都是提示用户输入代码,然后再同一进程执行所输入的代码。这其实就是REPL要做的事。

REPL是Read-Eval-Print-Loop的首字母,它是交互式编程的基础模型。

IPython内核

除了IPython自带的控制台,IPython还有很多第三方交互界面和工具,其中最著名的当属Jupyter Notebook。这些第三方界面要想驱动IPython工作就需要使用IPython内核。IPython内核运行在一个单独的进程中,负责运行用户的代码。前端通过ZeroMQ将消息以JSON格式发送给IPython内核,并通过 ZeroMQ socket与IPython内核保持通信,通信协议可以参考Jupyter消息传递。

IPython内核的核心执行机制与IPython终端共享:

在这里插入图片描述

图1. 执行器共享

一个内核进程可以同时连接多个前端,这是多个前端访问到的是同样的内存数据。

将内核和前端解耦的优点非常明显:

  1. 可以基于同一内核轻松开发不同的前端;
  2. 可以为同一前端提供其他语言的内核,Jupyter支持多种语言就是这一设计的直接受益者。

目前有两种方式开发其他语言的内核:

  • 包装IPython内核,重用IPython的通信机制,只实现核心执行部分;
  • 原生内核,用目标语言重新实现执行部分和通信部分。

在这里插入图片描述

图2. 包装内核 vs 原生内核

很明显,包装内核实现起来会更简单,因此如果目标语言与Python交互良好(比如octave_kernel),或者目标语言不方便实现通信的(比如bash_kernel),用包装内核实现会简单快速很多。

实现一个简单的包装内核

用包装IPython内核的方式实现新内核非常简单,关键步骤就是声明一个类,继承自ipykernel.kernelbase.Kernel,然后实现相应的属性和方法。

其中必须提供的属性如下:

属性名类型说明
implementationstr内核名称
implementation_versionstr内核版本
bannerstr控制台启动时输入提示符出现前展示给用户的信息
languagestr内核语言
language_versionstr内核语言版本
language_infodict内核语言信息,字典类型,需要包括mimetype 目标语言的mimetype 和 file_extension 目标语言源代码文件后缀名称。

必须实现的方法只有一个 do_execute(code, silent, store_history=True, user_expressions=None, allow_stdin=False),用于执行用户代码。参数含义如下:

参数名类型说明
codestring要执行的代码
silentbool是否显示输出
store_historybool是否将代码存入历史库中并增加执行计数。如果silent为True,则store_history默认为False。
user_expressionsdict代码执行完后,对要求值的表达式命名
allow_stdinbool前端是否可以根据请求提供输入

do_execute方法返回一个字典,其中需要包含的字段在 Execution results 中有详细的介绍。要显示输出,可以使用send_response()发送消息。有关各消息类型的详细信息,请参阅IPython中的消息传递。

内核写好后可以用IPKernelApplaunch_instance方法启动内核,传入我们自定义的内核类即可:

if __name__ == '__main__':from ipykernel.kernelapp import IPKernelAppIPKernelApp.launch_instance(kernel_class=MyKernel)

下面是一个完整的包装内核的实现例子:

from ipykernel.kernelbase import Kernelclass EchoKernel(Kernel):implementation = 'Echo'implementation_version = '1.0'language = 'no-op'language_version = '0.1'language_info = {'mimetype': 'text/plain'}banner = "Echo kernel - as useful as a parrot"def do_execute(self, code, silent, store_history=True, user_expressions=None,allow_stdin=False):if not silent:stream_content = {'name': 'stdout', 'text': code}self.send_response(self.iopub_socket, 'stream', stream_content)return {'status': 'ok',# The base class increments the execution count'execution_count': self.execution_count,'payload': [],'user_expressions': {},}if __name__ == '__main__':from ipykernel.kernelapp import IPKernelAppIPKernelApp.launch_instance(kernel_class=EchoKernel)

上面的代码简单地将用户输入直接输出出来。

除此之外,还需要提供一个内核描述文件用于指定如何运行内核

{"argv":["python","-m","echokernel", "-f", "{connection_file}"],"display_name":"Echo"
}

代码在IPython内核中的执行流程

当IPython内核收到代码执行请求时,会按照一下步骤处理:

  1. 触发pre_execute事件;
  2. 如果silence不为True,则触发pre_run_cell事件;
  3. 执行run_cell方法,run_cell方法会对传入代码进行预处理,然后编译执行它。这是整个执行流程的主体,后面会详细讲解这个过程;
  4. 如果执行成功,定义在user_expressions中的表达式会触发求值,这样做的好处是可以确保表达式中的任何错误都不会影响主代码的执行;
  5. 触发post_execute事件;
  6. 如果silence不为True,则触发post_run_cell事件。

其中代码的执行部分又会分2个阶段:首先IPython.core.inputtransformer2会将代码块中的%magic!system命令展开转换成python代码;然后用Python的 compile()方法编译并执行代码。

在这里插入图片描述

图3. IPython代码执行流程

Python的compile()方法提供了mode参数可以选择编译方式,有三种方式可选:

single

对于单一交互语句有效(尽管代码可以包含多行,例如for循环)。在这种模式下编译时,生成的字节码包含特殊指令,这些指令会触发对代码块中返回的任何表达式调用sys.displayhook()。这意味着一条语句实际上可以产生多次sys.displayhook()调用,例如下面的代码:

for i in range(10):i**2

表达式包含在循环中,且表达式的值没有赋给任何变量,此时将调用10次sys.displayhook()

exec

编译任意数量的代码,pyhton模块就是用这种方式编译的。这种编译模式不会调用sys.displayhook()

eval

执行单一表达式,不会调用sys.displayhook()

传入的源代码会被划分为若干独立的代码块,每个块确保可以使用single模式编译。如果只有一个代码块,那就用single模式编译;如果有多个代码块,则按照如下逻辑执行:

  • 如果最后一个代码块不超过两行,则最后一个代码块之前的代码用exec模式编译,只有最后一个代码块用single模式编译。这样可以很容易地在末尾输入简单表达式查看计算结果。
  • 如果最优一个代码块大于两行,则全部用exec模式编译。

相关内容

热门资讯

最高检:立足检察公益诉讼职能定... 12月25日,全总召开2025年劳动法律监督“一函两书”典型案例新闻发布会。会上,最高人民检察院公益...
筑牢低空安全根基:无人机法规教... 低空经济作为国家战略性新兴产业,正以技术创新为引擎、应用场景为载体,加快重塑航空产业格局,成为推动经...
规范幼儿园收费 确保普惠政策精... 近日,国家发展改革委、教育部、财政部联合印发《关于完善幼儿园收费政策的通知》,明确幼儿园可收取保育教...
三部门发文:完善幼儿园收费政策 12月23日,由国家发展改革委、教育部、财政部联合印发的《关于完善幼儿园收费政策的通知》(以下简称《...
三部门联合发布2025年劳动法... 中新网12月25日电 据最高人民检察院微信公众号25日消息,中华全国总工会、最高人民法院、最高人民检...
金融领域“黑灰产”违法犯罪集群... 海报新闻记者 孙佃潇 北京报道 12月25日,公安部召开新闻发布会,会上通报公安部和国家金融监督管理...
最高法、全国妇联、司法部联合发... 人民网北京12月25日电 (薄晨棣、高清扬)据最高人民法院消息,24日,最高法与全国妇联、司法部联合...
200余个金融领域犯罪团伙被警... 文 | CFN 大河 图 | 微摄 2025年12月25日,公安部在京召开专题新闻发布会,通报了一场...
锐评丨套牌电动车,闯了法律红灯 一辆在昌平被烧毁的电动车,却在朝阳出现交通违法记录;车牌早已注销,仍收到交通违法提示短信;人在国外度...