『Python底层原理』--CPython的变量实现机制
在Python中,变量的使用看起来非常简单,例如 a = 10,s = "hello"等等。
然而,这种简单的赋值操作背后,CPython其实做了很多复杂的工作。
本文将通过一些简单易懂的代码示例,一起探索Python变量背后的奥秘,让我们对它的实现机制有更深一步的理解。
1. 变量到底是什么?
在Python中,变量本质上是一个名字到值的映射。
例如,当你写a = 1时,a是一个名字,而1是一个值。
CPython会将这个名字和值关联起来,以便你后续可以通过名字访问这个值。
a = 1
print(a) # 输出:1
这种映射关系是通过一个名为命名空间的结构实现的。
命名空间是一个字典,其中的键是变量名,值是变量对应的对象。
它的定义可参考CPython源码中的Include/internal/pycore_frame.h文件。
typedef struct _PyInterpreterFrame {
// 省略... ...
PyObject *f_globals; /* Borrowed reference. Only valid if not on C stack */
PyObject *f_builtins; /* Borrowed reference. Only valid if not on C stack */
PyObject *f_locals; /* Strong reference, may be NULL. Only valid if not on C stack */
// 省略... ...
}
其中,f_locals 保存局部变量映射,函数执行时,局部变量值存于此;
f_globals 用于全局变量,模块级代码块执行时,f_globals 指向模块全局命名空间字典;
f_builtins 关联内置命名空间。
2. 变量的底层实现:字节码
CPython在执行代码时,会先将代码编译成字节码,然后由虚拟机执行这些字节码。我们可以通过 dis 模块查看代码的字节码。
例如,对于a = 1,字节码如下:
import dis
code = """
a = b
"""
dis.dis(code)

LOAD_NAME:从命名空间中加载变量b的值STORE_NAME:将值存储到变量a中
这两个指令展示了CPython如何处理变量的读取和赋值。
3. 命名空间与作用域
Python中的变量存储在不同的命名空间中,而这些命名空间又与代码的作用域相关,作用域决定了变量的可见性。
Python有三种主要的作用域:
- 局部作用域:函数内部的变量
- 全局作用域:模块级别的变量
- 内置作用域:包含内置函数和类型的命名空间
x = "global" # 全局变量
def func():
y = "local" # 局部变量
print(x) # 输出:global
print(y) # 输出:local
func()

在这个例子中,x是全局变量,y是局部变量。
如果在函数中尝试访问一个未定义的变量,CPython会按照以下顺序查找:
局部命名空间(
f_locals)全局命名空间(
f_globals)内置命名空间(
f_builtins)
如果仍然找不到,就会抛出NameError异常。
4. 不同变量的字节码
CPython为不同作用域的变量提供了不同的字节码指令,以优化性能和实现特定的行为。
4.1. 局部变量
在函数中,局部变量使用LOAD_FAST和STORE_FAST指令。
这些指令直接操作一个数组,而不是字典,因此速度更快。
def func():
a = 1 # STORE_FAST
b = a # LOAD_FAST
return b
dis.dis(func)

4.2. 全局变量
全局变量使用LOAD_GLOBAL和STORE_GLOBAL指令。
这些指令会直接操作全局命名空间。
x = 1
def func():
global x
x = 2 # STORE_GLOBAL
return x # LOAD_GLOBAL
dis.dis(func)

4.3. 闭包变量
当函数嵌套时,内部函数可以访问外部函数的变量。
这些变量称为闭包变量,使用LOAD_DEREF和STORE_DEREF指令。
def outer():
x = 1
def inner():
return x # LOAD_DEREF
return inner
dis.dis(outer)

5. 类中的变量
在类定义中,变量的行为与函数不同。
类定义中的变量使用LOAD_NAME和STORE_NAME指令,因为类的命名空间会动态地与全局命名空间交互。
x = "global"
class MyClass:
print(x) # 使用 LOAD_NAME
x = "local"
print(x) # 使用 LOAD_NAME
MyClass()
输出:

查看指令的话,可以使用:python.exe -m dis .\cpython-variable.py命令。
如果在类中使用嵌套函数,CPython会使用LOAD_CLASSDEREF指令来处理闭包变量。
class MyClass:
x = "cell"
def method(self):
print(x) # 使用 LOAD_CLASSDEREF
MyClass().method()
6. 编译器如何选择指令
CPython的编译器会根据变量的作用域和代码块类型选择合适的字节码指令。
例如:
- 如果变量是局部变量,编译器会生成
LOAD_FAST和STORE_FAST - 如果变量是全局变量,编译器会生成
LOAD_GLOBAL和STORE_GLOBAL - 如果变量是闭包变量,编译器会生成
LOAD_DEREF和STORE_DEREF
7. 总结
Python变量的实现机制比看起来复杂得多,它涉及到字节码指令、命名空间、作用域以及编译器的决策逻辑。
通过理解这些概念,可以更好地掌握Python的变量行为,尤其是在复杂的作用域场景中。
如果对CPython的实现感兴趣,可以进一步阅读其源码中与变量相关的部分。
『Python底层原理』--CPython的变量实现机制的更多相关文章
- 『Python基础-3』变量、定义变量、变量类型、关键字Python基础-3』变量、定义变量、变量类型、关键字
『Python基础-3』变量.定义变量.变量类型.关键字 目录: 1.Python变量.变量的命名 2.变量的类型(Python数据类型) 3.Python关键字 1. Python 变量.变量的命名 ...
- 『Python基础-11』集合 (set)
# 『Python基础-11』集合 (set) 目录: 集合的基本知识 集合的创建 访问集合里的值 向集合set增加元素 移除集合中的元素 集合set的运算 1. 集合的基本知识 集合(set)是一个 ...
- 『Python基础-10』字典
# 『Python基础-10』字典 目录: 1.字典基本概念 2.字典键(key)的特性 3.字典的创建 4-7.字典的增删改查 8.遍历字典 1. 字典的基本概念 字典一种key - value 的 ...
- 『Python基础-9』元祖 (tuple)
『Python基础-9』元祖 (tuple) 目录: 元祖的基本概念 创建元祖 将列表转化为元组 查询元组 更新元组 删除元组 1. 元祖的基本概念 元祖可以理解为,不可变的列表 元祖使用小括号括起所 ...
- 『Python基础-8』列表
『Python基础-8』列表 1. 列表的基本概念 列表让你能够在一个地方存储成组的信息,其中可以只包含几个 元素,也可以包含数百万个元素. 列表由一系列按特定顺序排列的元素组成.你可以创建包含字母表 ...
- 『Python基础-7』for循环 & while循环
『Python基础-7』for循环 & while循环 目录: 循环语句 for循环 while循环 循环的控制语句: break,continue,pass for...else 和 whi ...
- 『Python基础-5』数字,运算,转换
『Python基础-5』数字,运算,转换 目录 基本的数字类型 二进制,八进制,十六进制 数字类型间的转换 数字运算 1. 数字类型 Python 数字数据类型用于存储数学上的值,比如整数.浮点数.复 ...
- 『Python基础-4』字符串
# 『Python基础-4』字符串 目录 1.什么是字符串 2.修改字符串 2.1 修改字符串大小 2.2 合并(拼接)字符串 2.3 使用乘号'*'来实现字符串的叠加效果. 2.4 在字符串中添加空 ...
- 『Python基础-1 』 编程语言Python的基础背景知识
#『Python基础-1 』 编程语言Python的基础背景知识 目录: 1.编程语言 1.1 什么是编程语言 1.2 编程语言的种类 1.3 常见的编程语言 1.4 编译型语言和解释型语言的对比 2 ...
- 『Python基础-12』各种推导式(列表推导式、字典推导式、集合推导式)
# 『Python基础-12』各种推导式(列表推导式.字典推导式.集合推导式) 推导式comprehensions(又称解析式),是Python的一种独有特性.推导式是可以从一个数据序列构建另一个新的 ...
随机推荐
- Java 设计模式——观察者模式:从优衣库不使用新疆棉事件看系统的动态响应
背景事件:近日,优衣库宣布不再使用新疆棉花,这一举措引发了广泛的社会讨论.消费者的反应和舆论的压力,让优衣库的决策迅速影响了市场和品牌形象.类似的,许多系统也面临着需要根据外部事件或状态的变化,做出即 ...
- To B企业:2025继续打价格战,只有死路一条
从双十一数不清的促销.满减还有消费券,到大模型厂商的"你低价,我免费"中可以窥见,最近几年,在产品泛滥.市场红利消失的困境中,"价格战"已从To C卷到To B ...
- 物联网CC2530按键单双击分别控制两灯
(1)确定思路单击和双击的效果分别是怎样的(此文章采用简单的延时函数不涉及中断).首先可以定义一个普通延时delay和一个标志位count变量,这里需有个延时阈值咱们直接可以宏定义B值(这里需要注意宏 ...
- datagridview点击列头对当前列进行排序的功能无效
DataGridView 的默认行为是支持通过单击列头对列进行排序,但在以下情况下可能会取消该功能或无法使用: 1. 绑定的数据源不支持排序 如果 DataGridView 的数据源是绑定到一个不支持 ...
- ASP.NET Core IHostBuilder
HostBuilder 很显然,HostBuildr 就是用来构建 Host 的构建器. IHostBuilder 定义 通过 Build() 方法,构建器返回构建的 IHost 对象实例. 具体怎么 ...
- git clone 指定 ssh-key 文件
环境 & 软件 mac OS 问题 git clone 不是默认 ssh-key,无法克隆 解决方法 用ssh-add命令将对应的私钥加入到缓存 // ssh-add 自定义名称 // 例子 ...
- 执行docker ps时提示"dial unix /var/run/docker.sock: connect: permission denied"
0. 创建docker用户组 sudo groupadd docker 1. 将当前用户加入docker组 # sudo gpasswd -a $USER docker $ sudo usermod ...
- Base58在java程序中应用
Base58是用于Bitcoin中使用的一种独特的编码方式,主要用于产生Bitcoin的钱包地址. 相比Base64,Base58不使用数字"0",字母大写"O" ...
- 聊一聊 C#线程池 的线程动态注入 (下)
一:背景 1. 讲故事 前面二篇我们聊到了 Thread.Sleep 和 Task.Result 场景下的线程注入逻辑,在线程饥饿的情况下注入速度都不是很理想,那怎么办呢?有没有更快的注入速度,这篇作 ...
- Qt音视频开发41-文件推流(支持网页和播放器播放并切换进度)
一.前言 本功能最初也是有一些人提过类似的需求,就是能不能将本地的音视频文件,通过纯Qt程序推流出去,然后用户可以直接在网页上播放,也可以用各种播放器播放,然后还可以任意切换播放进度,其实说白了就是个 ...