跳至主要內容

Python 语法总结

大约 43 分钟

Python 语法总结

参考教程 https://docs.python.org/zh-cn/3/tutorial/index.htmlopen in new window

基础语法

基本语法

环境说明

  • Python 脚本需要在第一行使用 #!<解释器路径> 声明脚本使用的解释器 (来自 Linux 系统的规范), 通常使用
    • #!/usr/bin/python 使用安装在默认位置的 Python
    • #!/usr/bin/env python 在环境变量中查找 Python
  • Python 脚本需要在第二行声明脚本文件所使用的编码类型, 通常使用 utf-8, 因此使用如下方式声明
    • # -*- coding: UTF-8 -*-

行和缩进

  • Python 中通过缩进区分语句的层级, 最低层级的语句不需要缩进
  • 缩进可以是单个制表符, 两个空格或四个空格, 但在一个 Python 脚本中需要统一
  • 定义容器[], {}() 内的内容可以任意换行且没有缩进要求
  • 可以使用反斜杠 \ 将一行的语句分为多行显示, 第二行没有缩进要求
  • 同一行中使用多条语句时, 语句之间使用分号 ; 分割

注释

  • python中单行注释采用 # 开头
  • python 中多行注释使用三个单引号 ''' 或三个双引号 """ 包裹注释内容

变量定义

  • 通过 = 运算符未一个不存在的变量赋值时, 将自动创建变量
  • 使用变量前应当通过赋值的方式声明, 如果不能确定可以赋 None
  • 当变量名由大写字母与下划线时, 表示常量

运算符

算数运算

符号含义符号含义
+加法运算*乘法运算
-减法运算/除法运算
%求余运算**幂运算
//整除运算, 向 0 取整

赋值运算符

符号含义解释
=赋值通过赋值运算即可创建新的变量
+=运算赋值将右侧的值与被赋值变量运算后赋值, 可用任意算数运算符替换 +
:=海象赋值赋值同时将右侧的值作为结果返回, 可用于简化代码, 例如 (n := 5) > 5

比较与逻辑运算

符号含义符号含义
==等于!=不等于
>大于<小于
>=大于等于<=小于等于
and逻辑与 (短路求值)or逻辑或 (短路求值)
not逻辑非

0, 空字符串, False 表示逻辑假
其余数值与字符串, True 表示逻辑真

位运算

将参与运算的变量视为二进制
最好仅用于整数

符号含义符号含义
&按位与|按位或
^按位异或~按位取反
<<左移运算符>>右移运算符

特殊运算符

符号含义解释
is身份运算符判断两个变量是否引用同一个对象
in成员运算符运算 b in a 中, a 为一个字符串或元组等容器, 当容器中有元素与 b 相同时, 返回 True

流程控制

if 条件语句

if <条件1> :
    <语句1>
elif <条件2> :
    <语句2>
else: 
    <语句3>
  • 分支语句 elseelif 不是必须的
  • 允许有任意个 elif

match 条件匹配语句

在 3.10 版本加入

match <val>:
    case <n1>:
        ...
    case <n2>:
        ...
  • val 用于条件匹配的变量
  • n1, n2 匹配表达式, 当匹配时, 将执行对应的语句
    • 可以是单个值, 如数值, 枚举值
    • 可以使用 | 连接多个值用于匹配
    • 可以使用 _ 表示与已有条件不匹配的情况
    • 可以使用带未知量的元组, 如 (x, 0) (也可以使用 [])
      • val 为一个相同形状的列表或元组, 且匹配除了未知数以外位置的元素时, 匹配成功
      • 匹配成功时未知量将提取对应位置的元素作为条件语句的临时变量
      • 在末尾使用 *rest 可提取剩余的部分 (可以为空) 到 rest, 且不再要求形状匹配 (也可使用 *_ 但不提取剩余部分)
      • 可在后接 if 规定提取变量的条件, 如 case (x, y) if x == y:
    • 可以使用带值的字典, 如 {"X" : x, "Y" : 0}
      • 匹配与提取类似元组, 但对于 val 额外的键没有匹配要求
      • 使用 **rest 可提取剩余键值对到 rest
  • 不同于 C++, 不需要使用 break 退出

for 循环语句

for <it> in <a>:
    ...
  • 要求 a 为一个可迭代对象, 如字典, 列表等, 可参见容器迭代
  • 每次循环都将从 a 中迭代一个值并赋给 it, 当 a 被迭代完时退出循环
  • 当迭代变量为序列如元组时可以有多个迭代变量, 赋值规则类似序列解包
  • 可使用 range 函数构造迭代对象, it : range(n) 表示循环 n

while 循环语句

while <exp>:
    ...
  • exp 为一个得到逻辑表达式, 当表达式为真将运行循环
  • 循环开始前先判断 exp, 当循环结束后再次判断, 直到 exp 结果为 False 退出循环

控制语句

  • break 跳出当前循环 (用于循环语句)
  • continue 结束当前循环进入下一次循环 (用于循环语句)
  • pass 表示空过程, 可用于函数, 分支, 循环语句中占位等待编写

实用函数与操作

生成整数序列

range([start, ]stop, step = 1)

  • start 序列开始, 包含在序列中
  • stop 序列末尾, 不包含在序列中
  • step 数列步长

要求

打印对象到控制台

print(*args)

  • args 将尝试将所有参数转换为字符串, 然后依次打印 (各个参数间以空格分割)

注意

获取控制台输入

input([s])

  • s 用于接收输入的提示字符

注意

  • 默认接收到的均为字符串, 如果希望转换为数值, 则需要类型转换, 如 n = float(input())
  • 可以接收并解析推导式

获取对象的长度

len(x)

该方法时获取任意类型长度的通用一般方法

删除变量与列表

del x

  • x 为一个变量时, 将删除该变量
  • x 为带索引的容器时, 将删除该元素

变量类型判断

isinstance(x, type) 判断变量 x 是否是类 type 的实例 (即判断类型)

  • x 变量名称
  • type 类型名称, 可参考类型与容器中的类型名称

类型与容器

Python 主要有以下基本内置类型

  • Numbers (数字)
    • int (有符号整型)
    • float (实数)
    • complex (复数) 使用 1j 表示虚数单位
    • bool (布尔)
  • str (字符串)
  • list (列表)
  • tuple (元组)
  • dict (字典)
  • set (集合)
  • None (无类型)

Python 中一切变量皆为对象, 一切类型皆为类, 因此使用这些类型对应名称的函数即对应的构造函数, 将其他参数传入构造函数 Python 将尝试进行变量转换, 如 int(x) 将尝试将 x 转换为整数类

数值类型

  • 对于实数 float, 除了小数点加数字, 还可使用以下方式表示特殊实数
    • infInfinity 表示正无穷
    • nan 表示非数值
    • E/e 表示 *10^, 如 1e-2 表示 0.01
  • 对于复数 complex
    • 可使用 complex(real = 0, img = 0) 构造
    • 使用 1j 表示虚数单位
    • 在将字符串转为复数时, 如 a+b1j 其中的加号不能有空格
  • 对于布尔类型 bool
    • 使用 True 表示真
    • 使用 False 表示假

字符串

字符串表示

  • 普通的字符串可使用成对的 "' 包含表示
  • 使用反斜杠 \ 可用于转义, 如 \n 表示换行, \" 表示字符串中的 "
  • 使用前缀 r 表示不进行转义, 通常用于表示正则表达式
  • 使用前缀 u 表示 Unicode 保存字符串, 默认使用 utf-8
  • 表示多行字符串语法如下
  • 使用单独一行的 """\ 开始, """ 结尾, 表示多行字符串

字符串常用操作

  • 整数 a 与字符串 s 的乘法运算将重复字符串 sa
  • 字符串之间的加法运算能拼接两个字符串
  • 使用 [] 运算符可索引字符串, 使用方法见序列类型, 注意字符串索引后不可修改
  • 成员方法 str.isnumeric() 字符串中至少有一个字符且所有字符均为数值字符则返回 True

格式化字符串

格式化字符串时, 字符串将根据给定的参数转换格式化符号, 得到实际字符串
使用以下方式表示格式化字符串

  • "...".format(...) 通过字符串的成员方法 format 传入参数, 格式化字符串
  • "..."%(...).format 替换为 %, 效果与 format 相同
  • f"..." 使用前缀 f, 此时将根据参数名称, 从当前环境中自动寻找变量

格式化字符串的表示方法见附录字符串格式化表示方法

bytes 对象

对于 Python 的字符串, 每个索引对应一个字符, 但根据编码不同字符的实际长度不同, 默认使用的是 utf-8 编码
与字符串类似的, Python 还提供了 bytes 对象, 该对象更类似于 C++ 中的字符串, 每个索引对应一个字节即 uint8 类型的值
通过该对象可用于实现访问二进制对象内存等操作

表示 bytes 对象时

  • 使用类字符串表示, 如 b"something", 可使用 ', ", ''' 包裹, 但内容只能是 ASCII 编码中已存在的字符
  • 通过构造函数 bytes(obj) 传入一般对象时, 将复制对象的底层内存数据并以此创建 bytes 对象
  • 通过函数 bytes.fromhex(str), 传入一个由多个 16 进制数对, 数队间使用空格分隔组成的字符串, 将尝试通过该字符串给出的内容创建 bytes 对象

一般类型与 bytes 对象之间转换

  • 对于字符串
    • 字符串转为 bytes 对象
      字符串对象方法 encode(encoding = "utf -8") 获取字符串内容在编码 encoding 下的二进制数据
    • bytes 对象转为字符串
      bytes 对象方法 decode(encoding = "utf-8") 获取二进制数据在编码 encoding 下的字符串内容
  • 对于一般 Python 对象与容器

列表

  • 列表为一个有序的容器, 即序列类型
  • 列表的内容可以任意修改, 增加或删除
  • 列表中可以有任意类型的内容, 但一般存放的是同一类型的元素

列表表示

  • 基本表示时, 使用 [] 包裹, 列表元素间使用 , 分隔, 如 [1, 2, 3]
  • 可使用列表推导式生成列表
  • 允许列表嵌套, 以此实现多维列表
  • 可直接使用 [] 表示空列表

列表常用操作

  • del list[n] 使用 del 语句删除索引为 n 的元素
  • list.append(x) 将变量 x 插入队列末尾 (注意对列不能访问不存在的元素, 因此一般使用此方法向队列插入元素)
  • list.pop() 获取队列末尾元素, 并删除
  • 由于列表属于序列类型, 索引方法见序列类型

元组

  • 元组为一个有序的容器, 即序列类型
  • 元组的内容可以不可修改
  • 元组一般用于存放多个不同内容的组合

元组常用操作

  • 元组的表示方式与列表表示相同, 但使用 () 包裹内容, 也可省略
  • 由于元组属于序列类型, 索引方法见序列类型, 注意元组索引后不可修改
    虽然不能覆盖元组中的元素, 但调用元组中元素的方法导致元素被改变是允许的
  • 序列类型的序列解包语法一般用于元组

字典

字典由一系列键值对组成
键与值可以时任意类型, 但一般以字符串作为键类型
键一旦创建后就不可修改, 但键对应的值可以修改

字典表示

  • 基本表示时, 使用 {} 包裹, 键值对使用 key : val 表示, 使用 , 分隔, 如 {"x" : 1, "y" : 2}
  • 对于简单的键值对, 可使用构造函数 dict(**kwargs) 构造, 将函数参数的字符串作为键, 如 dict(x = 1, y = 2)
  • 可使用字典推导式生成列表
  • 可直接使用 {} 表示空字典

字典常用操作

  • del dict[n] 使用 del 语句删除键为 n 的元素以及键
  • dict[n] = ... 修改键 n 对应的值, 如果不存在将自动创建
  • a in dict 对于字典的成员运算符, 当字典存在键 a 时返回 True
  • dict.keys(), dict.valuse() 获取字典的键或值用于字典迭代成员运算符 in, 不是获取列表
  • dict1 | dict2 合并两个字典, 键冲突时以 dict2 的值优先, 可使用此方法设置配置字典的默认值

容器的通用操作

对于容器类型, 有以下常用的通用操作

序列类型

对于字符串, 列表, 元组均属于序列 (Sequence) 类型, 均可使用 [] 运算符可索引字符串

  • 以正整数 a 为索引时, 表示索引第 a 个字符, 从 0 开始
  • 以负整数 -a 为索引时, 表示索引 len - a, len 为字符串长度, 如 -1 可索引最后一个字符
  • 使用 a:b 可切片索引字符串, 获取索引 ab 的子字符串 (不包括末尾 b)
    其中 a,b 可以省略
  • 索引越界时将出错, 但切片索引能自动处理越界

此外, 类似字符串的运算, 序列均存在运算

  • + 用于拼接两个序列
  • * 用于重复序列

序列解包语法, 即可将序列中个元素分别赋给一系列变量

  • 例如 a, b = (1, 2) 可将序列的第 0, 1 个元素分别赋给 a, b
  • 解包时使用 _ 表示空变量占位符, 此时该位置的列表元素将被丢弃
  • 解包时使用 *rest 可将剩余位置存入变量 rest 中, 可以为空, 也可使用 *_ 丢弃剩余元素
  • 对于单元素序列, 通过例如 a, = [1] 的方法可以单独解包幅值其中的唯一一个元素

容器迭代

容器均为可迭代对象, 可使用 for 循环迭代

  • 迭代字典时, 将键值对作为元组 (key, val) 赋值给迭代变量
    for k, v in dict: 迭代变量 k 将接收字典的键, v 将接收对应的值
  • 迭代字典时, 使用 dict.keys() / dict.valuse() 能分别迭代键与值
  • 迭代序列时, 使用 enumerate(list) 将以元组 (i, val) 赋值给迭代变量 (i 为元素索引)
    for i, t in enumerate(list): 迭代变量 i 将接收索引, t 将接收对应的值
  • 迭代可迭代对象时, 使用 zip(list1, list2, ...) 可拼接多个序列同时迭代 (长度必须相同), 每次迭代索引同一位置的各序列中的元素, 并合并为一个元组
    for a, b in zip(list1, list2): 迭代变量 a 将接收序列 list1 的元素, b 将接收序列 list2 的元素
  • 迭代可迭代对象时, 使用 set(list) 可去除迭代对象中的重复元素 (本质为创建集合容器, 笔记中暂不介绍)

可变对象与不可变对象

在 Python 中, 类被分为两种类型, 即可变对象与不可变对象
二者最基本的区别即

  • 不可变对象只能被访问其中的元素 (允许元素自身变化), 而不能修改, 如数值类型, 字符串与元组
  • 可变对象能够访问与修改其中的元素, 如一般类, 列表与字典

引用与拷贝

  • 引用
    • 引用仅发生在可变对象的传递中
    • 引用即传递一个指向特定对象的指针
    • 在引用过程中, 没有新对象被创建, 当引用变量被修改, 原始变量也将随之修改
    • 例如定义列表 a = [1, 2], 通过赋值 b = a 使 b 引用了该列表, 赋值 b[0] = 1 则变量 a 也将随之改变
  • 浅拷贝
    • 浅拷贝发生在不可变对象的传递或可变对象的 copy() 方法创建
    • 浅拷贝即是创建一个新的对象, 并将旧对象中的元素 (包括成员与容器) 直接赋给新对象
      因此当对象有成员或元素为一个可变对象, 则其浅拷贝的对应成员或元素也将指向该可变对象
    • 例如定义元组 a = ([1, 2], 3), 通过赋值 b = a 使 b 获得 a 的浅拷贝, 赋值 b[0][0] = 1 访问了元组中的列表, 而 a[0] 也保存了该列表的引用, 因此将随之修改
  • 深拷贝
    • 深拷贝仅能通过 copy 模块的 copy.deepcopy(x) 方法实现
    • 深拷贝时将递归对象的所有元素, 并创造一个与被复制对象以及底层引用完全相同但新的对象
      因此深拷贝可能导致极大的内存消耗, 不建议使用

赋值与传递

在赋值与函数参数传递时

  • 可变对象总是仅传递自身的引用
  • 不可变对象总是传递自身的浅拷贝

函数

函数基本使用

使用如下方式定义函数

def fun(a, b):
'''
<docs>    
'''
    ...
    return val

定义函数时

  • 使用关键字 def 定义函数
  • fun 函数名称, 推荐函数名称规则为小写字母单词 + 下划线组合
    Python 中不允许存在同名的函数, 因此也无法直接定义重载函数, 也不推荐使用重载函数
  • a, b 参数列表, 使用 () 包裹, 参数传递规则见可变对象与不可变对象
  • docs 为函数的说明文档, 使用独立一行的 ''' 包裹, 可有多行并使用基础的 Markdown 语法, 也可省略
  • return 函数返回值, 可通过返回元组序列类型实现返回与接收多个值
    当没有返回值时, 函数返回 None

使用函数时

  • 通过 函数名(参数1, 参数2, ...) 的方式可调用函数, 即按位置传入参数
  • 除了按位置传入参数, 还可以按参数名, 即按关键字传入参数, 此时可以不限定顺序但按关键字传入参数一定要在按位置传入参数之后
  • 当直接使用函数时, 则相当于一个可调用类型的变量

参数限定

默认值参数

  • 在定义函数参数时, 使用 <参数名> = <默认值> 可确定函数参数的默认值, 当调用函数时, 若没有传入该参数, 则将使用默认值
  • 可以使用任意值作为默认值, 但使用可变对象时, 由于默认值为一个指向该对象的引用, 且不会在函数调用后刷新, 因此默认值可能因函数调用而被修改, 不建议将可变对象作为函数的默认值
  • 如果必须使用可变对象, 或其他代替默认值的场合可将 None 作为默认值, 并在函数运行前判断该参数是否为 None, 若为 None 则创建一个默认值并赋给该参数

变长参数与解包参数

  • 使用 *args 表示变长位置参数, 所有未被接收的位置参数都将保存到元组 args
  • 使用 **kwargs 表示变长关键字参数, 所有未被接收的关键字参数都将保存到字典 **kwargs
  • 两种变长参数可以混合使用, 但变长位置参数一定要在变长关键字参数前
  • 与变长位置参数对应的, 使用 *<序列类型对象> 可将序列类型中的元素以此解包为位置参数并传入函数 (函数不一定要接收变长位置参数)
  • 与变长关键字参数对应的, 使用 **<字典对象> 可将字典中的键值对以此解包为关键字参数并传入函数 (函数不一定要接收变长关键字参数, 字典中不能有非函数参数的键名)

限定参数

  • 在函数参数列表中, 使用伪参数名 (不是实际的参数) / 表示该参数前的所有参数只能是位置参数, 不能以关键字方式传入
    以可作为最后一个参数, 表明函数的所有参数均为仅位置参数
  • 在函数参数列表中, 使用伪参数名 (不是实际的参数) * 表示该参数后的所有参数只能是关键字参数, 不能以位置方式传入
    以可作为第一个参数, 表明函数的所有参数均为仅关键字参数
  • 以上两种限定参数可以混合使用, 但 / 一定要在 * 之前

在实际使用时

  • 通常将没有实际意义的参数作为仅位置参数
  • 通常将具有实际意义的参数作为仅关键字参数

函数的高级使用

Lambda 表达

Lambda 表达式可用于将复杂函数包装为简单形式并作为参数传递供其他函数调用
其基本语法如下

# 基本语法
fun = lambda [[参数1], [参数2], ...] : 函数体
# 示例
z = lambda x, y: x ** 3 - y

python 中的 Lambda 表达式有如下特点

  • Lambda 表达式的参数除了不需要 () 包裹, 其余与一般函数完全相同
  • Lambda 表达式的函数体只能有一行, 且这一行的执行结果将作为表达式的返回值, 因此不需要 return
  • 在 Lambda 表达式中不允许赋值
  • Labmda 表达式同样允许设置默认值参数, 并且可以通过默认值参数实现状态保存, 例如以下例子
flist1 = []
flist2 = []

for i in range(10):
    flist1.append(lambda val = i: print(val))
    flist2.append(lambda : print(i))

flist1[2]() # 输出 2, 循环中 i 的值作为默认值被保存下来
flist2[2]() # 输出 9, 根据函数定义, 将使用变量 i 的最新值

变量作用域

  • 函数内定义的变量将在函数退出后被销毁, 因此不会被访问
  • 在函数内使用变量时, 将按从内到外直到该脚本文件的顺序搜索变量, 最内层的名称符合的变量将被使用
  • 在函数内对全局变量赋值时, 并不会生效, 因为等同于定义了一个同名的局部变量 (不建议在函数内定义与全局变量同名的变量)
    但是在函数内通过变量的方法修改全局变量会生效
  • 如果希望在函数内修改全局变量, 则需要先使用 global <全局变量> 进行声明
  • 如果希望在函数内修改外部非全局变量, 则需要先使用 nonlocal <变量名> 进行声明, 将寻找外层最近的, 具有指定名称的变量

闭包与修饰器

参考 https://www.runoob.com/w3cnote/python-func-decorators.htmlopen in new window

对于闭包

  • 可以在函数中定义子函数, 并通过返回值将函数传递出去
  • 子函数可以访问函数内的局部变量以及函数参数, 并且函数运行结束后子函数依然能访问

对于修饰器

  • 修饰器的本质即在一个函数调用前与调用后, 执行部分代码, 但不改变函数原有的内容
  • 修饰器即通过闭包实现, 首先将修饰器传入被修饰的函数, 修饰器创建一个包裹函数, 并返回
  • 此外还可与使用 @ + 修饰器 的语法, 修饰器即一个以函数为参数的函数
  • 推荐使用 from functools import wraps 并使用 @wraps(fun) 修饰包裹函数, 防止函数的原始信息被包裹函数覆盖
  • 如果希望创建含参数的修饰器, 则可通过一个返回修饰器的函数实现, 该函数根据参数构造特定的修饰器 (此时函数内有两层嵌套)
    使用时需要以 () 向函数传递参数
  • 如以下例子所示, 修饰器可用于
import time
from functools import wraps

def simple_timer(fun):
    @wraps(fun)
    def wrap_function(*args, **kwargs):
        ticks = time.perf_counter()
        res = fun(*args, **kwargs)
        used_times = time.perf_counter() - ticks
        print(f"{fun.__name__} used {used_times} second")
        return res
    return wrap_function

# 创建方法 1
@simple_timer
def fun(n):
    res = 1
    for i in range(n):
        res = res * i
    return res

# 创建方法 2
wrap_fun = simple_timer(fun)

模块

在 Python 中, 将单个 Python 文件视为一个模块, 模块名即 Python 文件名 (不包括后缀)

模块导入命令

  • 使用 import <模块名> 可直接导入模块, 此时需要 <模块名>.<模块全局变量> 的方式调用模块内的函数或变量
  • 使用 from <模块名> import <模块变量1>[, <模块变量2>[, ...]] 可从模块内导入特定变量, 模块或方法到当前程序的全局变量中
    • 使用 * 作为模块变量名则将导入整个模块内的变量到当前程序, 由于可能造成变量冲突, 因此不建议使用此方法
  • 在导入语句后使用 as <新名称> 可以指定名称作为导入模块或变量的新名称
  • 导入模块时不需要给出模块路径, 导入时将首先搜索当前模块所在目录, 然后搜索环境变量 PYTHONPATH, 最后搜索依赖于安装的默认值, 搜索目录位于模块 sys 的变量 sys.path

模块导入行为

  • 在一个解释器内, 当一个模块被导入后, 即使该模块被修改也不会再被重新导入
    可使用 importlib 模块的方法 importlib.reload(modulename) 强制重新导入
  • 每当一个模块被导入时, 将首先运行该模块内的代码
  • 除了在一个模块中带入另一个模块, 还可使用 python <模块名> [参数] 将模块作为脚本运行 (此时要带有完整的后缀与路径)
  • 对于每个模块都有变量 __name__, 表明该模块的导入名称, 如果该模块被作为程序运行, 则有 __name__ == "__main__" (即作为程序在顶层运行的模块总是以名称 __main__ 导入)
    因此通过 if __name__ == "__main__" : 可判断模块是否被作为程序运行, 并执行相应代码

  • 除了将单个 Python 文件作为一个模块, 还可使用一个文件夹表示模块, 称为包, 该文件夹名即模块名
  • 此时要求该文件夹中必须有文件 __init__.py, 即使用该脚本文件代表了该文件夹是一个模块, 也将在导入模块时运行该脚本 (可以是空文件)
  • 当导入包时, 将该文件夹下所有 Python 文件都作为包的子模块, 也可使用 from 的方式导入特定自模块
  • 允许包中的文件夹作为子包使用
  • 在多个子包中, 如果希望引用上级包或同级子包中的模块, 需要相对引用, 与相对路径类似
    使用 . 表示当前包, .. 表示上层包, ..<子包名> 表示同级子包

异常与错误

处理异常

异常处理语句的基本结构如下

try:
    ...
except <exc1> [as <exc_name1>]:
    ...
[except <exc2> [as <exc_name2>]:
    ...]
[else:
    ...]
[finally:
    ...]
  • try 部分用于检测与捕获异常, 当其中的语句出现异常将交由 except 捕获
  • except 用于捕获类型或父类型为 exc 的异常, 可参考触发异常
    • 使用 as <exc_name1> 表示将异常对象捕获后作为变量 exc_name1 处理
    • 允许有多个异常捕获语句, 当异常被捕获后, 将运行其下对应的子程序
    • 由于 Exception 为所有异常的基类, 因此 except Exception 能捕获所有异常, 之后可通过 type() 判断异常类型
  • else 部分用于处理没有任何异常被捕获的情况
    • 如果有 except Exception 语句, 则将表示没有异常的情况
  • finally 用于执行异常处理后的清理操作
    • 通常在使用资源如文件或网络时, 使用 try 检查异常, 并使用 finally 释放资源
    • 无论如何 finally 语句总会在最后被触发, 即使 tryexcpt 语句内出现 return 或再次触发异常
    • except 语句内在触发的异常前先执行 finally 然后再触发异常
    • try 语句内在触发的 return 前先执行 finally 然后再执行 return, 如果 finnaly 内也有 return, 则优先执行 finally 内的语句

触发异常

  • 使用 raise <异常对象> 命令可用于触发异常, 其中 异常对象 必须是异常基类 Exception 及其派生类
    当该语句被调用时, 将中断程序并寻找最上层的处理异常语句, 如果不存在将退出程序
  • 通常异常对象都使用一段表示异常原因的字符串为参数实例化, 常用的内置异常类有
    • Exception 即一般的异常, 也是内置异常或用户自定义异常的基类
    • NameError 变量名称为被查找到
    • TypeError 类型不满足要求引发的异常
    • ValueError 参数值不适合引发的异常
    • IndexError 索引超出范围引发的异常
    • RuntimeError 运行时错误
  • 异常链
    • 当在处理异常的 except 语句中再次触发异常, 则将形成异常链, 将异常附加到被处理异常上
    • 直接使用 raise 可再次抛出处理的异常
    • 假设捕获了异常 e, 使用异常对象的方法 e.add_note(str) 可以为再次抛出的异常补充注释
    • 使用 raise <新异常对象> from <捕获的异常> 可以强调两者存在强因果关系
    • 使用 raise <新异常对象> from None 可以取消异常链
  • 使用内置异常对象 ExceptionGroup 表示异常组, 异常组对象除了异常原因字符串, 还接收一个异常组成的元组, 表示异常由多个原因组成

预定义异常处理

主要用于资源管理类

with <exp> as <target>:
    ...

该语句等价于

try:
    target = exp.__enter__()
    ...
except ... as e:
    hit_exp = True
    if not target.__exit__(e_type, e_val, traceback):
        raise
finally:
    if not hit_exp:
        target.__exit__(None, None, None)

因此

  • exp 为一个具有预定义异常处理能力的资源管理对象
  • target 为一个变量, 用于接收表达式 exp 成员 __enter__ 的结果, 并在语句结束后销毁
  • 要求具有预定义异常处理能力的对象有以下两个成员方法
    • __enter__() 返回供给 target 的资源
    • __exit__(type, val, traceback) 用于回收提供给 target 的资源, 函数接收异常的类型, 值与上下文
      • 当该函数返回 True 表明异常与资源回收处理完成并正常退出
      • 当正常回收资源时, 三个参数均为 None
      • 不应该在该函数中触发异常
  • 该语句通常用于配合打开文件操作, 如 with open(文件名) as f:
    此时该语句中的程序访问文件资源, 并在退出语句时保证释放打开的文件

异常回溯

标准模块 traceback 可用于异常信息的回溯
虽然一般情况下 Python 能自动打印异常回溯信息, 但对于多线程等特殊情况, 可能需要手动获取异常回溯信息

模块方法 traceback.print_exception(exc, /, limit = None, file = None, chain = True) 获取异常回溯信息

  • exc 被回溯的异常对象
  • limit 回溯层数
    • 传入 None 将回溯所有信息
    • 传入正整数将从异常出现的最底层代码回溯
    • 传入负数从反方向回溯, 即出现异常的最顶层代码
  • file 打印异常的位置
    • 传入 None 将打印到 stderr
    • 传入文件对象或文件路径字符串, 将打印到文件中
  • chain 是否打印造成异常的详细原因

警告

除了一般的异常, 还存在警告这一类特殊的异常, 警告同样继承自异常基类 Exception

  • 仅当是警告基类 Warning 的基类才能作为警告
  • 一般将内置异常类名称中的 Error 换为 Warning 即内置警告类
  • 当警告通过 raise 触发时效果于一般的异常相同

警告一般使用 warning 模块下的方法 warn 触发
方法 warning.warn(message, category = ...) 触发警告

  • message 警告内容, 传入字符串
  • category 警告类型, 传入任意继承自 Warning 的类型, 默认为 UserWarning
  • 当警告触发是程序不会终止, 仅打印警告到标准错误上, 且不会重复触发

类的基础

类的使用

类的定义

使用如下的语法定义一个类

class <ClassName>[(...)]:
['''
<docs>
''']
    def __init__(self, ...):
        ...

    def fun(self, ...):
        ...

    val = ...
    ...
  • 类定义说明
    • ClassName 即类的名称, 通常类名称以大写字母开头并且使用大小写区分单词
    • (...) 类的继承, 参考类的继承
    • docs 类的说明文档, 与函数相同
  • 类的实例化
    • 通过 <变量名> = <类名>(...) 的方法可以创建类的实例, 即对象
    • 类实例化后的对象同样使用 . 运算符访问对象成员
    • 将通过类内定义的特殊方法 def __init__(self, ...) 实例化对象, 即构造函数
  • 类的性质
    • 在 Python 中, 以上语法定义的类可以视为一个特殊的模块, 类内定义的函数与变量均为该模块下的函数与变量, 因此类下的静态成员同样通过 . 运算符访问
    • 类也存在修饰器语法, 通过特殊的修饰器修饰类可用于创建特殊行为的类如, 数据类

公有成员定义

class Example:
    # 静态成员常量
    STATIC_VARIABLE = 0

    # 类构造函数
    def __init__(self, value):
        # 公有成员变量与常量
        self.public_variable = 1
        self.PUBLIC_CONST = 2

        # 私有成员变量
        self._private_variable = value

    # 属性的创建与常见用途 (创建只读量)
    @property
    def class_value(self):
        return self._private_variable

    # 一般函数
    def set_fun(self, value):
        # 注意, 可通过 self 直接访问静态成员
        print(self.STATIC_VARIABLE)
        self._private_variable = value

    # 静态方法
    @classmethod
    def info(cls):
        # 静态成员在静态方法中的访问
        print(cls.STATIC_VARIABLE)

# 类的实例化
a = Example(10)
# 属性的使用
print(a.class_value)
  • 可在类层级下定义类函数, 在 Python 中一般称类的函数为方法
    • 对于一般方法, 必须以 self 作为第一个参数, 该参数即类的实例对象, 通过类的对象使用方法时不需要传入
    • 对于静态方法, 需要使用内置修饰器 @classmethod 修饰函数, 并且以 cls 作为第一个参数, 该参数即静态方法所在的类本身, 使用静态方法时不需要传入
      • Python 中一般将与该类有关的实用方法定义为其静态方法
  • 类的成员变量使用以下方法定义
    • 在类的方法内, 通过对 self 访问的未定义类变量赋值即可定义一般成员变量
    • 直接在类层级下通过赋值定义变量将直接作为类的静态成员变量, 当大写时即常量静态成员
    • 通过内置修饰器 @property 修饰一个仅接收 self 并存在返回值的函数, 可以创建类的属性, 属性是一种特殊的成员, 在通过对象访问属性时, 使用的是访问成员变量的语法, 但实际将调用对应的方法并以其返回值作为访问结果, 可以此包装只读量等
  • 访问类与对象时
    • 对于一般方法与成员变量, 使用 <对象变量>.<成员变量 / 方法> 的方式访问
    • 对于静态成员, 可使用 <类名>.<静态成员 / 方法> 的方式访问, 同样也可通过如 self<对象变量> 访问静态成员

私有成员定义

  • Python 中不存在实际的私有成员, 但约定使用一个下划线 _ 为前缀表示类的私有变量或对象的私有成员, 该成员可被子类与父类访问, 因此更接近 C++ 的保护成员
  • 使用两个下划线 __ 为前缀表示类的私有变量时, 将使用 _<类名>__xxx 代替 __xxx
    • 虽然 __xxx 依然可以在对象中直接访问, 但是由于名称改写, 因此子类无法访问父类名称为 __xxx 的成员, 以此实现变量相对子类的私有属性, 即 C++ 中严格的私有成员
    • 但是子类依然可以通过 _<父类名>__xxx 强制访问父类的私有属性 __xxx
    • 该方法也可以防止子类中定义了同名的函数导致父类函数被覆盖, 使父类出错的情况, 可参考例子 https://docs.python.org/zh-cn/3/tutorial/classes.html#private-variablesopen in new window

类的继承

参考自 https://docs.python.org/zh-cn/3/tutorial/classes.html#inheritanceopen in new window

在类创建时, 使用类名后的 () 内传入父类, 即可创建继承自父类的子类, 关于定义示例见访问父类
虽然 Python 也支持多重继承, 但除了特殊情况不建议使用, 应当通过组合代替, 因此此处不介绍

通过类的继承

  • 子类将获得父类中已经定义的方法与成员变量, 且可直接访问父类的静态方法与变量
  • 使用 isinstance 函数时, 认为子类的实例同时也是父类的实例, 例如在示例中, 表达式 isinstance(obj, A) 为真
  • 在继承中, 还有函数 issubclass(<子类>, <父类>) 可用于判断两个类型的关系, 例如在示例中, 表达式 issubclass(C, A) 为真

访问父类

使用子类的实例时, 将首先搜索子类命名空间内的对象成员, 如果搜索不到将尝试寻找父类的属性与方法
因此对于 Python, 所有的对象方法都等同于 C++ 中的虚函数

当子类与父类具有相同名称的方法, 且需要访问父类方法时

  • 可通过在类方法内通过内置类 super() 构造对象, 通过该对象访问父类
  • 当在类定义外使用 super 时, 需要传入 super(<对象类型>, <操作对象>)
  • 由于方法将自动覆盖, 因此子类一般需要在构造函数中显示的调用父类的构造函数

以下为继承与父类访问的示例

class A:
    def __init__(self, value):
        self.v_A = value
        print("in A init")

    def fun(self):
        print("in A fun")

class C(A):
    def __init__(self, value):
        # 注意在子类中显式的调用父类构造函数
        super().__init__(value)
        print("in C init")
    
    def fun(self):
        print("in C fun")

obj = C(10)
# 类外调用父类方法
super(C, obj).fun()
# 没有冲突时, 则可以直接调用父类的成员
print(obj.v_A)

抽象基类

Python 中, 需要通过继承模块 abc 下的类 ABC 创建抽象基类

from abc import ABC, abstractmethod

class Base(ABC):
    def __init__(self, a):
        self.a = a

    def fun(self):
        print(self.a)

    # 一般抽象方法
    @abstractmethod
    def set_value(self, value):
        ...

    # 抽象属性
    @property
    @abstractmethod
    def value(self) -> int:
        ...

    # 抽象静态方法
    @classmethod
    @abstractmethod
    def count(cls):
        ...

使用抽象基类时注意

  • 抽象基类中, 通过修饰器 abstractmethod 确定了基类中, 子类必须实现的抽象方法, 如果没有实现将会出错
  • 不需要给出抽象方法的定义, 可使用 ... 代替
  • 修饰特殊方法时, 应保证 abstractmethod 修饰器在最后
  • 允许抽象基类中存在一般方法, 同样可通过 super() 访问

运算符重载与类型转换

类的函数使用名称 __xxx__ 时表示的是特殊方法, 这些方法通常都有实际含义, 如构造函数 __init__
以下为一些用于运算符重载与类型转换的特殊方法

  • __str__(self) 将对象转换为可读的字符串, 要求返回字符串
  • __repr__(self) 将对象转换为一个可用于创建自身的字符串, 要求返回字符串, 当 __str__ 未定义时将代替之
  • __bool__(self) 将对象转换为布尔值, 要求返回布尔值
  • __eq__(self, other) 对象的等号 == 运算符重载, 要求返回布尔值, 默认使用 is
  • __ne__(self, other) 对象的不等号 != 运算符重载, 要求返回布尔值, 默认使用 not is

注意到, 重载运算符时, 对象总是位于运算符的左侧, 因此如果希望正确发挥重载运算符的效果, 二元运算时尽量让对象位于运算符左侧, 一般值位于右侧

类的高级使用

类型模拟

通过重载一套运算符, 可以完成类关于特定类型的模拟, 具体可参考官方文档

数据类

在部分情况下, 希望将类以类似 C++ 的 struct 的方式使用, 则可以通过来自模块 dataclasses 的修饰器 dataclass 创建修饰类
示例如下

from dataclasses import dataclass

@dataclass
class <DataClass>:
    <key1> : <type1>
    <key2> : <type2> = <default_val2>
  • 虽然使用的语法 <key1> : <type1> 适用于定义静态变量, 但经过修饰后, 数据类将以此为实例对象创建一个名称为 key1 类型为 type1 的属性 (注意必须给出类型 type1)
  • 可通过 <key2> : <type2> = <default_val2> 指定数据类特定对象的默认值为 <default_val2> (更推荐使用函数 field)
  • 在数据类将自动生成构造函数, 可通过 DataClass(key1, key2, ...) 按顺序传入数据类各对象的值
  • 此外还将自动创建字符串转换 __repr__ 方法与重载相等运算符
  • 对于常量数据类, 可通过数据类支持枚举类的方式定义

数据类的高级使用

  • 使用模块 dataclass 下的函数 field(*, default = ..., default_factory = ..., compare = True, metadata = False) 可对属性进行更高级的设置
    • default 属性默认值, 用于设置不可变对象类型属性的默认值
    • default_factory 属性默认值的工厂函数 (一个返回默认值且不接受参数的函数), 根据默认值参数可知, 不能直接将可变参数作为默认值
    • compare 该参数是否参与比较
    • metadata 是否使用该属性保存元数据, 取为 True 时该属性的类型为字典或 None, 且不可修改

枚举类

参考文档 https://docs.python.org/zh-cn/3/howto/enum.htmlopen in new window

在 Python 中, 没有直接提供枚举类型, 但可通过继承模块 emun 下的类实现各种特性的枚举类

枚举类基础定义

基础枚举基类为 Enum, 定义枚举类型时, 继承枚举基类, 通过定义类的静态成员确定枚举量的名称, 将静态成员的值作为枚举量对应的值
例如

from enum import Enum
class Color(Enum):
    RED = 0
    BLUE = 1
    GREEN = 2

定义枚举类时注意

  • 枚举量的名称与对应值都不应该重复
  • 枚举量的名称最好大写, 以此表示常量静态成员, 以防止以外修改

枚举类型的使用

对于继承自 Enum 的枚举类型, 有以下常用操作 (此处以上文定义的枚举类型举例)

  • 枚举量获取
    • 通过 <枚举类型>.<枚举量> 的方式即可访问枚举量, 并用于赋值
    • 将枚举量对应的值构造枚举类型也可创建对应的枚举量, 例如表达式 Color(0) == Color.RED 为真
    • 也可通过枚举量名称的方式访问枚举量, 此时需要对枚举类型使用 [] 运算赋, 传入枚举量名称字符串, 例如表达式 Color['RED'] == Color.RED 为真
  • 枚举量的成员
    • 认为枚举量为对应枚举类型的实例, 因此表达式 isinstance(Color.RED, Color) 为真
    • 通过成员 name 可访问枚举量的名称, 以字符串形式保存
    • 通过成员 value 可获取枚举量对应的值
    • 枚举量之间可通过 ==!= 比较, 但无法使用不等号如 > 比较, 也无法与对应的值比较, 例如表达式 Color.RED == 0 为假
  • 枚举类型的其他使用
    • 通过枚举类型的静态成员 __members__keys(), values(), items() 方法可分别迭代枚举量对应名称, 枚举量, 二者组成的元组
    • 允许为枚举类型定义静态方法

标志位类型

  • 继承自基类 enum.Flag, 基础使用与枚举类型的使用一致
  • 标志位类型中, 每个枚举量表示一个二进制标志位, 因此一般枚举量对应值为 2 的幂, 且允许枚举量之间的位运算
  • 除此之外, 在满足枚举量对应值不重复的基础上, 允许使用之前枚举量的组合或 0, 但通过静态成员 __members__ 遍历枚举类时, 只有以值位 2 的幂的枚举量会被访问
  • 建议使用函数 enum.auto() 自动确定枚举量对应的值 (该函数也可用于 Enum 以自动确定值)
from enum import Flag, auto

class Color(Flag):
    RED = auto()
    BLUE = auto()
    GREEN = auto()
    WHITE = RED | BLUE | GREEN
    BLACK = 0

数据类支持枚举类

如果希望以数据类对象作为枚举类的值, 应使用以下方式定义枚举类

  • 枚举类应同时继承所需数据类与 Enum
  • 数据类中不允许有属性 namevalue (与 Enum 的相关成员变量冲突)
  • 定义枚举量的值时, 需要传入构造数据类对应的元组, 而不是直接传入数据类
from enum import Enum
from dataclasses import dataclass

@dataclass
class ColorInfo:
    color_name : str
    color_value : int

class Color(ColorInfo, Enum):
    RED = ("red", 0xFF0000)
    BLUE = ("blue", 0x0000FF)
    GREEN = ("green", 0x00FF00)

# 使用示例, 创建枚举量与颜色名的映射
color_name_dict = {value : value.color_name for value in Color.__members__.values()}

附录

字符串格式化表示方法

格式化字符串的表示方法如下
参考自
http://www.cppcns.com/jiaoben/python/287553.htmlopen in new window
https://zhuanlan.zhihu.com/p/295621558open in new window

  • 参数填充
    • {} 按输入参数顺序填充
    • {n} 填充第 n 个参数, 从 0 开始计算
    • {val} 填充入名称为 val 的参数
    • 示例
# 基本替换
"y = {}x + {}".format("a", 12)
# 输出 "y = ax + 12"

# 按参数位置替换
"y = {0}x^2 +{1}x + {0}".format("a", 12)
# 输出 "y = ax^2 +12x + a"

# 按参数名称替换
"y = {num}x^2 +{param}x + {num}".format(num = 12, param = "a")
# 输出 "y = 12x^2 +ax + 12"
  • 字符串填充规则
    占用 n 个字符的宽度, 空白位置使用 x 填充 (未指定则使用空格)
    • {:x<n} 左对齐
    • {:x>n} 右对齐
    • {:x^n} 居中对齐
    • 示例
# 宽度控制
# 左对齐
print("out:{str:<10}\n    0123456789".format(str = "Hello"))
# 输出
# out:Hello     
#     0123456789

# 右对齐
print("out:{str:>10}\n    0123456789".format(str = "Hello"))
# 输出
# out:     Hello
#     0123456789

# 居中对齐
print("out:{str:^10}\n    0123456789".format(str = "Hello"))
# 输出
# out:  Hello   
#     0123456789
  • 数字格式化
    • {:.nf} 保留 n 位小数
    • {:.n%} 百分数, n 为百分数后的小数位
    • {:.ne} 科学计数法, n 为小数部分的小数位
    • {:+} 带符号 (前缀)
    • {:,} 千位分隔符 (前缀)
    • {x>/</^n} 对齐, 含义见上 (前缀)
    • {:#X} 十六进制 (仅用于整数)
      对于十六进制宽度通过此方法控制 {:#n_X}, n 为宽度
      也可使用 O, x 表示八进制或十六进制小写
# 保留 n 位小数
print("{pi:.3f}".format(pi = 3.14159))
# 输出 3.142

# 百分数
print("{pi:.2%}".format(pi = 3.14159))
# 输出 314.16%

# 科学计数法
print("{pi:.3e}".format(pi = 314159))
# 输出 3.142e+05

# 带符号保留
print("{pi:+.3f}".format(pi = 3.14159))
# 输出 +3.142

# 千位分隔符
print("{num:,}".format(num = 3141.5926))
# 输出 3,141.5926

# 对齐
print("out: {pi:0^10.2f}".format(pi = 3.14159))
# 输出 out: 0003.14000

# 十六进制
print("{pi:#X}".format(pi = 314159))
# 输出 0X4CB2F
  • {} 转义
    使用 {{}} 可对 {} 进行转义, 保留并输出 {}
    示例
"\\frac{{{y}}}{{x}}".format(y = 10)
> '\frac{10}{x}'
  • 混合格式

对于以上各种格式可以混合使用, 具体规则如下

format_spec     ::=  [[fill]align][sign][#][0][width][grouping_option][.precision][type]
fill            ::=  <any character>
align           ::=  "<" | ">" | "=" | "^"
sign            ::=  "+" | "-" | " "
width           ::=  digit
grouping_option ::=  "_" | ","
precision       ::=  digit
type            ::=  "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%"

推导式

通过字符串, 从一个数据序列构建另一个序列, 可以被 input 函数解析或直接使用表示变量的值

列表推导式

res = [out_exp_res for out_exp in input_list if condition]
  1. res 结果列表
  2. out_exp_res 遍历列表元素的表达式, 返回值作为列表元素
  3. out_exp 表达式中的迭代变量
  4. input_list 输入列表(可迭代类型)
  5. condition 迭代变量满足条件
  • eg. 过滤掉长度小于或等于 3 的字符串列表, 并将剩下的转换成大写字母
* new_names = [it.upper() for it in names if len(it) > 3]
  • 注意, 输入列表也可以是返回列表的函数, 如 range()

字典推导式

res = { key_expr: value_expr for value in collection if condition }
  1. res 结果字典
  2. key_expr 遍历列表元素的表达式, 返回值作为键名
  3. value_expr 遍历列表元素的表达式, 返回值作为键值
  4. value 表达式中的迭代变量
  5. collection 输入列表(可迭代类型)
  6. condition 迭代变量满足条件
  • eg. 使用字符串及其长度创建字典
newdict = {key:len(key) for key in listdemo}

集合推导式

res = { expression for item in Sequence if conditional }

与列表相同, 使用 {} 包裹

元组推导式

res = (expression for item in Sequence if conditional )

与列表相同, 使用 () 包裹
注意元组推导式返回的是生成器对象, 不是元组
生成器对象不直接保存数据, 而是在迭代时计算结果
需要使用方法 res = tuple([生成器]) 生成元组
例如 name_list = tuple(name_list[key] for key in bind_enum.__members__.keys())

分支推导式

res = [结果1 if 判断条件 else 结果2 for 变量名 in 原列表]