Python 教程

第 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() 来查看它的类型。

四种基础类型一览表

类型字面量示例核心用途典型操作
int42, -7, 0计数、索引、离散量+ - * // %
float3.14, -0.5, 1e10测量、比例、连续量+ - * /
str"hi", 'ok', ""文本、标识符、消息+(拼接), *(重复), .upper()
boolTrue(真/对), 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

完成以上验证后,请尝试以下练习:

  1. 计算 17 // 5 和 17 % 5,验证 (17 // 5) * 5 + (17 % 5) 是否等于 17。
  2. 定义两个内容相同的列表 a 和 b,分别用 == 和 is 比较它们,观察并解释结果差异。
  3. 猜测 "" or 0 or None or "found" 的结果,然后用 print() 验证。

1.4 本节小结

在本节中,我们构建了 Python 编程最基础的三块基石:

  1. 变量是带标签的盒子:它通过 = 将值与名称绑定,让我们能够动态地存储和复用数据。记住变量名的硬性规则与蛇形命名的软规范,这是写出可读代码的第一步。
  2. 类型是操作的说明书:int、float、str、bool 四种基础类型决定了数据“能做什么”。Python 是动态强类型语言,变量的类型随值而变,但跨类型的非法操作会被严格禁止。学会阅读 TypeError 和使用 type() 是排查问题的基本功。
  3. 表达式是产出值的代码单元:无论是算术运算、比较判断还是逻辑组合,表达式的核心特征是“能算出一个结果并被 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 附录:常用类型及特性

类型示例可变?有序?说明
int42, -7, 0否—任意大小整数,运算:+ - * / // % **
float3.14, -0.5, 1e10否—双精度浮点数。注意:0.1 + 0.2 != 0.3
boolTrue, 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"}是否无序不重复集合,支持交集并集差集
frozensetfrozenset({1, 2, 3})否否不可变的 set,可作字典的键
NoneTypeNone否—表示"空",判断时用 is None

可变与不可变速查

可变(内容可原地修改)不可变(修改=创建新对象)
list, dict, setint, float, str, tuple, frozenset, bool

类型判断

type(x)                # 返回类型对象

by 澜湾
by LanWan