没有变量,只有名字
Python 变量是盒子吗?
Python 中的变量看似很简单,实际关乎到很多概念,今天就来聊聊 Python 的变量。
什么是变量
什么是变量,以及为什么需要变量其实无需过多解释。我们在小学阶段学习数学的时候已经遇到过类似的情况,使用一个字符,比如 x
来代替未知的一个数。
计算机编程中的变量本质作用也是一样的,用一个 标识符(名字) 来指代某个 值(Value)。
需要区分的是,数学中的 x
符号替代的一般是一个待解的未知量或者常量,而编程中的变量更多情况下是程序运行过程中变化的量。此外,编程中的等号 =
不是表示两边相等,而是表示把右边的值 赋值 给左边的变量名。
所以理解赋值,就是理解变量的关键了。
变量是盒子吗?
很多教程在介绍变量的时候,把变量比喻成盒子,赋值就是往盒子里面放各种东西,变量名就是在盒子上写的标记。
这个比喻在某些编程语言里是比较贴切的,比如 C 或 Java,但是在 Python 中却并非如此。
假如 Python 变量是盒子,让我们不妨先来思考两个问题:
- Python 中的变量无需事先声明,为什么?
- Python 的变量赋值无需考虑对象的类型,为什么?
这两个问题没法用“盒子”来自圆其说。除此之外,学习到后面还有更多的情况没法用 “变量是盒子” 来解释。
正确地比喻,Python 的变量应该是标签纸,所谓赋值就是把标签贴在物体上,标签上写的名字就是变量名。
然而这毕竟是一个比喻,它虽然有助于我们理解问题,但是要想彻底的掌握 Python 中变量的概念,我们还是有必要继续探究一下。
没有 variable,只有 name
变量,是英文 variable
的直译。
如果用 variable 在 Python 的官方文档中索引这个关键字,你会发现并没有哪一章的标题是 Variable。
索引出来的唯一一个的文档的标题是:4.2. Naming and binding
,也就是 命名 和 绑定。这个标题已经道出了 Python 变量的本质。
细心的小伙伴可能注意到,索引列表上的
free
字段。事实上,将我们引导至此的关键字是free variable
,即 自由变量,后面讲到变量作用域和闭包的时候还会重点介绍它。
整个 4.2
这一节文档虽然篇幅不大,但是透彻地介绍了 Python 中变量的若干重要概念,强烈建议仔细阅读。不想自己看文档的请继续听我来解释。
变量的三要素
既然没有 variable,那我们在讨论 变量 的时候实际上是在讨论什么呢? 还是看文档:
Names refer to objects. Names are introduced by name binding operations.
短短一句话就给出了关于 变量 的 3 个元素:
- Name, 名字
- object,对象
- binding, 绑定
名字(name) 指向 对象(object) ,由 绑定(binding) 操作引入。
如何理解呢?
让我们先来解释什么是对象。
什么是对象
在 Python 里有句很著名的话:一切皆对象(Everything is object)。
关于 object
,Python 文档术语这么解释的:
object
Any data with state (attributes or value) and defined behavior (methods).
即:
任何有状态(属性或值)和定义行为(方法)的 数据。
Python 文档在解释数据模型时是这么说的:
Objects are Python’s abstraction for data. All data in a Python program is represented by objects or by relations between objects.
对象是 Python 对数据的抽象。Python 程序中的所有数据都可以表示为对象或者对象之间的关系。
技术人不打诳语,这里的一切真得就是指的一切。你已经学到或将要学的很多东西,都是对象,包括:数字、字符串、列表、元组、集合、字典、函数、类、模块等。
当然,Python 编程中还有很多不属于对象范畴的东西,比如关键字,表达式,条件语句,循环语句等语法。
从开始就说起的 赋值,其中的 值,指的就是 对象的值。
但是一般情况下,我们不会这么扣字眼,所以通常当提到 值/数据/对象 的时候,指的都是同一个概念。
接下来,我们从什么是数据的层面来理解什么是对象。
对象的三个基础属性
对程序来说,数据都是以二进制形式存在内存中的信息。
关于什么是二进制,以及内存的原理都是计算机专业的入门课程,这里就不展开了。提及它们的原因是它们决定了数据的另外两个必需属性:
- 数据必须要有类型,不然都是
0
1
代表什么意思呢? - 内存中的数据有唯一的地址,不然怎么去定位数据呢?
于是我们就凑齐了对象的三个基础属性,看看文档是怎么说的:
Every object has an identity, a type and a value.
关于 value
和 type
没什么好解释的了,唯有 identity
还需要额外说明下:
An object’s identity never changes once it has been created; you may think of it as the object’s address in memory.
可以这么去理解,因为内存地址过于暴露底层细节,所以 Python 其实是利用了内存地址的唯一性来为对象创建唯一的 ID。
对象是 Python 中非常重要的概念,后续还会深入介绍,请保持关注。
名字是什么
说完了对象再来说说名字,术语又叫 标识符(Identifier)。
名字语法规则很简单:
- 除了下划线和大小写字母,其它乱七八糟的符号都不能用
- 可以有数字但是不能以数字开头
- 下划线开头的名字有一点特殊
- 不能用 Python 保留的 关键字
- 区分大小写
规则简单,但是想要取好名字可没有看上去那么简单。如何命名和理解变量无关,以后再专门讨论,本文就此略过。
绑定的方式
有了对象和名字,下面就是如何把这两者 绑定(Binding)
把对象绑定到一个名字上的最常见的方式是 赋值语句(Assignment statement),这也是通常介绍变量的教程中唯一提到的形式。
实际上,以下行为都是在做名字绑定:
- 函数传参
def
定义函数class
定义类for
语句import
语句with ... as
语句except ... as
语句
这些语句绑定的名字和赋值语句产生的名字(变量)在使用的时候并没有本质差别。
理解了这些之后,再回头去看把数字、字符串不同类型的值赋给同一个变量名,就没什么意外的了。只是名字绑定的一小部分特例而已。
变量是什么
介绍完了 3 个重要概念:
- 名字
- 对象
- 绑定
可以用它们来凑出变量的定义:变量是 绑定了对象的名字。
虽然得出这么一个定义,但是还剩最关键的问题没有解释清楚。我们前面罗列的各种操作它们都在绑定,但是 绑定到底是在干什么?,
变量的本质
下面的语境中,名字和变量是同义词。
当名字出现在绑定语句之外的地方,它们代表的是什么呢?
比如下面这个最简单的赋值语句,左侧的 x
我们已经理解了,它是一个名字,先后被赋值(即被绑定)两次,右边那个 x
现在是什么?
x = 1
x = x + 2
你的第一反应肯定是想,不就是被绑定的那个对象吗?(我的第一反应也是这样的)
让我换个更具体的问法:
假设用 Python 中的字典结构来保存变量信息,Key
显然是 变量名字,那么对应的 Value
是什么?
Key (名字) | Value (?) |
---|---|
x | 1 |
y | “DavyCloud” |
直观的感觉确实应该就是 对象,然而实际 并不是 !
Value
保存的其实是 对象的引用。
Key (名字) | Value (对象的引用) |
---|---|
x | 1 的引用 |
y | “DavyCloud” 的引用 |
如果是保存的是对象本体,那变量是盒子的比喻就没毛病了。
对象引用是什么
我们前面说了,每个对象在内存中都有唯一的地址,可以这么理解:
Python 内部在处理绑定的时候保存的是对象的地址,但是让我们在使用的时候看到的是对象。
也就是说,当我们把名字/变量传递出去的时候,接受方收到的也是对象的地址,并不会去复制或改变对象本体。
a = []
b = a # b 和 a 绑定的是同一个列表对象
说起来很抽象,但其实这是非常自然的做法,举个现实中的例子:
假设我的频道或公众号 DavyCloud
你很喜欢,你订阅或者是分享给你的朋友:
channel = "DavyCloud"
subscribed += channel
share(channel, to)
显然,不论多少人关注,DavyCloud 本体自始至终只有一个。那么出现在大家订阅列表里的,就是我的频道的引用。
由此可见,对象引用实际上是一个很符合现实世界的形式,我们真的不用过多关心底层的内存地址这些细节。
变量和可变对象
当然,在你订阅之后,我的频道确实也发生了改变(订阅人数增加),这说明频道这个对象是 可变的(mutable)。相对应的还有 不可变的(immutable) 对象。这里的 变 指的是对象本身的值发生了变化。
和变量做个对比:
通过赋值(也可以是其它绑定操作)改变变量的时候,变的是绑定关系,也可以看成是对象 id
变了(因为每个对象的 id 都不一样)
绑定操作没有办法改变对象本身的,只能通过改变对象的属性来改变对象,也就是对象的 id
前后保持不变,但是值发生了变化。
获取对象的属性操作,例如 obj.attr
完全可以看成是一种另类的变量名,所以赋值操作和普通变量是一样的。
解绑和删除
有绑定自然就有解绑。绝大多数的解绑操作是隐藏的。
当对一个变量重新赋值,绑定到了新对象上,其实背后自然就是解绑了旧的对象。
变量自己是不会去关心旧对象的,只有 Python 在内部记录下情况,旧对象的引用数减少了。
对象的引用计数是 Python 语言的内部实现,并不属于对象的属性。这点和前面的频道订阅数是不一样的。
当一个对象的引用计数减少为 0,这个对象就要面临被垃圾回收的命运,即把它占用的内存释放出来。
主动的解绑操作是通过 del
语句实现的,如果不理解变量,可能看到这个名字会以为是把对象删除了。
通过我们上面的分析,现在应该明白,del
删除的其实只是 变量,即 名字 和 对象引用,所以有 2 个后果:
- 被删掉的名字不再可用,如果再试图访问会报
NameError
- 对象的引用计数减 1,如果对象的引用数为 0,它会被垃圾回收
小结
本文深度分析了 Python 中变量的概念,指出了变量是由三种元素组成:
- 名字
- 对象
- 绑定
因为涉及知识点较多,为了专注于变量本质,有些地方粗略带过。
费了这么多篇章,实际只讲解完了 4.2.1. Binding of names
中的几句话。文档中解释变量作用域的部分,留待以后再详述。
本文主要以抠字眼讲概念为主,后面会补充更多实例。透彻理解变量的本质,介绍起 Python 中的很多语法概念也将会变得很轻松。
如果本文对你有帮助,请 点赞、分享、关注,谢谢!