当前位置: 首页 > news >正文

CppCon 2014 学习:Lightning Talk: Writing a Python Interpreter for Fun and Profit

Lightning Talk: Writing a Python Interpreter for Fun and Profit

这段内容在讲的是 Python 的执行模型,尤其是 CPython 的工作流程。下面是逐步解析:

Python 是动态类型语言(Dynamically typed)

  • 变量类型在运行时决定。
    x = 42       # x 是 int
    x = "hello"  # x 变成 str
    

Python 是解释型语言(Interpreted)

  • Python 代码不是直接编译成机器码,而是先编译成字节码(bytecode),然后由解释器执行。

CPython 是官方实现

  • CPython 是 python.org 提供的默认实现。
  • C语言 编写。
  • .py 文件编译成字节码(.pyc),并由 CPython 解释器执行。

示例代码

Python 代码:

def func(a, b):c = a + bprint(c)return c

编译为字节码(Byte-code)

CPython 编译器会把上面的函数编译成类似这样的虚拟机指令(伪字节码):

PUSH a
PUSH b
BINARY_ADD       # 相当于 c = a + b
POP c
PUSH c
PRINT_ITEM       # 打印 c
PRINT_NEWLINE
PUSH c
RETURN_VALUE     # 返回 c

解释说明:

  • PUSH:把变量压入栈。
  • BINARY_ADD:执行加法操作(从栈中弹出两个值,相加后再压入栈)。
  • POP c:把加法结果赋值给 c
  • PRINT_ITEM + PRINT_NEWLINE:打印变量。
  • RETURN_VALUE:函数返回。

CPython 执行过程

  1. Python 源码被编译成字节码。
  2. 字节码被 CPython 的 虚拟机解释器执行(使用 eval loop)。
  3. 虚拟机使用 栈(stack-based VM) 执行操作。

总结

概念说明
动态类型变量类型运行时决定
解释型语言运行时解释执行,而不是编译成本地机器码
CPython官方 Python 实现,用 C 写的
字节码Python 编译后的一种中间表示,供解释器执行
虚拟机执行模型使用栈结构逐步执行指令

你这段内容是在描述 Python 的对象模型、引用计数机制,以及如何通过嵌入 Python(如在 C++ 中)执行 Python 代码。我们来逐步解释:

一切皆对象(Everything is an object)

在 Python 中,所有东西都是对象

类型说明
int整数对象
str字符串对象
list列表对象
dict字典对象
bool布尔对象
func函数对象
method方法对象
class类对象
module模块对象
无论是什么类型,在内存中都表现为一个对象结构,具有类型信息和引用计数

引用计数(Reference Counting)

Python(CPython 实现)使用引用计数作为垃圾回收机制的一部分:

a = "Hello!"
b = a

解释:

  • "Hello!" 是一个字符串对象。
  • a 引用它后,引用计数 = 1。
  • b = a 后,引用计数变成 2。
  • 这就像 C++ 中的 std::shared_ptr
    示意图:
        a ─┐│▼+---------------+|  "Hello!"     |  <-- 对象|  count = 2    |  <-- 引用计数|  type = str   |+---------------+▲│b ─┘

当引用计数为 0,CPython 会自动销毁该对象。

嵌入 Python:用 C++ 调用 Python

你下面的部分在描述 C++ 嵌入 Python 的场景(比如通过 CPython API):

// 假设是伪代码或嵌入式解释器伪逻辑
void test() {Ref module = vm.import(R"**(def pyfunc(a, b):return a + b)**");// MyInterpreter 执行 pyfunc
}

解释:

  • 使用 vm.import() 可能是某个虚拟机对象载入 Python 模块。
  • 多用于 C++ 嵌入式解释器或游戏引擎中嵌入脚本语言。
  • R"**( ... )**" 是原始字符串字面量,表示传入的是多行 Python 脚本。
    这种嵌入方式常用于:
  • 嵌入 Python 脚本到 C++ 游戏/系统。
  • 提供可扩展插件系统。
  • 自动化测试、动态执行配置脚本等。

总结图解

Python 对象模型:
+---------------------+
| PyObject (头部)     |
|   - refcount        | <-- 引用计数
|   - type* (PyType)  | <-- 类型信息
+---------------------+
|       数据          |
+---------------------+

总结要点

关键点说明
一切都是对象所有变量/类型都是对象
引用计数用来自动回收对象
引用共享多个变量引用同一个对象
嵌入式解释器C++ 可以调用/执行 Python 代码

描述如何在 C++ 中嵌入 Python 并调用 Python 函数。虽然是伪代码(不是标准 CPython API),但表达的概念非常清楚。下面是逐行解释和你应该理解的核心概念:

场景:C++ 调用 Python 代码

你想实现:

  • 在 C++ 中执行 Python 脚本(定义函数)。
  • 调用 Python 中定义的函数 pyfunc(a, b)
  • 获取返回值并转换为 C++ 类型。

分析你的伪代码

void test() 
{// 虚构的解释器对象Interpreter vm;// 导入一个 Python 模块(通过原始字符串定义)Ref module = vm.import(R"**(def pyfunc(a, b):return a + b)**");// 调用 Python 中的 pyfunc 函数Ref resObj = module.call("pyfunc", 1, 2);// 提取 Python 对象为 C++ int 类型int result = extract<int>(resObj);
}

背后理解

1. vm.import(...)

  • 表示将一段 Python 脚本动态“导入”或加载为模块。
  • R"**(...)" 是 C++ 的原始字符串字面量,表示不会转义,适合多行代码。
    等价于把 Python 代码:
def pyfunc(a, b):return a + b

注入到解释器中。

2. module.call("pyfunc", 1, 2);

  • 动态调用 Python 函数 pyfunc(1, 2)
  • 返回值是 Python 对象(封装为 Ref 类型,可能是某种智能指针或包装类)。

3. extract<int>(resObj);

  • 把 Python 对象转成 C++ 中的 int 类型。
  • 相当于你得到了 3,因为 1 + 2 = 3

对应的真实 CPython API 示例

你上面的伪代码用的是某个抽象封装(比如 boost::python、pybind11、或自定义解释器)。如果用 原生 CPython API 来做相同的事情,代码类似这样:

#include <Python.h>
void test() {Py_Initialize();const char* code = R"(
def pyfunc(a, b):return a + b
)";PyRun_SimpleString(code);PyObject* main = PyImport_AddModule("__main__");PyObject* global_dict = PyModule_GetDict(main);PyObject* func = PyDict_GetItemString(global_dict, "pyfunc");if (func && PyCallable_Check(func)) {PyObject* args = PyTuple_Pack(2, PyLong_FromLong(1), PyLong_FromLong(2));PyObject* result = PyObject_CallObject(func, args);long c_result = PyLong_AsLong(result);printf("Result: %ld\n", c_result);Py_DECREF(args);Py_DECREF(result);}Py_Finalize();
}

总结理解点

关键词含义
R"**(...)"原始字符串,方便写多行 Python 代码
vm.import(...)类似于 Python 模块导入,但用于动态注入脚本
.call(...)调用 Python 函数并返回 Python 对象
extract<T>把 Python 对象转换为 C++ 类型
CPython API可以直接用 Py_Initialize, PyRun_SimpleString, PyObject* 调用

这段代码展示了 在 C++ 中注册 C++ 函数给 Python 使用,并在 Python 中调用该函数,这是嵌入 Python 的一个高级特性,常用于游戏引擎、嵌入式脚本系统等。下面是详细解释:

代码理解

int cxxfunc(const string& msg) {cout << msg;return 42;
}

这是一个普通的 C++ 函数,接受一个字符串,打印出来,返回 42。

void test() 
{Interpreter vm;  // 假设你有一个内嵌的 Python 虚拟机对象// 向 Python 注册 C++ 函数vm.builtins.def("cxxfunc", cxxfunc);// 导入/执行 Python 脚本Ref module = vm.import(R"**(
def pyfunc(a, b):cxxfunc("Hello!")return a + b
)**");// 调用 Python 函数Ref resObj = module.call("pyfunc", 1, 2);
}

总结这个过程

步骤描述
1. 定义 C++ 函数int cxxfunc(const string&),将来让 Python 调用它。
2. 注册到 Pythonvm.builtins.def("cxxfunc", cxxfunc); 这一步把 C++ 函数注册为 Python 可用的全局函数。
3. 编写 Python 脚本Python 调用了 cxxfunc("Hello!")
4. 调用 Python 函数C++ 端用 .call("pyfunc", 1, 2) 执行 pyfunc

背后机制(类比 pybind11 或 Boost.Python)

你用的 vm.builtins.def(...) 类似于 pybind11 中的:

py::module_::import("__main__").attr("cxxfunc") = py::cpp_function(cxxfunc);

实际效果

运行这段代码时:

  1. 控制台会打印:
    Hello!
    
  2. Python 函数 pyfunc(1, 2) 会返回 3,即 a + b
  3. cxxfunc 被 Python 调用时会输出字符串并返回 42(虽然这个返回值在 Python 中没用到)。

应用场景

这种嵌入式调用方式可用于:

场景示例
游戏脚本Python 调用 playSound("boom.wav") 实际执行 C++ 函数
工业控制Python 写控制逻辑,但实际操作硬件是底层 C++
数据处理用 Python 组织逻辑流程,用 C++ 实现高性能计算

结论

你已经实现了:

  • 从 C++ 向 Python 注册函数;
  • Python 脚本中调用 C++ 函数;
  • Python 和 C++ 之间来回传递数据。
    这是 C++ 与 Python 脚本系统集成的核心能力

你这段代码展示了如何在 C++ 中将一个 C++ 类(含成员函数)暴露给 Python 解释器,并让 Python 调用它的成员函数。这是一种高级的 C++ ↔ Python 绑定机制,适合嵌入式脚本、游戏开发、插件系统等场景。

下面逐步解析并翻译这段代码的意义。

逐行分析

struct Thing {void action(int a, int b);
};

定义了一个简单的 C++ 结构体 Thing,里面有一个成员函数 action

void test() {Interpreter vm;  // 创建解释器对象

假设 Interpreter 是你封装的 Python 虚拟机对象。

    Ref m = vm.builtins;

获取内置模块,用于注册类和函数。

    Ref c = m.class_<Thing>("Thing").def("action", &Thing::action);

向 Python 暴露 C++ 的类 Thing,并绑定其成员函数 action 为 Python 可调用函数。
这段伪代码类似于 PyBind11 中的:

py::class_<Thing>(m, "Thing").def("action", &Thing::action);
    Thing t;Ref wrap_t = c.wrapInstance(t);wrap_t.call("action", 1, 2);
}
  1. 创建一个 C++ 的 Thing 实例。
  2. wrapInstance 把这个实例转换成 Python 端可识别的对象(通常是某种封装指针)。
  3. 通过 Python 的方式调用 action(1, 2) —— 实际调用的是 t.action(1, 2)

背后机制

操作作用
.class_<Thing>("Thing")把 C++ 类注册为 Python 类。
.def("action", &Thing::action)绑定成员函数到 Python。
wrapInstance(t)把 C++ 实例包成 Python 对象。
call("action", ...)在 Python 层调用方法(但底层执行 C++ 函数)。

示例效果

Python 中表现为:

obj = Thing()
obj.action(1, 2)

这调用的是你写的 C++ Thing::action(1, 2)

总结

你这段代码描述了以下概念:

概念描述
C++ 类暴露给 Python允许 Python 实例化 C++ 类,并调用其成员函数
Python 调用成员函数Python 的 .call(...) 最终会回调到 C++ 成员函数
脚本与底层集成非常适用于游戏开发、嵌入式设备等对性能和可扩展性都有要求的项目

你这段代码展示了一个 C++ 可变参数模板 + 类型擦除 + 参数打包 + 调用解释器函数的机制,常用于实现 通用的脚本函数调用接口,比如将 C++ 参数传给一个 Python 函数。

逐段理解

1. 基础 unpack 函数(递归终止)

void unpack(vector<Ref>& v) {}

当参数包为空时,递归终止。什么都不做。

2. 参数展开 + 转换 + 收集

template<typename T, typename ...Args>
void unpack(vector<Ref>& v, const T&& a, const Args&&... args) {v.push_back(objectFromT(std::forward<T>(a)));unpack(v, std::forward<Args>(args)...);
}

这段是参数展开递归模板的核心:

步骤描述
1把当前参数 a 转换成一个 Ref(可能是 Python 对象)
2objectFromT(...) 是类型擦除函数:将任意类型 T 转为统一的 Ref 类型(用于解释器调用)
3Ref 放入 vector<Ref> 容器中
4递归处理剩下的参数

3. 最终接口:call(...)

template<typename ...Args>
Ref call(const string& name, const Args&&... args) {vector<Ref> argv;unpack(argv, std::forward<Args>(args)...);runCode(lookup(name), argv);
}

这是你提供给用户调用的通用接口:

步骤含义
name要调用的函数名(通常是 Python 脚本中的函数)
args...任意数量的 C++ 参数
unpack(...)把 C++ 参数全部转换为 Ref 类型并放入 argv
lookup(name)查找解释器中函数的对象(比如 Python 中的 pyfunc
runCode(...)用解释器执行函数并传入参数

背后思想:类型擦除 + 参数打包

  • objectFromT(T):将 int, string, double, Thing* 等全部转换为统一 Ref 类型。
  • vector<Ref>:作为解释器通用参数容器。
  • unpack(...):实现递归参数收集。
  • call(...):简洁通用的接口隐藏了复杂的参数转换和打包过程。

举个例子

你这样调用:

call("pyfunc", 1, 3.14, "hello", someObject);

实际做了什么:

  1. objectFromT(1)Ref(int)
  2. objectFromT(3.14)Ref(double)
  3. objectFromT("hello")Ref(str)
  4. objectFromT(someObject)Ref(object)
  5. 调用解释器中函数 pyfunc,传入参数 [1, 3.14, "hello", someObject]

总结

你实现了一个 可扩展、类型安全、语法简洁的 C++ 调用解释器函数接口。它支持:

  • 任意参数数量(可变参数模板)
  • 类型转换封装(objectFromT
  • 与 Python / Lua / JS 等解释器无缝连接(Ref, runCode, lookup
    这正是许多嵌入式脚本系统的底层核心机制。

这段代码是一个虚拟机(VM)执行字节码的解释器主循环的简化版,核心思想和 Python 的 CPython 虚拟机非常相似。下面为你逐行解析其含义,并总结设计背后的思想。

你的函数定义

Ref runCode(ByteCode code, vector<Ref> args) {Frame f(args);int pc = 0;
  • runCode(...): 执行解释器中的函数体(字节码)。
  • code: 表示某个函数或脚本的字节码序列(每 2 字节一个指令 + 参数)。
  • args: 调用时传入的参数。
  • Frame f(args): 新建一个栈帧,保存局部变量、栈等上下文。
  • pc: 程序计数器(Program Counter),指向当前字节码的偏移量。

主循环:解释执行字节码

while (true) {byte opcode = code[pc];byte param = code[pc + 1];

一次取出一个指令(2 字节),其中:

  • opcode:操作码,表示要做什么(如 PUSH、POP、JUMP 等)
  • param:参数(比如变量索引、跳转地址、参数数量等)

指令解释

switch (opcode) {

你实现了一个字节码解释器的指令集,每种操作码做对应的行为。

PUSH
case PUSH:f.stack.push(f.vars[param]);break;
  • 从局部变量数组中取出变量 vars[param]
  • 推入运行时栈 stack
POP
case POP:f.vars[param] = f.stack.pop();break;
  • 从栈中弹出值
  • 存入变量槽 vars[param]
JUMP_TO
case JUMP_TO:pc = param;continue;
  • 改变 pc,跳转到指定位置(param 是偏移量)
  • 注意 continue 跳过了 pc += 2,不会前进
CALL_FUNCTION
case CALL_FUNCTION:runCode(pop(), popArgs(param));break;
  • pop():从栈中弹出被调用函数(或字节码对象)
  • popArgs(param):从栈中弹出 param 个参数(调用参数)
  • 递归调用 runCode(...)
RETURN_VALUE
case RETURN_VALUE:return f.stack.pop();
  • 函数结束,返回结果(从栈顶取出)

👣 继续前进

pc += 2;

默认每条指令占 2 字节,所以每轮前进 2 字节。

嵌套调用演示

runCode()
runCode()
runCode()

这说明你支持嵌套函数调用,每次 CALL_FUNCTION 会进入一个新的 Frame 并递归调用 runCode(...),这就是解释器的函数调用栈机制。

你实现的核心概念:

概念描述
字节码虚拟机用程序计数器逐字节解释执行指令
栈帧 Frame包含局部变量数组 vars、运行栈 stack 等上下文
操作码 opcode表示操作类型,例如 PUSH、CALL、RETURN 等
参数 param当前指令的辅助参数
函数调用通过 CALL_FUNCTION 实现函数嵌套和参数传递
返回值从当前栈帧返回计算结果

总结

你实现的是一个简化的 C++ 字节码解释器运行时核心,具备如下特性:

  • 字节码驱动控制流程(如跳转、调用)
  • 支持栈式计算模型
  • 局部变量访问
  • 支持函数调用、返回机制
  • 非常类似 Python、Lua 的 VM 设计

代码片段展示了一个简化的 引用计数型对象系统,类似 Python 或 JavaScript 的对象模型。下面是对各个结构和行为的详细中文解释,帮你完全理解这段代码的设计思想和用途。

整体设计目标

你在用类似 Python 的方式构建一个 统一对象类型系统,其中:

  • 所有值(int、string、list、dict)都继承自 Object
  • 所有对象都通过 Ref 来间接引用(类似智能指针)。
  • 每个对象通过 m_refcount 管理生命周期(引用计数)。

各部分详细解析

Object

struct Object {int m_refcount;
};
  • 所有对象的基类。
  • 包含 m_refcount,用于引用计数,控制对象何时析构。

基本对象类型(派生类)

Str
struct Str : Object {std::string v;
};
  • 表示字符串对象。
  • 成员 v 是具体的字符串值。
Int
struct Int : Object {int v;
};
  • 表示整数对象。
  • v 是其数值。
List
struct List : Object {std::vector<Ref> v;
};
  • 表示列表对象。
  • 存储一个 Ref 类型的向量,即引用了其它对象。
Dict
struct Dict : Object {std::map<int, Ref> v;
};
  • 表示字典(键值对),这里的键是 int 类型,值是 Ref
  • 可类比 Python 的 dict

📎 Ref:对象引用

struct Ref {Object* m_ptr;
};
  • Ref 是一个轻量对象引用类型,等价于智能指针。
  • 指向真实对象(继承自 Object 的任何派生类)。

应该配合引用计数增加/减少使用,例如类似 boost::intrusive_ptr 或手动实现 retain/release。

类型转换与提取

template<typename T>
T extract(Ref ref);

这是一个泛型模板函数,目的是从 Ref 中提取具体类型值。

特化版本(提取 std::string
template<>
std::string extract(Ref ref) {return dynamic_cast<Str*>(ref.m_ptr)->v;
}
  • ref.m_ptr 强制转换为 Str*
  • 提取其 v 字段(string 值)

如果传入的不是 Str 类型,dynamic_cast 会返回 nullptr,可能导致 dereference 崩溃,建议做安全检查或使用 static_cast(在你确认类型无误时)。

总结:你实现了什么?

你正在构建一个面向解释器的对象系统基础设施,具有以下特征:

特征说明
面向对象抽象所有对象都继承自基类 Object
动态类型系统类型信息通过 dynamic_cast 动态识别
引用计数管理通过 m_refcount 管理内存
引用封装统一使用 Ref 来间接操作所有对象
类型提取机制通过 extract<>()Ref 转换为真实类型值

建议改进(可选)

  1. 管理引用计数
    • 类似 boost::intrusive_ptr<Object> 自动管理 m_refcount
    • 或定义 retain() / release() 手动管理
  2. 类型安全性提升
    • 增加类型检查:if (auto s = dynamic_cast<Str*>(ref.m_ptr)) { ... }
    • 或使用 std::variant / typeid 做类型判断
  3. 抽象提取方式
    • 支持更多类型:如 intListDict
    • 或改用 visitor 模式处理对象
      如果你继续开发解释器、虚拟机、或动态语言运行时,这样的对象系统是非常常见且实用的设计基础。如果你有后续如调用机制、垃圾回收、

相关文章:

  • 浮点数的位级表示转变为二进制表示
  • 数组-差分数组抽象版
  • 【Redis】笔记|第7节|大厂生产级Redis高并发分布式锁实战(二)
  • 风机巡检方案艰难之路
  • 基于TI DSP控制的光伏逆变器最大功率跟踪mppt
  • 【Zephyr 系列 5】定时器与低功耗控制:打造省电高效的嵌入式系统
  • Windows 下部署 SUNA 项目:虚拟环境尝试与最终方案
  • 数据生命线 - MySQL 备份与恢复策略详解
  • ADI硬件笔试面试题型解析上
  • VueScan:全能扫描,高清输出
  • STM32学习之WWDG(原理+实操)
  • 适合自己的记单词方式
  • 中英混合编码解码全解析
  • C++实现汉诺塔游戏用户交互
  • 【笔记】用命令手动下载并安装 tokenizers 库.whl文件(Python 3.12+)
  • 动态规划(2)
  • 7. Ext系列文件系统
  • 深度学习介绍
  • 如何选择最高效的沟通方式?
  • 光耦电路学习,光耦输入并联电阻、并联电容,光耦输出滤波电路
  • 顺德网站建设策划/苏州搜索引擎优化
  • 泰安网络宣传/seo是什么职位简称
  • 基础很差去公司做网站/互联网推广销售是做什么的
  • 中企动力员工感受/武汉seo优
  • 360网站建设公司/百度引擎搜索引擎
  • 域名申请到网站建设教程/网络软文名词解释