第 2 章:变量、类型与表达式
本章内容限 Python 3.12+ 用户
1. 什么意思?
本节目标:了解变量、类型与表达式的意思
预计用时:10~15 分钟
完成标志:能够说清楚变量、类型与表达式是什么,并学会定义变量
1.1 什么是变量?
在写程序之前,我们得解决一个事情:怎么让计算机记住一个值?
显然,我们不可能每次都把东西存入文件——这实在是太慢了!
假如你想计算 $3 + 5$,可以直接写死在代码里。但如果这个 $3$ 是用户输入的年龄、$5$ 是动态变化的折扣呢?你需要一个地方把它们“存”起来,还能随时改、随时用。
这时候就要请出本小节的主角:变量。
变量是一个带标签的盒子
变量,顾名思义就是可变的量,可别被这名字吓到,你可以把它想象成一个贴了标签的收纳盒,它一般由两个东西和一个步骤组成:
- 标签名(变量名):你自己起的名字,比如
age、cost。 - 盒子里的东西(值):实际存储的数据,比如
18、0.8。 - 放进去的动作(赋值):用
=把值放进盒子里。
比如这样:
age = 18
cost = 0.8
需要注意:Python 中的
=并不是数学里的“相等”,而是“把右边的值放进左边的盒子里”。
变量名不是随便乱取的
Python 中的变量名有严格的硬性规定:
- 支持 Unicode 字母、数字和下划线,但不能以数字开头,也不能包含 +、-、空格等运算符或空白符。
- 严格区分大小写:
age和Age是两个盒子 - 不能使用 Python 关键字(如
if、for、in)
除此之外,还有几个软规范(不遵守不会报错,但是别人可能会看不懂你的代码):
- 命名方法统一:变量应采用小写蛇形命名法(小写下划线命名法),例如应采用
user_name而非UserName。 - 变量名称意义:变量名必须有意义,例如应采用
student_age而非n(除非仅作为临时变量使用),采用 字母、数字、下划线,不使用中文。
简单实操一下
那么怎么定义变量?其实非常简单。
打开编辑器,输入以下内容:
name = "Sam"
age = 18
恭喜你,你成功定义了两个变量(name 和 age),左边写变量名,中间用等号连接,右边写变量值。
编码建议:等号两侧写空格可以让代码有呼吸感,不会过于紧凑
现在请你尝试自己定义两个变量
变量可以存很多东西
刚才举的例子都是数字,其实变量可以存的东西远不止于此:
name = "Sam" # 文本(字符串)
is_docter = True # 真假值(布尔)
scores = [90, 85] # 一组数据(列表)
这便是下个小节的主题:类型——你不能把 "Sam" 和 True 相加,就像你不能把苹果和开关拧在一起。
1.2 什么是类型?
上一小节我们说,变量是带标签的盒子。但光有盒子不够,你还得知道盒子里装的是什么——是数字、文字,还是别的什么?
这就是类型
类型决定了“能做什么”
在 Python 里,类型不是一种约束,而是一份操作说明书。每种类型都自带一组合法操作,跨类型的操作要么被禁止,要么需要显式转换。
age = 18 # int(整数)
cost = 9.9 # float(浮点数)
name = "Sam" # str(字符串)
is_active = True # bool(布尔值)
你可以对它们做这些事情:
age + 10 # 整数加法 → 28
cost * 2 # 浮点乘法 → 19.8
name + " Smith" # 字符串拼接 → "Sam Smith"
is_active and False # 布尔逻辑运算 → False
但这些不行:
age + name # TypeError: unsupported operand type(s) for +: 'int' and 'str'
cost and name # 语法合法但语义荒谬,结果不是你想的那样
TypeError 是类型错误的信号,学会读懂它。
怎样知道一个变量的类型?
Python 提供了内置函数 type():
type(age) #
type(cost) #
type(name) #
type(is_active) #
注意返回的是 <class 'int'> 而不是 'int'——这说明在 Python 里,类型本身也是对象。这个细节现在不用深究,但请记住:Python 的类型系统是动态的、变量的类型取决于它当前绑定的值,而非声明。
可以使用以下代码验证:
x = 42
type(x) #
x = "hello"
type(x) # ← 同一个变量,类型变了
实操一下
打开编辑器,输入以下内容
age = 18
cost = 9.9
name = "Sam"
print(type(age))
print(type(cost))
print(type(name))
如果看到
<class 'int'>
<class 'float'>
<class 'str'>
那就说明成功了!
接下来,请在最后一行 print() 后换行,并写入以下内容
result = age + name
运行后你将看到类似如下的报错
TypeError: unsupported operand type(s) for +: 'int' and 'str'
不要慌,这恰切说明成功了,Python 是典型的强类型语言。
现在请你尝试定义一个布尔变量,并使用 type() 来查看它的类型。
四种基础类型一览表
| 类型 | 字面量示例 | 核心用途 | 典型操作 |
|---|---|---|---|
int | 42, -7, 0 | 计数、索引、离散量 | + - * // % |
float | 3.14, -0.5, 1e10 | 测量、比例、连续量 | + - * / |
str | "hi", 'ok', "" | 文本、标识符、消息 | +(拼接), *(重复), .upper() |
bool | True(真/对), False(假/错) | 条件判断、状态标记 | and or not, 比较运算 |
这里有一个非常著名的陷阱:0.1 + 0.2 ≠ 0.3。这不是 Python 的 bug,而是 IEEE 754 浮点标准的固有特性。所以涉及精准的浮点数比较时,永远不要直接用 ==。该问题会在后续章节展开。
1.3 什么是表达式?
在 1.1 和 1.2 中,我们学会了“存数据”和“认数据”。但光存着不用没有意义,程序的核心价值在于对数据进行加工。
在学会了存数据和认数据之后,我们需要让数据“动”起来。程序的核心价值在于加工,而 Python 中最基本的加工单元就是表达式。
简单来说,表达式就是一段能算出结果的代码。无论它长什么样,最终一定会变成一个具体的值。比如 3 + 5 是表达式,结果是 8;age > 18 也是表达式,结果是 True 或 False。
判断一段代码是不是表达式,有一个最简单的标准:看它能不能放进 print() 里打印出来。与之相对的是语句,比如 age = 18 只是完成了一个赋值动作,本身不产出结果,所以你不能写 print(age = 18)。
算术运算
这是最基础的表达式,和数学课上学的一致,但有两个符号需要特别注意:
- 除法
/:在 Python 中永远返回浮点数。即使10 / 2能整除,结果也是5.0而不是5。 - 取模
%:计算余数。10 % 3的结果是1。它在判断奇偶、循环计数等场景中非常常用。 - 乘方
**:表示幂运算。2 ** 10表示 2 的 10 次方,结果是1024。
至于加 +、减 -、乘 *,用法与数学完全相同,不再赘述。
比较运算符与 is
比较运算符用来判断两个值之间的关系,结果永远是布尔值(True 或 False)。包括大于 >、小于 <、大于等于 >=、小于等于 <=、不等于 !=。
这里要重点讲两个容易混淆的概念:
== 与 = 的区别
这是新手最容易踩的坑,必须彻底分清:
=是赋值语句,意思是“把右边的值放进左边的变量里”。它不产出结果,不是表达式。==是比较运算符,意思是“判断左右两边的值是否相等”。它是表达式,会产出True或False。
x = 5 # 赋值,把 5 放进 x
x == 5 # 比较,判断 x 的值是不是 5,结果为 True
x == 6 # 比较,结果为 False
如果你本意是比较却误写成了赋值,Python 通常会直接报语法错误,这其实是一种保护机制。
== 与 is 的区别
==比较的是值是否相等。is比较的是身份是否相同,即两个变量是否指向内存中的同一个对象。
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # True,因为值相同
print(a is b) # False,因为它们是两个独立的列表对象
print(a is c) # True,因为 c 就是 a 本身
对于数字和字符串等简单类型,Python 内部有优化机制,== 和 is 的结果常常一致。但对于列表、字典等复杂类型,务必记住:判断内容相等用 ==,判断是否为同一个对象才用 is。日常开发中,绝大多数场景你只需要用 ==。
简单逻辑运算
逻辑运算符用于组合多个条件,有三个关键字:and、or、not。
and:两边都为True时结果才为True。or:只要有一边为True,结果就为True。not:取反,True变False,False变True。
现阶段你只需要掌握它们的基本语义即可。关于 and 和 or 返回操作数本身而非布尔值的特性,属于进阶知识,我们后续再展开。
海象运算符
前面反复强调 = 是语句不是表达式,不能放进 print() 里。但 Python 3.8 引入了一个例外:海象运算符 :=。
它的特殊性在于,它让赋值这个动作本身也能产出一个值:
# 传统写法需要先赋值再使用
line = input()
if line != "quit":
process(line)
# 海象运算符:赋值的同时返回值供 if 判断
if (line := input()) != "quit":
process(line)
你可以把它理解为“赋值并返回被赋的值”。print(n := 10) 是合法的,它会打印 10 并同时把 10 赋给变量 n。
现阶段你不需要主动使用它,但在阅读他人代码或标准库源码时可能会遇到。知道它的存在和特殊性,就不会在看到 := 时感到困惑。
冷知识:海象运算符现仍被不少人嫌弃,因为它模糊了赋值和表达式的边界。
该运算符由 PEP 572 引入,由 Python 之父 龟叔(Guido van Rossum) 亲自推动。但由于社区反对声浪极其激烈,讨论中甚至出现了针对他个人的人身攻击与骚扰,加之长期为提案辩护带来的疲惫感,最终促使他宣布辞去 BDFL(终身仁慈独裁者)身份。这一事件也直接推动了 Python 治理模式从个人决策向指导委员会集体决策的转型。
其他表达式
除了上述几类,Python 中还有很多表达式形式,例如函数调用 len(name)、字符串方法 "hello".upper()、索引访问 my_list[0] 等。它们的共同点都是能产出一个值。随着学习的深入,你会逐渐接触到更多类型的表达式,但它们都遵循同一个核心原则:凡是能算出结果的代码,就是表达式。
实操一下
打开编辑器,依次输入以下代码验证你的理解:
# 算术运算
print(10 / 3) # 3.3333...
print(10 // 3) # 3
print(10 % 3) # 1
print(2 ** 10) # 1024
# 比较运算
print(5 == 5.0) # True
print(5 is 5.0) # False(注意区分)
print("abc" == "abc") # True
# 逻辑运算
print(True and False) # False
print(not True) # False
# 海象运算符
print(n := 10) # 10
print(n + 5) # 15
完成以上验证后,请尝试以下练习:
- 计算
17 // 5和17 % 5,验证(17 // 5) * 5 + (17 % 5)是否等于17。 - 定义两个内容相同的列表
a和b,分别用==和is比较它们,观察并解释结果差异。 - 猜测
"" or 0 or None or "found"的结果,然后用print()验证。
1.4 本节小结
在本节中,我们构建了 Python 编程最基础的三块基石:
- 变量是带标签的盒子:它通过
=将值与名称绑定,让我们能够动态地存储和复用数据。记住变量名的硬性规则与蛇形命名的软规范,这是写出可读代码的第一步。 - 类型是操作的说明书:
int、float、str、bool四种基础类型决定了数据“能做什么”。Python 是动态强类型语言,变量的类型随值而变,但跨类型的非法操作会被严格禁止。学会阅读TypeError和使用type()是排查问题的基本功。 - 表达式是产出值的代码单元:无论是算术运算、比较判断还是逻辑组合,表达式的核心特征是“能算出一个结果并被
print()打印”。务必分清赋值语句=与比较表达式==,理解is与==的本质区别,并对海象运算符:=保持“认识但不滥用”的态度。
这三者共同构成了 Python 程序的地基。下一节,我们将深入了解 Python 有关变量、类型与表达式的细节,并了解一些小“特性”。
2. 变量、类型与表达式背后的真相
本节目标:揭开变量赋值与类型比较的底层真相
预计用时:20~30 分钟
完成标志:能够准确区分is与==的本质差异;能画出变量赋值与浅拷贝的内存引用图;能解释del的真实行为及可变/不可变对象在引用传递中的表现
本节略微复杂,但较为重要,建议阅读
2.1 id / is / == 的三角关系
在第 1 节中,我们留下了一个悬念:为什么有时候 a == b 为 True,但 a is b 却是 False?要彻底解开这个谜题,我们需要引入第三个概念:id()。
三个概念的精确定义
id():返回对象在内存中的唯一标识符(在 CPython 实现中就是内存地址)。可以理解为对象的“身份证号”。==(值相等):比较两个对象的内容是否相同。就像看两个人长得像不像。is(身份相同):比较两个变量是否指向内存中的同一个对象。就像问“这两个名字是不是贴在同一个人身上”。
用代码来验证:
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(id(a)) # 例如: 140234866534720
print(id(b)) # 例如: 140234866535104 ← 不同的身份证号
print(id(c)) # 140234866534720 ← 与 a 完全相同
print(a == b) # True → 内容相同
print(a is b) # False → 不是同一个对象
print(a is c) # True → 是同一个对象
注意:这里存在一个“坑”
理解了上面的例子后,你可能会觉得“懂了”。但接下来这段代码会打破你的安全感:
x = 256
y = 256
print(x is y) # True ← 咦?不应该是 False 吗?
a = 257
b = 257
print(a is b) # False ← 这才对嘛...等等,为什么上面是 True?
这不是 bug,这是 CPython 的小整数缓存池优化。为了避免频繁创建整数对象,解释器在启动时预先创建 -5 到 256 之间的所有整数对象并常驻内存。
这就像一所学校提前准备好了 262 张名牌(编号从 -5 到 256),谁要用就拿一张。所以 x = 256 和 y = 256 拿的是同一张名牌,它们 is 为 True。而 257 超出了名牌库存,必须当场打印一张新的,所以每次赋值都是一张新名牌,a is b 为 False。
同样的陷阱也存在于字符串:Python 会对符合标识符规则的短字符串进行驻留,使得 "hello" is "hello" 可能为 True,但 "hello world!" is "hello world!" 通常为 False。具体行为取决于实现版本和运行环境,绝不可依赖。
实操验证
# 1. 基础验证
a = [1, 2]
b = [1, 2]
c = a
print(a == b, a is b, a is c)
# 2. 小整数缓存边界测试
print(255 is 255) # ?
print(256 is 256) # ?
print(257 is 257) # ?
print(-5 is -5) # ?
print(-6 is -6) # ?
# 3. 字符串驻留测试
print("abc" is "abc") # ?
print("abc def" is "abc def") # ?
print("" is "") # ?
# 4. None 的正确判断方式
x = None
print(x is None)
print(x == None) # 能跑但不推荐
完成验证后,请尝试回答:如果 a == b 为 True,能否推出 a is b 一定为 True?反过来呢?用代码证明你的结论。
2.2 赋值不是复制:引用语义的本质
假如你定义了一个变量 a,给它赋了一个列表。然后你想让 b 和 a 的值相等,于是你写 b = a。
如果此时你通过 b 修改了这个列表,然后打印 a,你会惊奇地发现:a 也被修改了!
这是为什么呢?
在 Python 中,b = a 并没有把 a 的“值”复制一份塞进 b,而是让 b 和 a 指向了内存中的同一个对象。这叫引用语义——变量存的是指向对象的引用,而不是对象本身。
变量是标签,不是盒子
很多初学者把变量想象成“盒子”,认为 a = [1, 2, 3] 是把列表放进了名为 a 的盒子里,b = a 则是把盒子里的东西复制一份放进 b。
这是错的。 正确的模型是:
- 对象(如
[1, 2, 3])是独立存在于内存中的实体。 - 变量(如
a、b)只是贴在对象上的标签/引用。 =的作用仅仅是把一个标签贴到某个对象上。
就像你有一个真实存在的物件(比如一个花瓶),a 和 b 是贴在花瓶上的两张标签。你把 b = a 理解为“再拿一张标签,贴到同一个花瓶上”,而不是“复制一个花瓶”。
a = [1, 2, 3] # 创建一个花瓶,贴上标签 a
b = a # 再拿一张标签 b,也贴到同一个花瓶上
b.append(4) # 通过标签 b 找到了花瓶,往里插了一朵花
print(a) # [1, 2, 3, 4] ← 通过标签 a 看同一个花瓶,花也在
验证方法:执行
print(id(a) == id(b)),结果一定是True。两张标签贴在同一个物件上。
浅拷贝只复制了外层标签
当你确实需要一份副本时,可能会想到切片或 copy()。但请注意:浅拷贝只复制了最外层的容器对象,内部的子对象仍然是共享引用。
original = [[1, 2], [3, 4]]
shallow = original.copy() # 或 shallow = original[:]
# 外层是两个不同的列表对象
print(original is shallow) # False 外层确实分开了
# 但内层子列表仍然是同一个对象!
print(original[0] is shallow[0]) # True 内部还是共享的
shallow[0].append(99)
print(original) # [[1, 2, 99], [3, 4]] ← 原列表也被改了!
用比喻来理解:浅拷贝就像复印了一份通讯录。纸张是新的(外层容器复制了),但上面写的每个电话号码仍然指向原来的那个人(内部子对象共享)。你改了复印件上某个电话号码的备注,原件上不会变;但如果那个“人”本身换了手机号,你两份通讯录上的号码都会失效——因为它们引用的是同一个“人”。
如果你需要彻底独立的副本(连内部子对象也复制),必须使用深拷贝:
import copy
deep = copy.deepcopy(original)
deep[0].append(99)
print(original) # [[1, 2], [3, 4]] ← 完全不受影响
可变 vs 不可变:为什么字符串"看起来"像复制了?
你可能会疑惑:为什么 a = "hello"; b = a; b = "world" 不会影响 a?这不是矛盾了吗?
不矛盾。关键在于不可变对象无法被原地修改:
| 操作 | 可变对象(list/dict/set) | 不可变对象(int/str/tuple) |
|---|---|---|
b = a | 两张标签贴到同一个物件 | 两张标签贴到同一个物件(此时相同) |
b.append(4) / b += "x" | 原地修改物件,a 跟着变 | 创建了一个新物件,b 贴到新物件上,a 不变 |
| 本质 | 标签没动,物件变了 | 标签 b 被撕下贴到了新物件上 |
# 不可变对象的"假复制"
a = "hello"
b = a # a 和 b 指向同一个字符串物件
b = b + " world" # + 运算创建了一个新字符串物件,b 贴到新物件上
print(a) # "hello" ← a 仍然指着原来的物件
print(a is b) # False ← 现在它们指向不同的物件了
所以不是不可变对象被复制了,而是任何"修改"操作实际上都是创建新对象+重新贴标签。 可变对象之所以危险,是因为它提供了原地修改的方法(append、extend、update 等),这些方法不会移动标签,只会改变标签所指向的对象的内容。
实操一下
1. 赋值共享验证
x = 66
y = x
print(x is y) # ? 解释原因
2. 不可变对象的"拷贝"等价验证
a = "hello"
b = a[:] # 字符串切片,形式上类似浅拷贝
c = a # 直接赋值
b = b + " world" # 拼接创建新字符串,b 贴到新对象
print(a) # ?
print(a is b) # ?
3. 不可变对象的标签转移
t1 = 100
t2 = t1
t2 = t2 + 1 # int 加法创建新对象,t2 贴到新对象
print(t1 is t2) # ?
print(t1) # ?
2.3 del 不是删除,是解除绑定
大多数人刚接触 del 时,会本能地把它理解成“删除数据”。比如:
a = 42
del a
print(a) # NameError: name 'a' is not defined
看起来确实像是 a 这个变量消失了,数据也没了。但真相远比这微妙。
del 的本质:撕掉一张标签
del 做的事情非常简单——它只是解除了变量名与对象之间的绑定关系。它撕掉了一张贴在对象上的标签,但对象本身是否被清除,完全不归 del 管。
可以这样理解:
| 操作 | 通俗理解 |
|---|---|
a = [1, 2, 3] | 把一张写着 a 的标签贴到列表物件上 |
b = a | 再贴一张写着 b 的标签,贴在同一个物件上 |
del a | 撕掉写着 a 的那张标签 |
此时 b 仍在 | 物件还在,只是少了一张标签 |
所以 del 不是“销毁”操作,而是“解绑”操作。
用代码撕开真相
a = [1, 2, 3]
b = a
del a
# print(a) ← 这行会报错,因为名字 a 已经不存在了
print(b) # [1, 2, 3] ← 还能用 b 访问物件
a 被 del 之后,b 仍然能访问原来的列表,因为 del 只是撕掉了 a 这张标签,物件本身没有被影响。
那对象到底什么时候消失?
对象是在“没有任何标签贴在它身上”时,被 Python 的垃圾回收机制自动回收。这个机制叫引用计数:每个对象都记录着自己被多少变量引用(有多少张标签)。当这个数字降到 0 时,对象就会被释放。
import sys
a = [1, 2, 3]
b = a
c = a
print(sys.getrefcount(a)) # 引用计数:4(a、b、c + getrefcount 临时引用)
del b
print(sys.getrefcount(a)) # 引用计数减 1
del a
del c
# 此时引用计数归零,对象被回收
del 只负责把引用计数减一,真正决定对象生死的是引用计数本身。
一个常见的误解:del 清空容器
你可能见过这样的代码:
lst = [1, 2, 3]
del lst[0] # 从列表中移除第一个元素
print(lst) # [2, 3]
这里的 del 看起来像是在“删除”列表里的元素。实际上,del lst[0] 等价于调用列表的 __delitem__ 方法,执行的是“从容器中移除对第一个元素的引用”。如果这个元素没有别的变量引用它,它才会被回收。
小整数缓存池:del 不适用
还记得前几个小节提到的小整数缓存池吗?CPython 在启动时预先创建了 -5 到 256 之间的所有整数对象,并一直保留它们,供所有变量共享。
这意味着,即使你 del 了一个指向小整数的变量,整数对象本身也不会被回收,因为解释器内部一直保留着对它的引用。
a = 256
del a
# 整数 256 依然在缓存池中常驻,无法被回收
实操验证
# 1. 基础验证:撕标签
a = [1, 2, 3]
b = a
del a
print(b) # 输出是什么?为什么?
# 2. 验证 del 不影响其他引用
c = [4, 5, 6]
d = c
del d
print(c) # 输出是什么?
# 3. del 容器内的元素 vs del 变量
lst = [1, 2, 3]
x = lst[0]
del lst[0]
print(x) # x 还在吗?
print(lst) # lst 变成什么了?
# 4. 验证引用计数变化
import sys
obj = [1, 2, 3]
print(sys.getrefcount(obj)) # 看引用计数
ref = obj
print(sys.getrefcount(obj)) # 增加了吗?
del ref
print(sys.getrefcount(obj)) # 回来了吗?
重点回顾
| 常见的误解 | 真实的情况 |
|---|---|
del 是删除数据 | del 只是解除变量与对象的绑定关系 |
del 会立刻释放内存 | 释放内存取决于引用计数是否归零 |
del 能删除任何东西 | del 只能作用于当前命名空间或容器内可解绑的项 |
思考一下:如果有两个变量 a 和 b 指向同一个对象,先 del a,再 del b,对象在哪个时刻被回收?用代码验证你的答案。
2.4 本节小结
这一节我们拆开了变量系统的几层真相,用“标签”和“物件”这套比喻来理解:
==看值,is看身份:==比较两个物件长得像不像(内容),is比较是不是同一个物件(身份)。绝大多数情况用==,判断None时用is。小整数缓存池和字符串驻留是 CPython 的内部优化,就像提前准备好的标签贴纸,心里有数就行,但永远不要依赖它们来写代码。- 赋值是贴标签,不是复制物件:
b = a不会把a的值复制一份,而是再写一张标签,贴到a所指的那个物件上。所以通过b修改可变物件,a也会跟着变。浅拷贝是复印通讯录——外层纸张是新的,但里面的人还是同一批;深拷贝才是连人带电话全复制一份。 del是撕标签,不是砸物件:del只负责撕掉一张标签。物件会在所有标签都被撕掉之后,由 Python 自动回收。
总结:变量是标签,赋值是贴标签,比较是看内容或身份,删除是撕标签,拷贝是贴新标签到新物件上(或递归地贴)。
3. 本章小结
本章分为两节,完成了从"会用"到"懂原理"的跨越。
3.1 总结本章全部内容
第1节:基础概念
你学会了三个最基本的概念:
- 变量是带标签的盒子。 用
=把值存进去,可以随时修改。变量名有硬性规定(不能以数字开头、不能是关键字)和软规范(蛇形命名、有意义)。 - 类型决定能做什么。
int、float、str、bool是四种最常用的基础类型。Python 是动态强类型语言——变量的类型随值而变,但不同类型之间不能乱操作。学会用type()查看类型,学会读TypeError。 - 表达式是能算出结果的代码。 算术运算、比较运算、逻辑运算都是表达式。区分两个关键:
=是赋值语句(不产出结果),==是比较表达式(产出True或False)。is用来判断是否为同一个对象,但日常开发中绝大多数场景用==就够了。
第2节:底层真相
这一节用"变量是标签,不是盒子"这个比喻,揭开了变量系统的内幕:
is看身份,==看值。a == b比较两个物件长得像不像(内容),a is b问是不是同一个物件(身份)。小整数缓存池是 CPython 的内部优化——-5到256之间的整数像提前准备好的标签贴纸,谁要用就拿一张。了解就行,但永远不要依赖它来写代码。- 赋值是贴标签,不是复制物件。
b = a不会复制a的值,而是拿一张新标签b,贴到a所指的那个物件上。所以通过b修改可变物件,a也会跟着变。浅拷贝像复印通讯录——纸张是新的,但里面的人还是同一批;深拷贝才是连人带电话全复制一份。 del是撕标签,不是砸物件。del只解除变量与对象的绑定关系,对象本身是否被回收,取决于还有没有其他标签贴着它。当所有标签都被撕掉,Python 会自动回收。
贯穿始终的一条线:变量是标签,赋值是贴标签,比较是看内容或身份,删除是撕标签,拷贝是贴新标签到新物件上(或递归地贴)。
3.2 附录:常用类型及特性
| 类型 | 示例 | 可变? | 有序? | 说明 |
|---|---|---|---|---|
int | 42, -7, 0 | 否 | — | 任意大小整数,运算:+ - * / // % ** |
float | 3.14, -0.5, 1e10 | 否 | — | 双精度浮点数。注意:0.1 + 0.2 != 0.3 |
bool | True, False | 否 | — | 布尔值,bool 是 int 的子类 |
str | "hello", '你好' | 否 | 是 | Unicode 字符串,常用:+ * .upper() .split() |
list | [1, 2, 3], ["a", "b"] | 是 | 是 | 可变序列,最常用的容器 |
tuple | (1, 2, 3), ("a",) | 否 | 是 | 不可变序列,可作字典的键 |
dict | {"name": "Sam", "age": 18} | 是 | 否 | 键值对映射,键必须不可变,查询极快 |
set | {1, 2, 3}, {"a", "b"} | 是 | 否 | 无序不重复集合,支持交集并集差集 |
frozenset | frozenset({1, 2, 3}) | 否 | 否 | 不可变的 set,可作字典的键 |
NoneType | None | 否 | — | 表示"空",判断时用 is None |
可变与不可变速查
| 可变(内容可原地修改) | 不可变(修改=创建新对象) |
|---|---|
list, dict, set | int, float, str, tuple, frozenset, bool |
类型判断
type(x) # 返回类型对象
by 澜湾
by LanWan