IPython工作原理

简介: IPython的目标是为交互式和探索性计算创建一个全面、完整、易用的环境。本文带大家深入到IPython内部,看一下IPython的工作原理。

IPython工作原理

ipython.jpg

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原生的控制台界面,你将看到类似下面的控制台交互界面:

ipython_01.png

此时我们就可以直接在控制台中输入要运行的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终端共享:

ipython_02.png

图1. 执行器共享

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

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

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

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

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

ipython_03.png

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

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

实现一个简单的包装内核

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

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

属性名 类型 说明
implementation str 内核名称
implementation_version str 内核版本
banner str 控制台启动时输入提示符出现前展示给用户的信息
language str 内核语言
language_version str 内核语言版本
language_info dict 内核语言信息,字典类型,需要包括mimetype 目标语言的mimetype 和 file_extension 目标语言源代码文件后缀名称。

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

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

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

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

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

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

from ipykernel.kernelbase import Kernel

class 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 IPKernelApp
    IPKernelApp.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()方法编译并执行代码。

ipython_04.png

图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模式编译。
目录
相关文章
|
3月前
|
传感器 JSON 监控
python中psutil模块的使用详解(python3经典编程案例)
这篇文章介绍了如何使用Python的`pyinstaller`库打包应用程序,并提供了详细的打包步骤和参数说明。
71 7
|
4月前
|
安全 Linux Python
Python强大的信号库-blinker 入门教程
Python强大的信号库-blinker 入门教程
|
6月前
|
Unix Shell 数据处理
怎样使用Cython提升Python的性能
**Cython是Python的性能增强工具,用于提升Python代码的速度。它允许声明变量类型并调用C库。安装Cython使用`pip install Cython`。Cython语法接近Python,但通过类型声明优化性能。编译Cython代码需创建setup.py文件,然后运行`python setup.py build_ext --inplace`。通过Cython,可以直接优化Python代码和调用C函数,平衡速度与灵活性。**
119 2
|
数据采集 并行计算 数据可视化
IPython的安装和基本使用
IPython的安装和基本使用
|
Python
Python Tkinter 模块简要介绍
Python Tkinter 模块简要介绍
174 0
|
Python
在notebook中运行cython和Python效率对比
在notebook中运行cython和Python效率对比
101 0
在notebook中运行cython和Python效率对比
|
数据安全/隐私保护 C++ iOS开发
002 与 Python3 交互
本文主要讲解如何与 Python 进行交互并编写第一个 Python 程序。
96 0
002 与 Python3 交互
|
Linux Python Windows
jupyter中ipython的基本使用方法,帮助你更快速高效的学习
jupyter中ipython的基本使用方法,帮助你更快速高效的学习
|
C++ Python
1_python高阶_进程
python高阶_进程
130 0
1_python高阶_进程
|
Linux Python Windows
学习Python的运行方式
当你下载安装好Python需要进行环境搭建,环境变量配置等一系列准备工作,做好之后就开始调试运行,Python的运行也有几种方式的。例如:交互模式、命令行脚本、集成开发环境。
146 0
学习Python的运行方式