通过 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,那么你能不能基于对象的地址反推出对象是什么呢?

相关文章
|
8月前
|
索引 Python
Python的变量和简单类型
本文介绍了Python中变量命名规则、常用变量类型及字符串操作。变量命名需遵循字母、数字和下划线组合,不能以数字开头且不可与关键字冲突。字符串支持单引号、双引号或三引号定义,涵盖基本输出、转义字符、索引、拼接等操作。此外,还详细解析了字符串方法如`islower()`、`upper()`、`count()`等,帮助理解字符串处理技巧。
214 15
|
8月前
|
人工智能 Python
[oeasy]python082_变量部分总结_variable_summary
本文介绍了变量的定义、声明、赋值及删除操作,以及Python中的命名规则和常见数据类型。通过示例讲解了字符串与整型的基本用法、类型转换方法和加法运算的区别。此外,还涉及异常处理(try-except)、模块导入(如math和random)及随机数生成等内容。最后总结了实验要点,包括捕获异常、进制转化、变量类型及其相互转换,并简述了编程中AI辅助的应用策略,强调明确目标、分步实施和逐步巩固的重要性。更多资源可在蓝桥、GitHub和Gitee获取。
275 97
|
8月前
|
存储 程序员 Python
Python 变量和简单数据类型
本文介绍了 Python 编程的基础知识,从创建第一个 Python 文件 `hello_world.py` 开始,讲解了 Python 文件的运行机制及解释器的作用。接着深入探讨了变量的定义、命名规则和使用方法,并通过示例说明如何修改变量值。同时,文章详细解析了字符串的操作,包括大小写转换、变量插入及空白字符处理等技巧。此外,还涵盖了数字运算(整数与浮点数)、常量定义以及注释的使用。最后引用了《Python 之禅》,强调代码设计的美学原则和哲学思想。适合初学者快速掌握 Python 基础语法和编程理念。
154 5
|
10月前
|
存储 Linux iOS开发
Python入门:2.注释与变量的全面解析
在学习Python编程的过程中,注释和变量是必须掌握的两个基础概念。注释帮助我们理解代码的意图,而变量则是用于存储和操作数据的核心工具。熟练掌握这两者,不仅能提高代码的可读性和维护性,还能为后续学习复杂编程概念打下坚实的基础。
Python入门:2.注释与变量的全面解析
|
12月前
|
Python
[oeasy]python050_如何删除变量_del_delete_variable
本文介绍了Python中如何删除变量,通过`del`关键字实现。首先回顾了变量的声明与赋值,说明变量在声明前是不存在的,通过声明赋予其生命和初始值。使用`locals()`函数可查看当前作用域内的所有本地变量。进一步探讨了变量的生命周期,包括自然死亡(程序结束时自动释放)和手动删除(使用`del`关键字)。最后指出,删除后的变量将无法在当前作用域中被访问,并提供了相关示例代码及图像辅助理解。
246 68
|
9月前
|
Python
[oeasy]python073_下划线在python里是什么含义_内部变量_私有变量_系统变量
本文回顾了Python中从模块导入变量和函数的方式,重点讨论了避免本地变量名冲突(local name clashes)的方法。通过`from module import variable as alias`可以为导入的变量重命名,防止冲突。根据PEP8规范,建议避免使用`from module import *`,因为它会导入模块中所有非下划线开头的变量,容易引发冲突。下划线在变量命名中有特殊含义:单个前导下划线表示内部变量,后置下划线用于避免与关键字冲突,双下划线前后包围表示系统变量。总结了下划线的不同用法及其作用。下次将继续探讨更实用的编程技巧。
183 3
|
10月前
|
存储 Python 容器
python之变量的使用
Python 中变量是对象的引用,赋值即为指向内存中对象。创建对象时,解释器分配内存,引用计数管理内存回收。Python 是动态类型语言,变量类型在运行时确定。对象分为可变与不可变,前者可修改内部状态,后者则不行。命名空间管理变量作用域,确保不同区域的变量独立。
243 11
|
11月前
|
人工智能 Unix Java
[oeasy]python059变量命名有什么规则_惯用法_蛇形命名法_name_convention_snake
本文探讨了Python中变量命名的几种常见方式,包括汉语拼音变量名、蛇形命名法(snake_case)和驼峰命名法(CamelCase)。回顾上次内容,我们主要讨论了使用下划线替代空格以提高代码可读性。实际编程中,当变量名由多个单词组成时,合理的命名惯例变得尤为重要。
392 9
|
12月前
|
Shell Python
[oeasy]python049_[词根溯源]locals_现在都定义了哪些变量
本文介绍了Python中`locals()`函数的使用方法及其在调试中的作用。通过回顾变量赋值、连等赋值、解包赋值等内容,文章详细解释了如何利用`locals()`函数查看当前作用域内的本地变量,并探讨了变量声明前后以及导入模块对本地变量的影响。最后,文章还涉及了一些与“local”相关的英语词汇,如`locate`、`allocate`等,帮助读者更好地理解“本地”概念在编程及日常生活中的应用。
134 9
|
Python
【10月更文挑战第5天】「Mac上学Python 8」基础篇2 - 变量深入详解
本篇将详细介绍Python中变量的使用方式和进阶操作,涵盖变量的输入与输出、变量的多重赋值、变量的内存地址管理以及变量的传递和交换等操作。通过本篇的学习,用户将对变量的使用有更深入的理解,并能灵活运用变量进行各种编程操作。
132 1
【10月更文挑战第5天】「Mac上学Python 8」基础篇2 - 变量深入详解

推荐镜像

更多