目录
- 变量与数据类型
- 变量
- 数据类型
- 声明变量
- 变量命名规则
- 多重赋值
- 类型注释
- Pylance[^4]
- 运算符
- 算术运算符
- 比较运算符
- 逻辑运算符
- 位运算符[^8]
- 成员运算符
- 身份运算符
- 拼接运算符
- 集合运算符
- 浮点数精度问题
- 隐式类型转换
- 类型提升
- 表达式与赋值运算符
- 符号运算
- 运算后赋值运算符
- 复合表达式
- 海象运算符[^19]
- 流程控制
- 代码语句
- 执行顺序
- 缩进和代码块
- 关键字
- 占位语句
- 条件语句
- 嵌套条件
- 三元表达式
- 模式匹配
- 模式守卫
- 循环语句
- 循环控制语句
- else 子句 - 循环语句
- 异常处理
- 异常处理语句
- else 子句 - 异常处理语句
变量与数据类型
变量
变量是用于存储数据的开发者自定义的名称,类似于一个地址,唯一标识程序中数据的位置。
数据类型
数据有多种类型:
数据类型 | 数据 |
---|---|
int | 1、0b1010(二进制数)、-2333等整数 |
float | -0.8、2e-10(科学计数法)等小数 |
complex | 1+2j、0.125j等复数 |
bool | 仅有真值True或False |
str | “Hello Python”、‘It is an apple’、’’‘I have a dream’’'等由双引号、单引号或三引号包裹的字符序列 |
list | [1,2,3]、[“苹果”,“香蕉”]、[True,True,False]等用方括号[]定义的,用,隔开元素的元素序列 |
tuple | (0,0,1)、(‘v_x’,‘v_y’)等用圆括号()定义的,用,隔开元素的元素序列,与list的区别在于不可增、不可删、不可改其元素序列中的元素 |
dict | {‘李明’:23, '王红:25, ‘张燕’:27}、{True:False, False:True}等用花括号{}定义的,用,隔开键值对的键值对集合 |
set | {‘x’,‘y’}、{0,1,2,6}等用花括号{}定义的,用,隔开元素的元素集合 |
frozenset | 同set,但是不可增、不可删、不可改其元素集合中的元素 |
range | 用range()函数定义的数据类型的数据 |
bytes | 如b’hello’、b’\x01\x02’等不可变的二进制数据 |
bytearray | 同bytes,但是可增、可删、可改其元素集合中的元素,由bytes数据类型的数据通过bytearray()函数构造 |
NoneType | 仅有None一个值,表示空或缺失 |
声明变量
变量可存储不同类型的数据,而且可随时改变。
x = 10 # 变量 x 存储 int 数据类型的数据 10
x = "Hello" # 变量 x 改存储 str 数据类型的数据 "Hello"
变量命名规则
- 合法字符:字符/下划线开头,后面可接字母、数字、下划线
- 大小写敏感:如 var 和 Var 被视为不同变量
- 保留字禁止1:不能使用Python的保留字命名变量
- 命名惯例2:
- 小写+下划线,如 user_name
- 常量全大写,如 MAX_SIZE
多重赋值
变量允许两种简便的声明方式:
x, y, z = 1, 2, 3 # 并行赋值
### 相当于
# x = 1
# y = 2
# z = 3
a = b = c = 1 # 链式赋值
### 相当于
# a = 1
# b = 1
# c = 1
类型注释
如果希望提醒开发者,你希望某些变量只存储固定的数据类型的数据,你需要用到类型注释3。
x:int # 直接声明,无需声明时赋值
x = 3 # 自然是可以赋值 int 数据类型的数据的
x = "example" # 但是赋值 str 数据类型的数据也不会报错,因为类型注释本质上是一种注释
建议对重要的变量添加类型注释。
Pylance4
如果你使用VS Code,你可以利用它丰富的插件来帮助你强制开发者给变量赋值类型注释所要求的数据类型,那就是使用Pylance5插件,安装后,如果代码有类型注释,出现类型错误问题时会直接实时高亮显示。
运算符
算术运算符
符号 +、-、*、/、//、%、** 用于算术运算,被称为算术运算符:
- bool 数据类型的数据在参与算术运算时,会转换为 int 数据类型的数据,其中True 转换成 1, False 转换成 06;
- int 数据类型的数据在参与 / 运算时,会转换成 float 数据类型的数据;
- int 数据类型的数据,在与 float 数据类型的数据一起进行算术运算时,会转换为 float 数据类型的数据;
- float数据类型的数据在参与算术运算时,存在精度问题,如0.1+0.2的结果为0.30000000000000004;
- int 数据类型的数据与与 float 数据类型的数据,在与 complex 数据类型的数据一起进行算术运算时,会转换为 complex 数据类型的数据;
- complex 数据类型的数据在参与 / 运算或 ** 运算时,int 数据类型的实部或虚部会转换为 float 数据类型的实部或虚部;
- complex 数据类型的数据不能参与 // 运算或 % 运算;
- 其余数据类型不能参与算术运算。
比较运算符
符号 >、<、>=、<=、==、!= 用于比较运算并返回bool数据类型的数据,被称为比较运算符:
- bool 数据类型的数据在参与比较运算时,会转换为 int 数据类型的数据,其中True 转换成 1, False 转换成 06;
- int 数据类型的数据,在与 float 数据类型的数据一起进行比较运算时,会转换为 float 数据类型的数据;
- int 数据类型的数据与与 float 数据类型的数据,在与 complex 数据类型的数据一起进行比较运算时,会转换为 complex 数据类型的数据;
- complex 数据类型的数据只能参与**==** 运算或 != 运算。
- 其余数据类型的数据只能参与**==** 运算或 != 运算。
逻辑运算符
符号 and、or、not 用于逻辑运算,被称为比较运算符:
- 非 bool 数据类型的数据在参与逻辑运算式,会转换为 bool 数据类型的数据,其中0、0.0、0j、""、[]、()、{}、set()、range(0) 在参与逻辑运算时,会转换为 False,其余会转换为 True;
- a and b,若a可转换为False,结果为a7;
- a or b,若a可转换为True,结果为a7;
- not a,若a可转换为True,结果为False,否则True7。
位运算符8
符号 &、|、^、~、<<、>> 用于位运算,被称为位运算符9:
- bool6 数据类型的数据与非二进制数 的 int 数据类型的数据在参与位运算时作为 &、|、^、~ 运算的 操作数,会转换为 二进制数 的 int 数据类型的数据10;
- bool6 数据类型的数据与非二进制数 的 int 数据类型的数据在参与位运算时作为 <<、>> 运算的 左操作数,会转换为 二进制数 的 int 数据类型的数据10;
- <<、>> 运算的右操作数必须是 int 数据类型的数据;
- 其余数据类型的数据不能参与位运算。
成员运算符
符号 in 与 not in 用于检查元素是否在容器中,被称为成员运算符:
- 一个str 数据类型的数据是另一个 str 数据类型的数据的连续部分11,参与 in 运算时返回 True12 ,不是则返回 False;
- 一个str 数据类型的数据是另一个 str 数据类型的数据的连续部分11,参与 not in 运算时返回 False13 ,不是则返回 True;
- 一个不可变元素是另一个由不可变元素 构成的list、tuple、set、frozenset、range数据类型数据的成员,参与 in 运算时返回 True,不是则返回 False;
- 一个不可变元素是另一个由不可变元素 构成的 list、tuple、set、frozenset、range数据类型数据的成员,参与 not in 运算时返回 False,不是则返回 True;
- 一个bytes 或 bytearray 数据类型的数据是另一个 bytes 或 bytearray 数据类型的数据的连续部分11,参与 in 运算时返回 True14 ,不是则返回 False;
- 一个bytes 或 bytearray 数据类型的数据是另一个 bytes 或 bytearray 数据类型的数据的连续部分11,参与 not in 运算时返回 False15 ,不是则返回 True。
身份运算符
符号 is、is not 用于判断两个数据的存储地址是否相等,被称为 身份运算符:
- 小整数池(-5 到 256)中的 int 数据类型的数据会被Python 缓存,两个变量赋值相同的这类数据,地址是相同的,与该数据也是地址相同的;
- 某些 str 数据类型的数据可能会有字符串驻留的现象,Python会自动缓存满足某种 标准16 的str 数据类型的数据,两个变量赋值这类数据,地址是相同的,与该数据也是地址相同的;
- Python会自动缓存满足某种 标准17 的某些不可变数据,两个变量赋值这种数据,地址是相同的,与该数据也是地址相同的;
- 使用链式赋值的几个变量,地址是 相同的;
- 被Python以某种 机制18 缓存 的数据,与赋值该数据的变量是地址相同的,与本身也是地址相同的;
- 两个赋值True、False或None的变量,地址是相同的,与该数据也是地址相同的;
- 若两个变量地址是相同的,参与 is 运算的结果是 True,否则是 False;
- 若两个变量地址是相同的,参与 is not 运算的结果是 False,否则是 True。
拼接运算符
符号 + 用于拼接元素序列,被称为拼接运算符:
- 只有str、list、tuple、bytes、bytearray 数据类型的数据才参与拼接;
- bytes 数据类型的数据与,bytearray 数据类型的数据参与拼接时,会转换为 bytearray 数据类型的数据;
- 按照拼接顺序首尾相接;
- 拼接的结果存储在新的 地址 中,不与任何参与拼接的数据的地址相同。
集合运算符
符号 &、|、-、^ 用于 集合运算,被称为 集合运算符:只有 set、frozenset 才能参与集合运算。
浮点数精度问题
计算机使用 二进制数 存储 float 数据类型的数据,而大多数 十进制小数 在二进制中是 无限循环小数。
Python默认使用的 IEEE 754 双精度浮点数19,使用64位存储 float 数据类型的数据,即 1 位符号位,11 位指数位,52 位有效数字位,二进制无法精确表示无限循环小数,只能截断或舍弃,导致精度丢失。
0.1 + 0.2 == 0.3 #结果为 False
0.1 + 0.2 #结果为 0.30000000000000004
1e16 + 1.0 == 1e16 #结果为 True
因此最好不要 直接比较 float 数据类型的数据。20
隐式类型转换
在Python中,隐式类型转换是指程序在 运行时 自动 将一种数据类型的数据 转换成 另一种数据类型的数据,以上存在许多例子,不再赘述。
算术运算 和 比较运算时,转换次序为 bool → int → float → complex 目的是为了 保证运算的精度。
拼接运算时,转换次序为 bytes → bytearray。
逻辑运算 和 位运算 时,没有转换次序,见逻辑运算符 与 位运算。
类型提升
有些运算符会 提升 某些数据类型的数据的数据类型,即使并没有遇到触发隐式类型转换的另一个操作数,只有 / 运算中有 int 数据类型的数据或 complex 数据类型的数据的 实部 或 虚部 是 int 数据类型的数据时,才会有这种情况。
表达式与赋值运算符
= 就是Python的赋值运算符,用于赋值变量,赋值 是唯一没有运算结果的运算,或者说它的运算结果是 None。
操作数 与 运算符 的结合被称为 表达式。
可以把表达式当做数据赋值给变量。
x = 1 + 2 #表达式为 1 + 2
符号运算
变量可以当做操作数参与运算,此时它的值是它存储的值,它的数据类型是它的值的数据类型:
x = 1
y = x + 2
其中,将一个变量赋值给另一个变量,两个变量的地址是相同的;
x = 1
y = x
y is x # 结果是 True
运算后赋值运算符
将表达式当做数据赋值给变量可以被简写,以下是简写方式:
x = 1
x += 2 # 加后赋值运算符
### 等价于:
# x = 1
# x = x + 2
y = 1
y -= 2 # 减后赋值运算符
### 等价于:
# y = 1
# y = y + 2
z = 1
z *= 2 # 乘后赋值运算符
### 等价于:
# z = 1
# z = z + 2
w = 1
w /= 2 # 除后赋值运算符
### 等价于:
# w = 1
# w = w + 2
# 以此类推
所有算术运算符、拼接运算符、集合运算符 都有对应的 运算后赋值运算符,位运算符 中只有 ~ 运算符 没有 对应的 运算后赋值运算符,&、|、^、<<、>> 都有对应的 运算后赋值运算符,剩下来的所有运算符都没有对应的运算后赋值运算符。
复合表达式
表达式 也可以被视作 操作数 参与构建表达式,此时它的运算结果的值就是它的值,它的运算结果的数据类型就是它的数据类型,它们的运算被称为 复合运算 ,它们与 运算符 的结合被称为 复合表达式。
x = (1 + 2) * 3
### 等价于
# x = 1 + 2
# x *= 3
海象运算符21
Python 3.8+ 以上版本引入了 海象运算符,是一种唯一同时具有赋值和运算结果能力的运算符,严格来说是一种赋值运算符。
:= 是 海象运算符,有这个名称的原因在于它长得像一只躺倒的海象。
y = (x := 1) + 2
### 海象运算
# x 此时存储 int 数据类型的数据 1
# 而 x := 1 运算的结果与 x 存储的数据一样
# 代码等价于
# x = 1
# y = x + 2
流程控制
代码语句
代码语句 是程序在 运行 过程中,被 处理 的代码22,明确告诉解释器,它现在应该做什么。
执行顺序
代码在程序运行过程中,处理代码的顺序被称为 执行顺序,Python是严格按照 书写顺序 严格处理代码的,因此Python的 执行顺序 是 从上到下 的。
缩进和代码块
在Python中,缩进 和 代码块是互相定义的,因此我们放在一起讲。
缩进23 标记 代码块 的 开始 和 结束,代码块 是一组按照 缩进 组织在一起的 代码语句,它们作为一个 整体 被处理。
x = 1
x += 2 # 一个代码块内的代码语句
x = 1 # 另一个代码块内的代码语句
注意,Python默认按书写顺序处理它遇到的第一个代码块,因此缩进不一致的代码不会被处理,还会抛出 IndentationError。
关键字
关键字 是Python的 保留字,它类似自然语言中的 助词,被用来 辅助构建 Python代码的 语法结构。
占位语句
pass 是Python的 关键字,也是Python的代码语句,被称为占位语句,表示程序处理这条语句时不应该做任何操作。
pass
条件语句
if、elif、else 是Python的 关键字。if 或 elif、表达式 与 : ,或者 else 与 : 的结合被称为 条件语句 。
相同缩进的 条件语句,从 if 开始,中间要求 不存在 同缩进的 非条件语句 或其他以 if 开头的条件语句,则为同一族 语句序列。其中,当中若存在 else,则必定唯一且为语句序列的最后一条语句。
if ((x := 3) ** 2) % 3 == 0:
pass
elif (x ** 2) % 3 == 1:
x += 2
else:
x += 1
### 这里的
# if ((x := 3) ** 2) % 3 == 0:
# elif (x ** 2) % 3 == 1:
# 和 else:
# 是同一族的语句序列
# 而以下的:
# if ((x := 3) ** 2) % 3 == 0:
# 和 elif (x ** 2) % 3 == 1:
# 是同一族的语句序列
# else: 不属于任何一族语句序列
if ((x := 3) ** 2) % 3 == 0:
pass
elif (x ** 2) % 3 == 1:
x += 2
x += 1
else:
pass
#
若存在不属于任何一族语句序列的 条件语句,那么这条条件语句是 不合法 的。
条件语句 可改变程序的 执行顺序,解释器遇到一条if开头的条件语句,则从与这条条件语句同一族条件语句序列开始开始检查,当遇到第一个表达式的运算结果可转换为 True时,执行该语句下的第一个代码块,如果找不到但是有else 开始的条件语句,则执行它下面的第一个代码块。执行完则跳出该族序列直到遇到第一条同缩进 的 代码语句。
if ((x := 3) ** 2) % 3 == 0:
pass
# 执行 if ((x := 3) ** 2) % 3 == 0:
# 之后的第一个代码块,也就是这里
elif (x ** 2) % 3 == 1:
x += 2
else:
x += 1
# 执行完则跳到这里
嵌套条件
条件语句块 可以 多层嵌套,这是由语句块的定义所决定的。
if (x := 25) >= 18:
if y := True:
pass
else:
pass
else:
pass
三元表达式
表达式1、if、表达式2、else 与 表达式3 的结合被称为 三元表达式。三元表达式 不改变执行顺序,它有运算结果,是表达式,若表达式2的运算结果可转换为True,则三元表达式的运算结果为表达式1的运算结果,否则为表达式3的运算结果。
x = -5
y = '正数' if x > 0 else '负数'
模式匹配
match和case是Python的关键字。match、操作数 与 :,或者case、操作数 与 : 构成 match-case语句。
case开头的match-case语句必须处在match开头的match-case语句下的第一个语句块之中。match开头的match-case语句下的第一个语句块中只能有case开头的match-case语句及其语句块。
match x := 3:
case 1:
match r := 1:
case x:
pass
case 2:
if (y := 6) > x:
pass
case 3:
x += 3 if x == 3 else 0
### 这说明了
# 它并不关心
# case 开头的 match-case 语句下的
# 语句块里有什么内容
match-case 语句 会改变执行顺序,当解释器遇到 match 开头的 match-case 语句 时,它会逐个扫描它下面第一个语句块的所有case开头的match-case 语句。
若一条case开头的match-case 语句中的操作数的值与match 开头的 match-case 语句中的操作数的值相同,则进入它下面的第一个语句块并执行。
随后跳出match 开头的 match-case 语句下第一个语句块,并继续按顺序执行直到遇到与match 开头的 match-case 语句同缩进的代码语句。
match value := 2:
case 1:
value += 2
case 2:
value += 1
# 进入这里
# 然后到这
如果一个也 匹配不到,可以选择添加一条默认匹配语句,case 、_ 与 :的结合构成一条match-case语句,表示 如果一切都不匹配,则匹配这条:
match a := 1:
case _:
pass
# 没有一个case开头的
# match-case语句的操作数的值是 1
# 进入这里
模式守卫
case、操作数、if、表达式 与 : 构成一条 match-case语句,表示若,操作数 匹配且 表达式 的运算结果可转换为 True,则匹配这条:
r = 2
a = 0
match pi := 3.14:
case 3.14 if r == 1:
a = pi * r ** 2
case 3.14 if r == 2:
a = pi * r ** 2
# 匹配这里
循环语句
for、in 是Python的 关键字。for、变量、in、可迭代数据类型的数据24 与 : 构成一条 循环语句。
for 开头的 循环语句 会改变 执行顺序,in会逐一从可迭代数据类型的数据 取 数据给变量赋值,并每次来回进入它下面第一个代码块,直到取完跳出到与它下面同缩进的第一条语句:
t = 0
for s in 'apple':
if s == 'p':
t += 1
# 一次取完 'a'、'p'、'p'、'l'和'e'
# 跳到这里
while 是Python的关键字。while、表达式 与 : 的结合是一条 循环语句。
while 开头的循环语句会改变执行顺序,只要表达式的结果可转换为True,重复进入执行它下面第一个代码块,直到表达式的结果可转换为 False跳出到与它下面同缩进的第一条语句:
p = 0
while p < 100: # 直到 p >= 100
p += 1
# 重复进入这里
# 然后到这
循环控制语句
循环语句 不总是需要执行完才能 退出,如果想要中途退出,可以使用 循环控制语句。continue 和 break是Python的关键字,也是循环控制语句,以下是用法:
y = 0
for x in [1, 2, 3, 4]:
if x == 1:
continue
### 如果满足:
# 则结束这次执行
# 跳到下一次执行
y += 1
if x == 3:
break # 如果满足,则跳出循环
else 子句 - 循环语句
你知道,循环语句 的 重复操作 完了之后,我们可能有一些善后操作。原本你可以直接在跳出循环后执行那些操作,但是你的代码可能存在一些中断操作,提前在完成之前离开,这对你来说不是很妙。
因此,Python引入了 else 子句,else 与 : 的结合是 else 子句,代表如果循环正常结束,则执行 else 子句下的第一个代码块:
sign = False
d = {'张三':23, '李四':24, '王五':25}
for k in d:
if k == '赵六':
break
else: # 没有赵六,正常结束
d['赵六'] = 26 # 添加赵六
for k in d:
if k == '赵六':
break # 有赵六,跳出循环
else:
d['赵六'] = 26 # 不会进入这里
异常处理
写Python脚本不可能总是不出错误,于是Python有很多异常类型,来告知你或者其他用户,操作中可能出现了预期之外的异常情况。
异常类型 | 含义 |
---|---|
ValueError | 值不符合预期 |
TypeError | 操作应用到不适当数据类型的数据 |
IndexError | 索引超出序列范围 |
KeyError | 字典键不存在 |
FileNotFoundError | 文件未找到 |
ZeroDivisionError | 除以零 |
Exception | 反正就是发生了异常情况 |
异常处理语句
try、except 和 finally 是Python的关键字。try 与 :,或者 except、异常类型 与 :,或者finally 与 : 的结合被称为 异常处理语句。
它们的行为 类似 match-case 语句25,但是有所不同。match 开始的 match-case 语句 需要 操作数,而 try 开始的 异常处理语句 不需要。
try 开始的 异常处理语句 蕴含着如果它下面的代码块有 异常 与 某个except 开始的 异常处理语句 中的异常类型 匹配,就执行这个 except 开始的 异常处理语句 下的第一个代码块。
try:
x = 1 / 0
# 除于 0 会引发 ZeroDivisionError
except ZeroDivisionError:
x = None # 不再报错,代码可以继续进行
# 然后跳到这里
它多了一条 finally 开始的 异常处理语句,这是 match-case 语句 所没有的,它是一种 收尾操作,无论有没有发生 异常情况,都要执行的代码块。
你可能会疑惑,为什么无论有没有发生异常,都要执行finally代码块的内容。这是因为:你永远都不知道你的代码块里还有多少异常。
而异常是会阻塞代码执行的。
try:
x = 1 / 0
except ZeroDivisionError:
x = None
x = None
# 假设你就是想让 x 是None,特别不讲理
这时候,如果代码还有其他错误,就会阻塞你的代码,不能继续执行。26因此如果你无论如何都想执行某段操作,请放在finally开始的异常处理语句下的第一个代码块里。
else 子句 - 异常处理语句
你知道,如果你的代码没有异常,你肯定有想要在你的代码正常发挥功能后,添加一些必要的代码。但是事不遂人意,你的代码可能发生了异常,而你想添加的代码本应在你的代码发挥正常功能后继续执行,但是它因异常阻塞了。
于是你添加到了 finally 开始的异常处理语句下的第一个代码块里,却发现 更糟了。因为你的需求是,在你的代码正常发挥功能后,继续执行 接下来的代码,而不是 发生了异常后还要继续执行,因此你十分懊恼。
Python很贴心的给你准备了 else子句,它和此前在条件语句和循环语句中的形式一样,功能不尽相同:
try:
x = 1 / 2
except ZeroDivisionError:
x = None
# 除零错误,虽然理应不会发生
# 进入过这里
# 就不会再进入 else 子句下面的
# 第一个代码块
else:
y = 1
# 如果没有发生异常
# 顺利到这里
# 因除零错误到这里
在学习到保留字部分之前,目前你不需要知道有哪些Python保留字 ↩︎
不是强制性的,事实上不按照惯例来命名变量,并不会有错误,但是会引起同行的不满 ↩︎
类型注释在 Python 3.5 之前的版本是没有的,它正式引入于 Python 3.5,此后的版本均可以使用 ↩︎
不是必须看的部分,跳过也可以 ↩︎
Pylance插件深度集成了Pyright,Pyright是用TypeScript编写的静态类型检查工具,不过,Pylance的功能不仅限于此,后面将有对Pylance更多的应用 ↩︎
事实上由于Python的应用领域不在底层,位运算符用得很少 ↩︎
负数的二进制以补码形式表示,位运算时会自动处理符号位 ↩︎
空字符串恒为True ↩︎
空字符串恒为False ↩︎
空比特串恒为True ↩︎
空比特串恒为False ↩︎
标准不明,空字符串、长度为1的ASCII字符一定会有字符串驻留的情况,长度为≤20的ASCII字符串、编译期确定的字符串字面量火满足标识符规则的字面量可能有字符串驻留的情况,动态生成的字符串、长度超过20个字符的ASCII字符串或含有特殊字符的字符串通常没有字符串驻留的情况,需要查询相关文档 ↩︎
标准完全不明,需要查询相关文档 ↩︎
以上提到的机制,以及其他某些机制,需要查询相关文档 ↩︎
即float数据类型 ↩︎
有有效的解决方法,在之后会讲到 ↩︎
这个部分可以跳过不看,除非你遇到需求 ↩︎
所有表达式都是代码语句 ↩︎
通常用四个空格缩进 ↩︎
指所有实现了iter()方法或getitem()方法的对象,str、list、tuple、dict、set、frozenset、range、bytes、bytearray都是可迭代的数据类型 ↩︎
而且 try、except 和 finally 要求缩进相同,这又有些 类似条件语句。 ↩︎
你的这段代码可能有其他异常,但是你的这段代码有其他异常又不太可能 ↩︎