通过 Cython 带你认清 Python 变量的本质

简介: 通过 Cython 带你认清 Python 变量的本质

Python 和其它静态语言之间有一个显著的不同,就是 Python 的变量其实只是一个名字。站在 C 语言的角度来看,Python 变量本质上就是一个指针(准确的说是引用),存储的是对象的内存地址,指针指向的内存才是对象。

所以在 Python 中,我们都说变量指向了某个对象。而在其它静态语言中,变量相当于是为某个对象起的别名,获取变量就等于获取这块内存所存储的值。但 Python 变量代表的内存所存储的不是对象,而是对象的地址。

我们用两段代码,一段 C 语言的代码,一段 Python 的代码,来看一下差别。

#include <stdio.h>
void main()
{
    int a = 123;
    printf("address of a = %p\n", &a);
    a = 456
    printf("address of a = %p\n", &a);
}
//输出结果
/*
address of a = 0x7fffa94de03c
address of a = 0x7fffa94de03c
*/

可以看到前后输出的地址是一样的,再来看看 Python 的。

a = 666
print(hex(id(a)))  # 0x1b1333394f0
a = 667
print(hex(id(a)))  # 0x1b133339510

然而我们看到地址前后发生了变化,我们分析一下原因。

首先在 C 中,创建一个变量的时候必须规定好类型,比如 int a = 666,那么变量 a 就是 int 类型,以后在所处的作用域中就不可以变了。如果这时候,再设置 a = 777,那么等于是把内存中存储的 666 换成 777,但 a 的地址和类型是不会变化的。

而在 Python 中,a = 666 等于是先开辟一块内存,存储的值为 666,然后让变量 a 指向这片内存,或者说让变量 a 存储这块内存的地址。然后 a = 777 的时候,再开辟一块内存,然后让 a 指向存储 777 的内存,由于是两块不同的内存,所以它们的地址是不一样的。

所以 Python 的变量只是一个和对象关联的名字罢了,它是一个指针,代表的是对象的地址。换句话说 Python 变量就是个便利贴,可以贴在任何对象上,一旦贴上去了,就代表这个对象被引用了。

再来看看变量之间的传递,在 Python 中是如何体现的。

a = 666
print(hex(id(a)))  # 0x1e6c51e3cf0
b = a
print(hex(id(b)))  # 0x1e6c51e3cf0

我们看到打印的地址是一样的,用一张图解释一下。

我们说 a = 666 的时候,先开辟一份内存,再让 a 存储对应内存的地址;然后 b = a 的时候,会把 a 拷贝一份给 b,所以 b 存储了和 a 相同的地址,它们都指向了同一个对象。

因此说 Python 是值传递、或者引用传递都是不准确的,准确的说 Python 是变量的赋值传递对象的引用传递。因为 Python 变量本质上就是一个指针,所以在 b = a 的时候,等于把 a 指向的对象的地址(a 本身)拷贝一份给 b,所以对于变量来说是赋值传递;然后 a 和 b 又都是指向对象的指针,因此对于对象来说是引用传递。

另外还有最关键的一点,我们说 Python 的变量是一个指针,当传递一个变量的时候,传递的是指针;但是在操作一个变量的时候,会操作变量指向的内存。

所以 id(a) 获取的不是 a 的地址,而是 a 指向的内存的地址(在底层其实就是 a 本身);同理 b = a 也是将 a 本身,或者说将 a 存储的、指向某个具体对象的地址传递给了 b。

在 C 的层面上,显然 a 和 b 属于指针变量,那么 a 和 b 有没有地址呢?显然是有的,也就是二级指针。只不过在 Python 中你是看不到的,Python 解释器只允许你看到对象的地址。

为了更好地理解上述内容,我们看一段 Cython 代码:

# name 是一个变量,它是一个指针
name = "古明地觉"
# 而在 C 中,指针是可以相互转化的
# 因此这里我们转成 void * 类型
# 而 void * 可以转成整型
print(<Py_ssize_t><void *> name)
"""
2198935240400
"""
# 我们得到了一串数字,因为地址本身就是一串数字
# 所以它和我们调用 id 函数的结果是一样的
print(id(name))
"""
2198935240400
"""

如果你对解释器有一定了解的话,那么你应该知道变量是一个泛型指针 PyObject *,而指针存储的地址其实就是一串数字。我们将变量转成 void * 之后再转成整型,那么就能拿到它存储的数字,而这显然也是内置函数 id 所做的事情。

那么问题来了,如果我知道对象的地址,那么能不能反推出对象是什么呢?答案是可以的,只需要将上述过程逆转过来就可以了。

解释一下,首先这串数字虽然表示对象的地址,但它不具备指针的含义,很明显它就是一个普通的 Python 整数而已。如果想让它变成指针,那么需要先转成 void *,因为 void * 和整数是可以相互转化的。只不过这个整数是 C 的整数,因此要先转成 Py_ssize_t,再转成 void *。

具备指针的含义之后,再转成 object 即可拿到对象本身,是不是很神奇呢?如果不借助 Cython,那么你能不能基于对象的地址反推出对象是什么呢?

相关文章
|
19天前
|
人工智能 Python
[oeasy]python039_for循环_循环遍历_循环变量
本文回顾了上一次的内容,介绍了小写和大写字母的序号范围,并通过 `range` 函数生成了 `for` 循环。重点讲解了 `range(start, stop)` 的使用方法,解释了为什么不会输出 `stop` 值,并通过示例展示了如何遍历小写和大写字母的序号。最后总结了 `range` 函数的结构和 `for` 循环的使用技巧。
29 4
|
1月前
|
Python
【10月更文挑战第5天】「Mac上学Python 8」基础篇2 - 变量深入详解
本篇将详细介绍Python中变量的使用方式和进阶操作,涵盖变量的输入与输出、变量的多重赋值、变量的内存地址管理以及变量的传递和交换等操作。通过本篇的学习,用户将对变量的使用有更深入的理解,并能灵活运用变量进行各种编程操作。
51 1
【10月更文挑战第5天】「Mac上学Python 8」基础篇2 - 变量深入详解
|
2月前
|
机器学习/深度学习 算法 数据可视化
8种数值变量的特征工程技术:利用Sklearn、Numpy和Python将数值转化为预测模型的有效特征
特征工程是机器学习流程中的关键步骤,通过将原始数据转换为更具意义的特征,增强模型对数据关系的理解能力。本文重点介绍处理数值变量的高级特征工程技术,包括归一化、多项式特征、FunctionTransformer、KBinsDiscretizer、对数变换、PowerTransformer、QuantileTransformer和PCA,旨在提升模型性能。这些技术能够揭示数据中的潜在模式、优化变量表示,并应对数据分布和内在特性带来的挑战,从而提高模型的稳健性和泛化能力。每种技术都有其独特优势,适用于不同类型的数据和问题。通过实验和验证选择最适合的变换方法至关重要。
46 5
8种数值变量的特征工程技术:利用Sklearn、Numpy和Python将数值转化为预测模型的有效特征
|
1月前
|
存储 编译器 Python
Python--变量、输出与输入
【10月更文挑战第5天】
|
1月前
|
测试技术 Python
Python MagicMock: Mock 变量的强大工具
Python MagicMock: Mock 变量的强大工具
|
1月前
|
存储 Java 编译器
Python学习三:学习python的 变量命名规则,算数、比较、逻辑、赋值运算符,输入与输出。
这篇文章是关于Python编程语言中变量命名规则、基本数据类型、算数运算符、比较运算符、逻辑运算符、赋值运算符以及格式化输出与输入的详细教程。
19 0
Python学习三:学习python的 变量命名规则,算数、比较、逻辑、赋值运算符,输入与输出。
|
1月前
|
存储 C语言 Python
解密 Python 的变量和对象,它们之间有什么区别和联系呢?
解密 Python 的变量和对象,它们之间有什么区别和联系呢?
21 2
|
2月前
|
测试技术 Python
Python MagicMock: Mock 变量的强大工具
Python MagicMock: Mock 变量的强大工具
|
1月前
|
存储 程序员 Python
Python编程入门:探索变量和数据类型
【10月更文挑战第8天】本文是针对初学者的Python编程入门指南,重点介绍Python中变量的定义和使用以及不同的数据类型。我们将通过实例来理解基本概念,并展示如何在Python程序中应用这些知识。文章旨在帮助初学者建立扎实的基础,使他们能够更自信地编写Python代码。
WK
|
1月前
|
Python
Python变量命名
在Python编程中,变量命名对代码的可读性和维护性至关重要。遵循PEP 8风格指南,变量名应使用小写字母和下划线分隔单词,保持简洁明了、描述性强,避免使用单字母、Python关键字和内置函数名,采用有意义的缩写,使用英文命名,保持命名风格一致,避免魔法数字,考虑上下文。正确示例:`user_name`、`order_quantity`;不正确示例:`n`、`q`。
WK
22 0