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模式编译。
目录
相关文章
|
SQL 关系型数据库 数据管理
Datahub实践——Sqllineage解析Sql实现端到端数据血缘
Datahub实践——Sqllineage解析Sql实现端到端数据血缘
3116 1
|
机器学习/深度学习
电磁兼容EMC理论基础汇总
电磁兼容EMC理论基础汇总
380 0
|
28天前
|
传感器 人工智能 监控
AI视觉监控技术在仓储物流防损中的应用解析
AI视觉技术赋能仓储防损,通过边缘计算摄像头与多传感器融合,实现对叉车操作、人员行为等实时智能监控。精准识别异常行为,2秒内预警,降低盗损超40%,助力仓储物流向数字化、智能化升级。
159 8
|
3月前
|
机器学习/深度学习 算法
Proximal SFT:用PPO强化学习机制优化SFT,让大模型训练更稳定
本文介绍了一种改进的监督微调方法——Proximal Supervised Fine-Tuning (PSFT),旨在解决传统SFT易过拟合、泛化能力差及导致“熵坍塌”的问题。受PPO强化学习算法启发,PSFT通过引入参数更新的稳定性机制,防止模型在训练中变得过于确定,从而提升探索能力与后续强化学习阶段的表现。实验表明,PSFT在数学推理、模型对齐及泛化能力方面均优于传统SFT。
371 3
Proximal SFT:用PPO强化学习机制优化SFT,让大模型训练更稳定
|
缓存 JavaScript 安全
nodejs里面的http模块介绍和使用
综上所述,Node.js的http模块是构建Web服务的基础,其灵活性和强大功能,结合Node.js异步非阻塞的特点,为现代Web应用开发提供了坚实的基础。
389 62
|
6月前
|
IDE Shell 开发工具
灵码使用体验
上周使用了通义灵码三天,分享一下体验。相较于Trae、VSCode和CodeBuddy,灵码存在一些不足:响应速度较慢,生成代码效率低;汉化不够完善,菜单仍为英文;纠错能力弱,无法有效提示代码问题;Shell集成效果差,终端命令错误处理不佳;MCP工具集成不如Trae便捷。不过,灵码也有亮点:支持超长上下文输入,有助于精确开发;Qwen3推理能力强,能较好理解用户意图并编辑代码。希望后续更新能优化这些问题,提升用户体验。
829 0
|
机器学习/深度学习 人工智能 自然语言处理
"揭秘TF-IDF算法的神奇力量:如何一招制胜,让自然语言处理焕发新生?"
【8月更文挑战第20天】自然语言处理(NLP)是AI的关键领域,旨在使计算机理解人类语言。TF-IDF是一种重要的文本特征提取方法,用于衡量词汇的重要性。算法结合词频(TF)与逆文档频(IDF),强调文档独有词汇。示例代码展示了如何利用Python的scikit-learn库实现TF-IDF,并应用于文本分类任务,通过朴素贝叶斯分类器实现高效分类。此方法广泛应用于信息检索、文本挖掘等领域。
262 0
|
12月前
|
并行计算 算法 安全
面试必问的多线程优化技巧与实战
多线程编程是现代软件开发中不可或缺的一部分,特别是在处理高并发场景和优化程序性能时。作为Java开发者,掌握多线程优化技巧不仅能够提升程序的执行效率,还能在面试中脱颖而出。本文将从多线程基础、线程与进程的区别、多线程的优势出发,深入探讨如何避免死锁与竞态条件、线程间的通信机制、线程池的使用优势、线程优化算法与数据结构的选择,以及硬件加速技术。通过多个Java示例,我们将揭示这些技术的底层原理与实现方法。
771 3
|
Docker 容器
docker swarm 在服务中使用网络
【10月更文挑战第14天】
330 2
|
存储 Java API
【网安AIGC专题11.7】17ASAP如何更好地改进少样本提示:在LLMs的prompt中添加语义信息,来提高代码摘要生成+代码补全任务的性能。CodeSearchNet数据集(下)
【网安AIGC专题11.7】17ASAP如何更好地改进少样本提示:在LLMs的prompt中添加语义信息,来提高代码摘要生成+代码补全任务的性能。CodeSearchNet数据集(下)
466 0