Python 特殊语法

最近在看 AI智能体 相关的项目,这些项目大多是用 Python 语言编写的。Python 语言作为一门高级编程语言,有一些自己独特的语法和功能,这篇文章便是做一个记录,以备后续查看,本文主要记录的是 Python3 的语法。

基础语法

标识符

标识符是一种名称标识,用来表示变量、函数、类/对象、属性及方法等等,它有如下的命名规则:

  • 第一个字符必须以字母(a-z, A-Z)或下划线 _
  • 标识符的其他的部分由字母、数字和下划线组成。
  • 标识符对大小写敏感,count 和 Count 是不同的标识符。
  • 标识符对长度无硬性限制,但建议保持简洁(一般不超过 20 个字符)。
  • 禁止使用保留关键字,如 if、for、class 等不能作为标识符。

合法的标识符有:

1
2
3
4
5
6
7
age = 25                # 普通变量名,最常见
user_name = "Alice" # 用下划线连接单词,清晰易读
_total = 100 # 下划线开头通常表示“内部使用”或“私有”
MAX_SIZE = 1024 # 全大写通常表示“常量”(固定不变的值)
calculate_area() # 函数名,动词+名词
StudentInfo # 类名,首字母大写(驼峰命名法)
__private_var # 双下划线开头,有特殊含义

非法的标识符有:

1
2
3
4
5
2nd_place = "silver"    # 错误:以数字开头
user-name = "Bob" # 错误:包含连字符
class = "Math" # 错误:使用关键字
$price = 9.99 # 错误:包含特殊字符
for = "loop" # 错误:使用关键字

Python 保留关键字

保留字即关键字,不能把它们用作任何标识符名称。Python 的标准库提供了一个 keyword模块,可以输出当前版本的所有关键字:

1
2
3
4
5
6
7
>>> import keyword
>>> keyword.kwlist
['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break',
'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for',
'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not',
'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
>>>

说明:

类别 关键字 说明
逻辑值 True 布尔真值
False 布尔假值
None 表示空值或无值
逻辑运算 and 逻辑与运算
or 逻辑或运算
not 逻辑非运算
条件控制 if 条件判断语句
elif 否则如果(else if 的缩写)
else 否则分支
循环控制 for 迭代循环
while 条件循环
break 跳出循环
continue 跳过当前循环的剩余部分,进入下一次迭代
异常处理 try 尝试执行代码块
except 捕获异常
finally 无论是否发生异常都会执行的代码块
raise 抛出异常
函数定义 def 定义函数
return 从函数返回值
lambda 创建匿名函数
类与对象 class 定义类
del 删除对象引用
模块导入 import 导入模块
from 从模块导入特定部分
as 为导入的模块或对象创建别名
作用域 global 声明全局变量
nonlocal 声明非局部变量(用于嵌套函数)
异步编程 async 声明异步函数
await 等待异步操作完成
其他 assert 断言,用于测试条件是否为真
in 检查成员关系
is 检查对象身份(是否是同一个对象)
pass 空语句,用于占位
with 上下文管理器,用于资源管理
yield 从生成器函数返回值

注释

Python 中单行注释以 # 开头,实例如下:

1
2
3
4
#!/usr/bin/python3

# 第一个注释
print ("Hello, Python!") # 第二个注释

多行注释可以用多个 # 号,还有 '''"""

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/python3

# 第一个注释
# 第二个注释

'''
第三注释
第四注释
'''

"""
第五注释
第六注释
"""
print ("Hello, Python!")

行与缩进

python 最具特色的就是使用缩进来表示代码块,不需要使用大括号 {} 。 缩进的空格数是可变的,但是同一个代码块的语句必须包含相同的缩进空格数。实例如下:

1
2
3
4
if True:
print ("True")
else:
print ("False")

多行语句

Python 通常是一行写完一条语句,但如果语句很长,可以使用反斜杠 \ 来实现多行语句,例如:

1
2
3
total = item_one + \
item_two + \
item_three

[], {}, 或 () 中的多行语句,不需要使用反斜杠 \,例如:

1
2
total = ['item_one', 'item_two', 'item_three',
'item_four', 'item_five']

空行

函数之间或类的方法之间用空行分隔,表示一段新的代码的开始。类和函数入口之间也用一行空行分隔,以突出函数入口的开始。

空行与代码缩进不同,空行并不是 Python 语法的一部分。书写时不插入空行,Python 解释器运行也不会出错。但是空行的作用在于分隔两段不同功能或含义的代码,便于日后代码的维护或重构。

多个语句构成代码组

缩进相同的一组语句构成一个代码块,称之代码组。

像if、while、def和class这样的复合语句,首行以关键字开始,以冒号( : )结束,该行之后的一行或多行代码构成代码组。

将首行及后面的代码组称为一个子句(clause)。

1
2
3
4
5
6
if expression : 
suite
elif expression :
suite
else :
suite

import 与 from...import

在 python 用 import 或者 from...import 来导入相应的模块。

将整个模块(somemodule)导入,格式为: import somemodule

从某个模块中导入某个函数,格式为: from somemodule import somefunction

从某个模块中导入多个函数,格式为: from somemodule import firstfunc, secondfunc, thirdfunc

将某个模块中的全部函数导入,格式为: from somemodule import *

基本数据类型

变量

Python 中的变量不需要声明,每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。使用等号 = 用来给变量赋值,等号左边是变量名,右边是存储在变量中的值。如下所示:

1
2
3
4
5
6
7
8
9
#!/usr/bin/python3

counter = 100 # 整型变量
miles = 1000.0 # 浮点型变量
name = "runoob" # 字符串

print(counter)
print(miles)
print(name)

Python 允许同时为多个变量赋值,既可以为多个变量赋一个相同的值,也可以为多个变量赋不同的值,如下所示:

1
2
3
4
5
# 多个变量赋一个相同的值
a = b = c = 1

# 多个变量赋不同的值
a, b, c = 1, 2, "runoob"

可以通过 type() 函数查看变量的类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 变量定义
x = 10 # 整数
y = 3.14 # 浮点数
name = "Alice" # 字符串
is_active = True # 布尔值

# 多变量赋值
a, b, c = 1, 2, "three"

# 查看数据类型
print(type(x)) # <class 'int'>
print(type(y)) # <class 'float'>
print(type(name)) # <class 'str'>
print(type(is_active)) # <class 'bool'>

标准数据类型

Python3 中有 6 种标准数据类型,以及 bool 布尔类型(bool 是 int 的子类):

  • Number(数字)
  • String(字符串)
  • bool(布尔类型)
  • List(列表)
  • Tuple(元组)
  • Set(集合)
  • Dictionary(字典)

按是否可变,可以分为以下两类:

  • 不可变数据(4 个):Number(数字)、String(字符串)、bool(布尔)、Tuple(元组)
  • 可变数据(3 个):List(列表)、Dictionary(字典)、Set(集合)

此外还有一些高级的数据类型,如字节数组类型 bytes

数字(Number)类型

python 中数字有四种类型:整数、布尔型、浮点数和复数。

  • int (整数), 如 1, 只有一种整数类型 int,表示为长整型,没有 python2 中的 Long。
  • bool (布尔), 如 True。
  • float (浮点数), 如 1.23、3E-2。
  • complex (复数) - 复数由实部和虚部组成,形式为 a + bj,其中 a 是实部,b 是虚部,j 表示虚数单位。如 1 + 2j、 1.1 + 2.2j.

字符串(String)

  • Python 中单引号 ' 和双引号 " 使用完全相同。
  • 使用三引号('''""")可以指定一个多行字符串。
  • 转义符 \
  • 反斜杠可以用来转义,使用 r 可以让反斜杠不发生转义。 如 r"this is a line with \n"\n 会显示,并不是换行。
  • 按字面意义级联字符串,如 "this " "is " "string" 会被自动转换为 this is string
  • 字符串可以用 + 运算符连接在一起,用 * 运算符重复。
  • Python 中的字符串有两种索引方式,从左往右以 0 开始,从右往左以 -1 开始。
  • Python 中的字符串不能改变。
  • Python 没有单独的字符类型,一个字符就是长度为 1 的字符串。
  • 字符串切片 str[start:end],其中 start(包含)是切片开始的索引,end(不包含)是切片结束的索引。
  • 字符串的切片可以加上步长参数 step,语法格式如下:str[start:end:step].

字符串截取的语法格式如下:

1
变量[头下标:尾下标]

索引值以 0 为开始值,-1 为从末尾开始的位置。 字符串索引

实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/python3

str='123456789'

print(str) # 输出字符串
print(str[0:-1]) # 输出第一个到倒数第二个的所有字符
print(str[0]) # 输出字符串第一个字符
print(str[2:5]) # 输出从第三个开始到第六个的字符(不包含)
print(str[2:]) # 输出从第三个开始后的所有字符
print(str[1:5:2]) # 输出从第二个开始到第五个且每隔一个的字符(步长为2)
print(str * 2) # 输出字符串两次
print(str + '你好') # 连接字符串

print('------------------------------')

print('hello\nrunoob') # 使用反斜杠(\)+n转义特殊字符
print(r'hello\nrunoob') # 在字符串前面添加一个 r,表示原始字符串,不会发生转义

这里的 rraw,即 raw string,会保持字符原本含义。

转义字符

在需要在字符中使用特殊字符时,python 用反斜杠 \ 转义字符。

Python 字符串运算符

下表实例变量 a 值为字符串 "Hello",b 变量值为 "Python":

操作符 描述 实例
+ 字符串连接 a + b 输出结果: HelloPython
* 重复输出字符串 a*2 输出结果:HelloHello
[] 通过索引获取字符串中字符 a1 输出结果 e
[ : ] 截取字符串中的一部分,遵循左闭右开原则,str[0:2] 是不包含第 3 个字符的。 a[1:4] 输出结果 ell
in 成员运算符 - 如果字符串中包含给定的字符返回 True 'H' in a 输出结果 True
not in 成员运算符 - 如果字符串中不包含给定的字符返回 True 'M' not in a 输出结果 True
r/R 原始字符串 - 原始字符串:所有的字符串都是直接按照字面的意思来使用,没有转义特殊或不能打印的字符。 原始字符串除在字符串的第一个引号前加上字母 r(可以大小写)以外,与普通字符串有着几乎完全相同的语法。 print( r'\n' )
% 格式字符串 见下节

字符串格式化

Python 支持格式化字符串的输出,最基本的用法是将一个值插入到一个有字符串格式符 %s 的字符串中。

在 Python 中,字符串格式化使用与 C 中 sprintf 函数一样的语法。

1
2
3
4
5
6
#!/usr/bin/python3

print ("我叫 %s 今年 %d 岁!" % ('小明', 10))

# 结果
我叫 小明 今年 10 岁!

python字符串格式化符号:

符 号 描述
%c 格式化字符及其ASCII码
%s 格式化字符串
%d 格式化整数
%u 格式化无符号整型
%o 格式化无符号八进制数
%x 格式化无符号十六进制数
%X 格式化无符号十六进制数(大写)
%f 格式化浮点数字,可指定小数点后的精度
%e 用科学计数法格式化浮点数
%E 作用同%e,用科学计数法格式化浮点数
%g %f和%e的简写
%G %f 和 %E 的简写
%p 用十六进制数格式化变量的地址

格式化操作符辅助指令:

符 号 功能
* 定义宽度或者小数点精度
- 用做左对齐
+ 在正数前面显示加号( + )
<sp> 在正数前面显示空格
# 在八进制数前面显示零('0'),在十六进制前面显示'0x'或者'0X'(取决于用的是'x'还是'X')
0 显示的数字前面填充'0'而不是默认的空格
% '%%'输出一个单一的'%'
(var) 映射变量(字典参数)
m.n. m 是显示的最小总宽度,n 是小数点后的位数(如果可用的话)

三引号

python三引号允许一个字符串跨多行,字符串中可以包含换行符、制表符以及其他特殊字符。实例如下:

1
2
3
4
5
6
7
8
#!/usr/bin/python3

para_str = """这是一个多行字符串的实例
多行字符串可以使用制表符
TAB ( \t )。
也可以使用换行符 [ \n ]。
"""
print (para_str)

f-string

f-string 是 python3.6 之后版本添加的,称之为字面量格式化字符串,是新的格式化字符串的语法。

f-string 格式化字符串以 f 开头,后面跟着字符串,字符串中的表达式用大括号 {} 包起来,它会将变量或表达式计算后的值替换进去,实例如下:

1
2
3
4
5
6
7
8
9
>>> name = 'Runoob'
>>> f'Hello {name}' # 替换变量
'Hello Runoob'
>>> f'{1+2}' # 使用表达式
'3'

>>> w = {'name': 'Runoob', 'url': 'www.runoob.com'}
>>> f'{w["name"]}: {w["url"]}'
'Runoob: www.runoob.com'

bool(布尔类型)

布尔类型即 TrueFalse。在 Python 中,TrueFalse 都是关键字,表示布尔值。

布尔类型可以用来控制程序的流程,比如判断某个条件是否成立,或者在某个条件满足时执行某段代码。

布尔类型特点:

  • 布尔类型只有两个值:TrueFalse
  • boolint 的子类,因此布尔值可以被看作整数来使用,其中 True 等价于 1,False 等价于 0。
  • 布尔类型可以和其他数据类型进行比较,比如数字、字符串等。在比较时,Python 会将 True 视为 1,False 视为 0。
  • 布尔类型可以和逻辑运算符一起使用,包括 andornot,用来组合多个布尔表达式。
  • 布尔类型也可以被转换成其他数据类型,比如整数、浮点数和字符串。在转换时,True 会被转换成 1,False 会被转换成 0。
  • 可以使用 bool() 函数将其他类型的值转换为布尔值。以下值转换为布尔值时为 FalseNoneFalse零(0、0.0、0j)、空序列(如 ''、()、[])和空映射(如 {})。其他所有值转换为布尔值时均为 True
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 布尔类型的值和类型
a = True
b = False
print(type(a)) # <class 'bool'>
print(type(b)) # <class 'bool'>

# 布尔类型的整数表现
print(int(True)) # 1
print(int(False)) # 0

# 使用 bool() 函数进行转换
print(bool(0)) # False
print(bool(42)) # True
print(bool('')) # False
print(bool('Python')) # True
print(bool([])) # False
print(bool([1, 2, 3])) # True

# 布尔逻辑运算
print(True and False) # False
print(True or False) # True
print(not True) # False

# 布尔比较运算
print(5 > 3) # True
print(2 == 2) # True
print(7 < 4) # False

# 布尔值在控制流中的应用
if True:
print("This will always print")

if not False:
print("This will also always print")

x = 10
if x:
print("x 是非零值,在布尔上下文中为 True")

注意:在 Python 中,所有非零的数字和非空的字符串、列表、元组等数据类型都被视为 True,只有 0、空字符串、空列表、空元组等被视为 False。因此,在进行布尔类型转换时,需要注意数据类型的真假性。

List(列表)

List(列表)是 Python 中使用最频繁的数据类型。

列表可以完成大多数集合类的数据结构实现。列表中元素的类型可以不相同,它支持数字、字符串,甚至可以包含列表(即嵌套列表)。

列表写在方括号 [] 之间,用逗号分隔开的元素列表。

和字符串一样,列表同样可以被索引和截取,列表被截取后返回一个包含所需元素的新列表。

列表截取的语法格式如下:

1
变量[头下标:尾下标]

索引值以 0 为开始值,-1 为从末尾的开始位置。 列表索引

加号 + 是列表连接运算符,星号 * 是重复操作。如下实例:

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/python3

my_list = ['abcd', 786, 2.23, 'runoob', 70.2] # 避免使用 list 作为变量名,会覆盖内置类型
tinylist = [123, 'runoob']

print(my_list) # 打印整个列表:['abcd', 786, 2.23, 'runoob', 70.2]
print(my_list[0]) # 打印第一个元素(索引 0):abcd
print(my_list[1:3]) # 打印索引 1 和 2 的元素(不含索引 3):[786, 2.23]
print(my_list[2:]) # 打印从索引 2 开始到末尾的所有元素:[2.23, 'runoob', 70.2]
print(tinylist * 2) # 重复打印 tinylist 两次:[123, 'runoob', 123, 'runoob']
print(my_list + tinylist) # 拼接两个列表

Tuple(元组)

元组(tuple)与列表类似,不同之处在于元组的元素不能修改。元组写在小括号 () 里,元素之间用逗号隔开。

元组中的元素类型也可以不相同:

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/python3

my_tuple = ('abcd', 786, 2.23, 'runoob', 70.2) # 避免使用 tuple 作为变量名
tinytuple = (123, 'runoob')

print(my_tuple) # 输出完整元组
print(my_tuple[0]) # 输出第一个元素:abcd
print(my_tuple[1:3]) # 输出索引 1 和 2 的元素:(786, 2.23)
print(my_tuple[2:]) # 输出从索引 2 开始的所有元素
print(tinytuple * 2) # 输出两次 tinytuple
print(my_tuple + tinytuple) # 连接两个元组

元组与字符串类似,可以被索引且下标从 0 开始,-1 为从末尾开始的位置,也可以进行截取。

其实,可以把字符串看作一种特殊的元组。

构造包含 0 个或 1 个元素的元组比较特殊,有一些额外的语法规则:

1
2
tup1 = ()    # 空元组
tup2 = (20,) # 一个元素,需要在元素后添加逗号
如果你想创建只有一个元素的元组,需要在元素后面添加一个逗号,以区分它是元组而不是普通的值。因为在没有逗号的情况下,Python 会将括号解释为数学运算中的括号:
1
not_a_tuple = (42)  # 这是整数 42,不是元组

string、list 和 tuple 都属于 sequence(序列)。

注意:

  • 与字符串一样,元组的元素不能修改(不可变类型)。
  • 元组也可以被索引和切片,方法与列表相同。
  • 元组也可以使用 + 操作符进行拼。

Set(集合)

Python 中的集合(Set)是一种无序、可变的数据类型,用于存储唯一的元素。集合中的元素不会重复,并且可以进行交集、并集、差集等常见的集合操作。

在 Python 中,集合使用大括号 {} 表示,元素之间用逗号 , 分隔。也可以使用 set() 函数创建集合。

注意: 创建一个空集合必须用 set() 而不是 {},因为 {} 创建的是一个空字典。

创建格式:

1
2
3
parame = {value01, value02, ...}
或者
set(value)

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/python3

sites = {'Google', 'Taobao', 'Runoob', 'Facebook', 'Zhihu', 'Baidu'}

print(sites) # 输出集合(无序,重复元素会被自动去掉)

# 成员测试
if 'Runoob' in sites:
print('Runoob 在集合中')
else:
print('Runoob 不在集合中')

# set 可以进行集合运算
a = set('abracadabra')
b = set('alacazam')

print(a) # a 中的唯一字符

print(a - b) # a 和 b 的差集(在 a 中但不在 b 中)
print(a | b) # a 和 b 的并集(在 a 或 b 中)
print(a & b) # a 和 b 的交集(同时在 a 和 b 中)
print(a ^ b) # a 和 b 的对称差集(在 a 或 b 中,但不同时存在)

Dictionary(字典)

字典(dictionary)是 Python 中另一个非常有用的内置数据类型。

字典是一种映射类型,用 {} 标识,它是一个 键(key) : 值(value) 的集合。键(key) 必须使用不可变类型,且在同一个字典中键必须是唯一的。

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/python3

my_dict = {}
my_dict['one'] = "1 - 菜鸟教程"
my_dict[2] = "2 - 菜鸟工具"

tinydict = {'name': 'runoob', 'code': 1, 'site': 'www.runoob.com'}

print(my_dict['one']) # 输出键为 'one' 的值
print(my_dict[2]) # 输出键为 2 的值
print(tinydict) # 输出完整的字典
print(tinydict.keys()) # 输出所有键
print(tinydict.values()) # 输出所有值

bytes 类型

在 Python3 中,bytes 类型表示的是不可变的二进制序列(byte sequence)。与字符串类型不同的是,bytes 类型中的元素是整数值(0 到 255 之间的整数),而不是 Unicode 字符。

bytes 类型通常用于处理二进制数据,比如图像文件、音频文件、视频文件等。在网络编程中,也经常使用 bytes 类型来传输二进制数据。

创建 bytes 对象最常见的方式是使用 b 前缀:

1
2
3
4
x = b"hello"           # 使用 b 前缀创建 bytes 对象
print(x) # b'hello'
print(type(x)) # <class 'bytes'>
print(x[0]) # 104('h' 的 ASCII 值,bytes 元素是整数)

也可以使用 bytes() 函数将其他类型的对象转换为 bytes 类型,第二个参数指定编码方式:

1
x = bytes("hello", encoding="utf-8")

与字符串类型类似,bytes 类型也支持切片、拼接、查找、替换等操作。由于 bytes 类型是不可变的,修改操作需要创建一个新的 bytes 对象:

1
2
3
4
5
x = b"hello"
y = x[1:3] # 切片操作,得到 b'el'
z = x + b"world" # 拼接操作,得到 b'helloworld'
print(y) # b'el'
print(z) # b'helloworld'

需要注意的是,bytes 类型中的元素是整数值,因此在进行比较操作时需要使用相应的整数值。可以用 ord() 函数将字符转换为对应的整数值:

1
2
3
x = b"hello"
if x[0] == ord("h"): # ord("h") 返回 104
print("第一个元素是 'h'")

数据类型转换

函数 描述
int(x [,base]) 将 x 转换为一个整数
float(x) 将 x 转换为一个浮点数
complex(real [,imag]) 将 x 转换为一个整数
str(x) 将对象 x 转换为字符串
repr(x) 将对象 x 转换为表达式字符串
eval(str) 计算字符串中的有效 Python 表达式,并返回一个对象
tuple(s) 将序列 s 转换为一个元组
list(s) 将序列 s 转换为一个列表
set(s) 转换为可变集合
dict(d) 创建一个字典。d 必须是一个 (key, value) 元组序列。
frozenset(s) 转换为不可变集合
chr(x) 将一个整数转换为对应的字符
ord(x) 将一个字符转换为它的整数值(Unicode 码点)
hex(x) 将一个整数转换为十六进制字符串
oct(x) 将一个整数转换为八进制字符串

条件控制

Python 条件语句是通过一条或多条语句的执行结果(True 或者 False)来决定执行的代码块。

条件判断关键字

关键字 / 函数 说明 示例
if 条件判断语句,当条件为 True 时执行代码块 if x > 0:
elif 多条件判断分支(else if) elif x == 0:
else 所有条件不满足时执行 else:
pass 空语句,占位用,保证语法完整 if x > 0: pass
match 结构化模式匹配(Python 3.10+,类似 switch) match x: case 1: ...

说明:Python 中用 elif 代替了 else if,所以 if 语句的关键字为:if – elif – else

match...case

除了常规 if 语句,Python 3.10 增加了 match...case 的条件判断,不需要再使用一连串的 if-else 来判断了。

match 后的对象会依次与 case 后的内容进行匹配,如果匹配成功,则执行匹配到的表达式,否则直接跳过,_可以匹配一切。 match语句

语法格式如下:

1
2
3
4
5
6
7
8
9
match subject:
case <pattern_1>:
<action_1>
case <pattern_2>:
<action_2>
case <pattern_3>:
<action_3>
case _:
<action_wildcard>

case _: 类似于 C 和 Java 中的 default:,当其他 case 都无法匹配时,匹配这条,保证永远会匹配成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def check_permission(status):
match status:
case 200:
return "OK - 请求成功"
case 301 | 302:
return "Redirect - 重定向"
case 401 | 403 | 404:
return "Not allowed - 无权限或未找到"
case 500 | 502 | 503:
return "Server Error - 服务器错误"
case _:
return "Unknown status - 未知状态码"

for code in [200, 301, 403, 500, 418]:
print(f"状态码 {code}: {check_permission(code)}")

一个 case 也可以设置多个匹配条件,条件使用 | 隔开,例如:

1
2
3
...
case 401|403|404:
return "Not allowed"

循环语句

Python 中的循环语句有 forwhile

循环控制关键字与方法

关键字 / 函数 说明 示例
for 迭代循环,用于遍历序列或可迭代对象 for i in list:
while 条件循环,条件为 True 时持续执行 while x > 0:
break 立即终止当前循环 break:
continue 跳过本次循环剩余代码,进入下一次迭代 continue
else(循环) 循环正常结束(未被 break)时执行 for i in range(3): ... else: ..
pass 循环中的占位语句(空操作) for i in range(5): pass
range() 生成整数序列,常与 for 循环配合使用 range(0, 5)
enumerate() 遍历时同时获取索引和值 for i, v in enumerate(list):

while 语句

1
2
3
4
while <expr>:
<statement(s)>
else:
<additional_statement(s)>

相对于其它语句,Python 中,加入了 else 语句,当 while 后面的条件语句为 false 时,则执行 else 的语句块。

expr 条件语句为 true 则执行 statement(s) 语句块,如果为 false,则执行 additional_statement(s)。

如下实例循环输出数字,并判断大小:

1
2
3
4
5
6
7
8
#!/usr/bin/python3

count = 0
while count < 5:
print (count, " 小于 5")
count = count + 1
else:
print (count, " 大于或等于 5")

执行以上脚本,输出结果如下:

1
2
3
4
5
6
0  小于 5
1 小于 5
2 小于 5
3 小于 5
4 小于 5
5 大于或等于 5

简单语句组

类似 if 语句的语法,如果你的 while 循环体中只有一条语句,你可以将该语句与 while 写在同一行中, 如下所示:

1
2
3
4
5
6
7
#!/usr/bin/python

flag = 1

while (flag): print ('欢迎访问菜鸟教程!')

print ("Good bye!")

for 语句

Python for 循环可以遍历任何可迭代对象,如一个列表或者一个字符串。 for 循环的一般格式如下:

1
2
3
4
for <variable> in <sequence>:
<statements>
else:
<statements>

对比于其它语言,在 for 语句中,加入了 else。当循环执行完毕,会执行 else 子句中的代码,如果在循环过程中遇到了 break 语句,则会中断循环,此时不会执行 else 子句。

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/python3

sites = ["Baidu", "Google","Runoob","Taobao"]
for site in sites:
if site == "Runoob":
print("菜鸟教程!")
break
print("循环数据 " + site)
else:
print("没有循环数据!")
print("完成循环!")

执行脚本后,在循环到 "Runoob"时会跳出循环体,不会执行 else 子句:

1
2
3
4
循环数据 Baidu
循环数据 Google
菜鸟教程!
完成循环!

range() 函数

如果需要遍历数字序列,可以使用内置 range() 函数。它会生成数列,例如:

1
2
3
4
5
6
7
8
>>>for i in range(5):
... print(i)
...
0
1
2
3
4

可以使用 range() 指定区间的值:

1
2
3
4
5
6
7
8
9
>>>for i in range(5,9) :
print(i)


5
6
7
8
>>>

也可以使 range() 以指定数字开始并指定不同的增量(甚至可以是负数,有时这也叫做'步长'):

1
2
3
4
5
6
7
8
9
>>>for i in range(0, 10, 3) :
print(i)


0
3
6
9
>>>

负数:

1
2
3
4
5
6
7
8
>>>for i in range(-10, -100, -30) :
print(i)


-10
-40
-70
>>>

可以结合 range() 和 len() 函数以遍历一个序列的索引,如下所示:

1
2
3
4
5
6
7
8
9
10
>>>a = ['Google', 'Baidu', 'Runoob', 'Taobao', 'QQ']
>>> for i in range(len(a)):
... print(i, a[i])
...
0 Google
1 Baidu
2 Runoob
3 Taobao
4 QQ
>>>

还可以使用 range() 函数来创建一个列表:

1
2
3
>>>list(range(5))
[0, 1, 2, 3, 4]
>>>

推导式

Python 推导式是一种独特的数据处理方式,可以从一个数据序列构建另一个新的数据序列的结构体。

Python 推导式是一种强大且简洁的语法,适用于生成列表、字典、集合和生成器。

在使用推导式时,需要注意可读性,尽量保持表达式简洁,以免影响代码的可读性和可维护性。

Python 支持各种数据结构的推导式:

  • 列表(list)推导式
  • 字典(dict)推导式
  • 集合(set)推导式
  • 元组(tuple)推导式

列表推导式

列表推导式格式为:

1
2
3
4
5
6
7
[表达式 for 变量 in 列表] 
[out_exp_res for out_exp in input_list]

或者

[表达式 for 变量 in 列表 if 条件]
[out_exp_res for out_exp in input_list if condition]

参数说明:

  • out_exp_res:列表生成元素表达式,可以是有返回值的函数。
  • for out_exp in input_list:迭代 input_list 将 out_exp 传入到 out_exp_res 表达式中。
  • if condition:条件语句,可以过滤列表中不符合条件的值。

过滤掉长度小于或等于3的字符串列表,并将剩下的转换成大写字母:

1
2
3
4
>>> names = ['Bob','Tom','alice','Jerry','Wendy','Smith']
>>> new_names = [name.upper()for name in names if len(name)>3]
>>> print(new_names)
['ALICE', 'JERRY', 'WENDY', 'SMITH']

字典推导式

字典推导基本格式:

1
2
3
4
5
{ key_expr: value_expr for value in collection }



{ key_expr: value_expr for value in collection if condition }

使用字符串及其长度创建字典:

1
2
3
4
5
listdemo = ['Google','Runoob', 'Taobao']
# 将列表中各字符串值为键,各字符串的长度为值,组成键值对
>>> newdict = {key:len(key) for key in listdemo}
>>> newdict
{'Google': 6, 'Runoob': 6, 'Taobao': 6}

集合推导式

集合推导式基本格式:

1
2
3
{ expression for item in Sequence }

{ expression for item in Sequence if conditional }

计算数字 1,2,3 的平方数:

1
2
3
>>> setnew = {i**2 for i in (1,2,3)}
>>> setnew
{1, 4, 9}

元组推导式(生成器表达式)

元组推导式可以利用 range 区间、元组、列表、字典和集合等数据类型,快速生成一个满足指定需求的元组。

元组推导式基本格式:

1
2
3
(expression for item in Sequence )

(expression for item in Sequence if conditional )

元组推导式和列表推导式的用法也完全相同,只是元组推导式是用 () 圆括号将各部分括起来,而列表推导式用的是中括号 [],另外元组推导式返回的结果是一个生成器对象。

例如,我们可以使用下面的代码生成一个包含数字 1~9 的元组:

1
2
3
4
5
6
>>> a = (x for x in range(1,10))
>>> a
<generator object <genexpr> at 0x7faf6ee20a50> # 返回的是生成器对象

>>> tuple(a) # 使用 tuple() 函数,可以直接将生成器对象转换成元组
(1, 2, 3, 4, 5, 6, 7, 8, 9)

迭代器与生成器

迭代器

迭代是 Python 最强大的功能之一,是访问集合元素的一种方式。

迭代器是一个可以记住遍历的位置的对象。

迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

迭代器有两个基本的方法:iter()next()

字符串,列表或元组对象都可用于创建迭代器

1
2
3
4
5
6
7
>>> list=[1,2,3,4]
>>> it = iter(list) # 创建迭代器对象
>>> print (next(it)) # 输出迭代器的下一个元素
1
>>> print (next(it))
2
>>>

迭代器对象可以使用常规for语句进行遍历:

1
2
3
4
5
6
#!/usr/bin/python3

list=[1,2,3,4]
it = iter(list) # 创建迭代器对象
for x in it:
print (x, end=" ")

也可以使用 next() 函数:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/python3

import sys # 引入 sys 模块

list=[1,2,3,4]
it = iter(list) # 创建迭代器对象

while True:
try:
print (next(it))
except StopIteration:
sys.exit()

创建一个迭代器

把一个类作为一个迭代器使用需要在类中实现两个方法 __iter__()__next__()

__iter__() 方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 __next__() 方法并通过 StopIteration 异常标识迭代的完成。

__next__() 方法(Python 2 里是 next())会返回下一个迭代器对象。

创建一个返回数字的迭代器,初始值为 1,逐步递增 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MyNumbers:
def __iter__(self):
self.a = 1
return self

def __next__(self):
x = self.a
self.a += 1
return x

myclass = MyNumbers()
myiter = iter(myclass)

print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))

StopIteration

StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 __next__() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。

在 20 次迭代后停止执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MyNumbers:
def __iter__(self):
self.a = 1
return self

def __next__(self):
if self.a <= 20:
x = self.a
self.a += 1
return x
else:
raise StopIteration

myclass = MyNumbers()
myiter = iter(myclass)

for x in myiter:
print(x)

生成器

在 Python 中,使用了 yield 的函数被称为生成器(generator)。

yield 是一个关键字,用于定义生成器函数,生成器函数是一种特殊的函数,可以在迭代过程中逐步产生值,而不是一次性返回所有结果。

跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。

当在生成器函数中使用 yield 语句时,函数的执行将会暂停,并将 yield 后面的表达式作为当前迭代的值返回。

然后,每次调用生成器的 next() 方法或使用 for 循环进行迭代时,函数会从上次暂停的地方继续执行,直到再次遇到 yield 语句。这样,生成器函数可以逐步产生值,而不需要一次性计算并返回所有结果。

调用一个生成器函数,返回的是一个迭代器对象。

下面是一个简单的示例,展示了生成器函数的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def countdown(n):
while n > 0:
yield n
n -= 1

# 创建生成器对象
generator = countdown(5)

# 通过迭代生成器获取值
print(next(generator)) # 输出: 5
print(next(generator)) # 输出: 4
print(next(generator)) # 输出: 3

# 使用 for 循环迭代生成器
for value in generator:
print(value) # 输出: 2 1

以上实例中,countdown 函数是一个生成器函数。它使用 yield 语句逐步产生从 n 到 1 的倒数数字。在每次调用 yield 语句时,函数会返回当前的倒数值,并在下一次调用时从上次暂停的地方继续执行。

通过创建生成器对象并使用 next() 函数或 for 循环迭代生成器,我们可以逐步获取生成器函数产生的值。在这个例子中,首先使用 next() 函数获取前三个倒数值,然后通过 for 循环获取剩下的两个倒数值。

生成器函数的优势是它们可以按需生成值,避免一次性生成大量数据并占用大量内存。此外,生成器还可以与其他迭代工具(如for循环)无缝配合使用,提供简洁和高效的迭代方式。

with 关键字

with 是 Python 中的一个关键字,用于上下文管理协议(Context Management Protocol)。它简化了资源管理代码,特别是那些需要明确释放或清理的资源(如文件、网络连接、数据库连接等)。

为什么需要 with 语句?

传统资源管理的问题

先看一个典型的文件操作示例:

1
2
3
4
5
6
file = open('example.txt', 'r')
try:
content = file.read()
# 处理文件内容
finally:
file.close()

这种写法存在几个问题:

  1. 容易忘记关闭资源:如果没有 try-finally 块,可能会忘记调用 close();
  2. 代码冗长:简单的文件操作需要多行代码;
  3. 异常处理复杂:需要手动处理可能出现的异常。

with 语句的优势

with 语句通过上下文管理协议(Context Management Protocol)解决了这些问题:

  1. 自动资源释放:确保资源在使用后被正确关闭;
  2. 代码简洁:减少样板代码;
  3. 异常安全:即使在代码块中发生异常,资源也会被正确释放;
  4. 可读性强:明确标识资源的作用域。

with 语句的基本语法

基础用法

with 语句的基本形式如下:

1
2
with expression [as variable]:
# 代码块

参数说明:

  • expression 返回一个支持上下文管理协议的对象;
  • as variable 是可选的,用于将表达式结果赋值给变量;
  • 代码块执行完毕后,自动调用清理方法。

文件操作示例

最常见的 with 语句应用是文件操作:

1
2
3
4
with open('example.txt', 'r') as file:
content = file.read()
print(content)
# 文件已自动关闭

这段代码等价于前面的 try-finally 实现,但更加简洁明了。

with 语句的工作原理

上下文管理协议

with 语句背后是 Python 的上下文管理协议,该协议要求对象实现两个方法:

  1. __enter__():进入上下文时调用,返回值赋给 as 后的变量;
  2. __exit__():退出上下文时调用,处理清理工作。

执行流程: with 执行流程

异常处理机制

__exit__() 方法接收三个参数:

  • exc_type:异常类型
  • exc_val:异常值
  • exc_tb:异常追踪信息

如果 __exit__() 返回 True,则表示异常已被处理,不会继续传播;返回 FalseNone,异常会继续向外传播。

函数

函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。函数能提高应用的模块性,和代码的重复利用率。

定义一个函数

定义一个函数,有如下规则:

  • 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()
  • 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
  • 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
  • 函数内容以冒号 : 起始,并且缩进。
  • return [表达式] 结束函数,选择性地返回一个值给调用方,不带表达式的 return 相当于返回 None函数定义

语法

Python 定义函数使用 def 关键字,一般格式如下:

1
2
def 函数名(参数列表):
函数体
默认情况下,参数值和参数名称是按函数声明中定义的顺序匹配起来的。

实例

如下的函数,定义了两个参数,并比较两个数,最后返回较大的数:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/python3

def max(a, b):
if a > b:
return a
else:
return b

a = 4
b = 5
# 函数调用
print(max(a, b))

参数传递

在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象

  • 不可变类型:变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变 a 的值,相当于新生成了 a。
  • 可变类型:变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。

python 函数的参数传递:

  • 不可变类型:类似 C++ 的值传递,如整数、字符串、元组。如 fun(a),传递的只是 a 的值,没有影响 a 对象本身。如果在 fun(a) 内部修改 a 的值,则是新生成一个 a 的对象。
  • 可变类型:类似 C++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后 fun 外部的 la 也会受影响。

python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。

参数

调用函数时可使用的参数类型有:

  • 必需参数
  • 关键字参数
  • 默认参数
  • 不定长参数

必需参数

必需参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样。

调用 printme() 函数,你必须传入一个参数,不然会出现语法错误:

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/python3

#可写函数说明
def printme( str ):
"打印任何传入的字符串"
print (str)
return

# 调用 printme 函数,不加参数会报错
printme()

关键字参数

关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。

使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。

以下实例在函数 printme() 调用时使用参数名:

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/python3

#可写函数说明
def printinfo( name, age ):
"打印任何传入的字符串"
print ("名字: ", name)
print ("年龄: ", age)
return

#调用printinfo函数
printinfo( age=50, name="runoob" )

默认参数

调用函数时,如果没有传递参数,则会使用默认参数。以下实例中如果没有传入 age 参数,则使用默认值:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/python3

#可写函数说明
def printinfo( name, age = 35 ):
"打印任何传入的字符串"
print ("名字: ", name)
print ("年龄: ", age)
return

#调用printinfo函数
printinfo( age=50, name="runoob" )
print ("------------------------")
printinfo( name="runoob" )

不定长参数

在有些场景下,可能需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数,和上述 2 种参数不同,声明时不会命名。基本语法如下:

1
2
3
4
def functionname([formal_args,] *var_args_tuple ):
"函数_文档字符串"
function_suite
return [expression]

加了星号 * 的参数会以元组(tuple)的形式导入,存放所有未命名的变量参数。

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/python3

# 可写函数说明
def printinfo( arg1, *vartuple ):
"打印任何传入的参数"
print ("输出: ")
print (arg1)
print (vartuple)

# 调用printinfo 函数
printinfo( 70, 60, 50 )

以上实例输出结果:

1
2
3
输出: 
70
(60, 50)

还有一种就是参数带两个星号 ** 基本语法如下:

1
2
3
4
def functionname([formal_args,] **var_args_dict ):
"函数_文档字符串"
function_suite
return [expression]

加了两个星号 ** 的参数会以字典的形式导入。

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/python3

# 可写函数说明
def printinfo( arg1, **vardict ):
"打印任何传入的参数"
print ("输出: ")
print (arg1)
print (vardict)

# 调用printinfo 函数
printinfo(1, a=2,b=3)

以上实例输出结果:

1
2
3
输出: 
1
{'a': 2, 'b': 3}

声明函数时,参数中星号 * 可以单独出现,例如:

1
2
def f(a,b,*,c):
return a+b+c

如果单独出现星号 *,则星号 * 后的参数必须用关键字传入:

1
2
3
4
5
6
7
8
9
10
>>> def f(a,b,*,c):
... return a+b+c
...
>>> f(1,2,3) # 报错
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() takes 2 positional arguments but 3 were given
>>> f(1,2,c=3) # 正常
6
>>>

强制位置参数

Python3.8 新增了一个函数形参语法 / 用来指明函数形参必须使用指定位置参数,不能使用关键字参数的形式。

在以下的例子中,形参 a 和 b 必须使用指定位置参数,c 或 d 可以是位置形参或关键字形参,而 e 和 f 要求为关键字形参:

1
2
def f(a, b, /, c, d, *, e, f):
print(a, b, c, d, e, f)

以下使用方法是正确的:

1
f(10, 20, 30, d=40, e=50, f=60)

以下使用方法会发生错误:

1
2
f(10, b=20, c=30, d=40, e=50, f=60)   # b 不能使用关键字参数的形式
f(10, 20, 30, 40, 50, f=60) # e 必须使用关键字参数的形式

return 语句

return [表达式]语句用于退出函数,选择性地向调用方返回一个表达式。不带参数值的 return 语句返回 None。之前的例子都没有示范如何返回数值,以下实例演示了 return 语句的用法:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/python3

# 可写函数说明
def sum( arg1, arg2 ):
# 返回2个参数的和."
total = arg1 + arg2
print ("函数内 : ", total)
return total

# 调用sum函数
total = sum( 10, 20 )
print ("函数外 : ", total)

匿名函数

Python 使用 lambda 来创建匿名函数。

所谓匿名,意即不再使用 def 语句这样标准的形式定义一个函数。

  • lambda 只是一个表达式,函数体比 def 简单很多。
  • lambda 的主体是一个表达式,而不是一个代码块。仅仅能在 lambda 表达式中封装有限的逻辑进去。
  • lambda 函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
  • 虽然 lambda 函数看起来只能写一行,却不等同于 C 或 C++ 的内联函数,内联函数的目的是调用小函数时不占用栈内存从而减少函数调用的开销,提高代码的执行速度。

语法

lambda 函数的语法只包含一个语句,如下:

1
lambda [arg1 [,arg2,.....argn]]:expression

以下实例匿名函数设置两个参数:

1
2
3
4
5
6
7
8
#!/usr/bin/python3

# 可写函数说明
sum = lambda arg1, arg2: arg1 + arg2

# 调用sum函数
print ("相加后的值为 : ", sum( 10, 20 ))
print ("相加后的值为 : ", sum( 20, 20 ))

可以将匿名函数封装在一个函数内,这样可以使用同样的代码来创建多个匿名函数。

以下实例将匿名函数封装在 myfunc 函数中,通过传入不同的参数来创建不同的匿名函数:

1
2
3
4
5
6
7
8
def myfunc(n):
return lambda a : a * n

mydoubler = myfunc(2)
mytripler = myfunc(3)

print(mydoubler(11))
print(mytripler(11))

装饰器

装饰器(decorator)是 Python 中的一种高级功能,用于在不修改原函数代码的前提下,动态扩展函数或类的功能

本质上,装饰器是一个函数:它接收一个函数作为参数,并返回一个新的函数(通常是对原函数的增强版本)。类似于 Java 语言中切面的概念。 装饰器

装饰器通过 @decorator_name 语法应用在函数或方法定义之前。

Python 还提供了一些内置装饰器,例如 @staticmethod@classmethod

常见应用场景:

  • 日志记录:记录函数调用信息、参数和返回值
  • 性能统计:统计函数执行时间
  • 权限控制:限制函数访问权限
  • 缓存:缓存函数结果,提高性能

基本语法

装饰器的核心思想是:用一个函数"包装"另一个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def decorator_function(original_function):
def wrapper(*args, **kwargs):
# 调用前
print("执行前")

result = original_function(*args, **kwargs)

# 调用后
print("执行后")

return result
return wrapper

@decorator_function
def target_function():
print("原函数执行")

解析:

  • decorator_function:装饰器函数(接收原函数)
  • wrapper:包装函数(真正被执行)
  • @decorator_function:等价于函数替换

等价写法:

1
target_function = decorator_function(target_function)

调用 target_function() 时,实际上执行的是 wrapper().

使用装饰器

装饰器通过 @ 语法糖应用在函数定义前:

1
2
3
@time_logger
def target_function():
pass

等价于:

1
2
3
4
def target_function():
pass

target_function = time_logger(target_function)

这种机制使我们可以在不修改原函数的情况下,统一添加功能(如日志、权限等)。

1
2
3
4
5
6
7
8
9
10
11
12
def my_decorator(func):
def wrapper():
print("函数执行前")
func()
print("函数执行后")
return wrapper

@my_decorator
def say_hello():
print("Hello!")

say_hello()

输出:

1
2
3
函数执行前
Hello!
函数执行后

说明: - my_decorator 接收 say_hello - @my_decorator 替换原函数

类装饰器

除了函数,装饰器也可以作用于类。

类装饰器接收一个类,并返回修改后的类或包装类:

  • 增强类方法
  • 控制实例化过程
  • 实现单例、日志等功能

函数形式的类装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def log_class(cls):
class Wrapper:
def __init__(self, *args, **kwargs):
self.wrapped = cls(*args, **kwargs)

def __getattr__(self, name):
return getattr(self.wrapped, name)

def display(self):
print("调用前")
self.wrapped.display()
print("调用后")

return Wrapper

@log_class
class MyClass:
def display(self):
print("原方法")

obj = MyClass()
obj.display()

类形式的类装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class SingletonDecorator:
def __init__(self, cls):
self.cls = cls
self.instance = None

def __call__(self, *args, **kwargs):
if self.instance is None:
self.instance = self.cls(*args, **kwargs)
return self.instance

@SingletonDecorator
class Database:
def __init__(self):
print("初始化")

db1 = Database()
db2 = Database()
print(db1 is db2)

内置装饰器

常用内置装饰器:

  • @staticmethod:定义静态方法
  • @classmethod:定义类方法
  • @property:将方法变为属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyClass:
@staticmethod
def static_method():
print("静态方法")

@classmethod
def class_method(cls):
print(cls.__name__)

@property
def name(self):
return self._name

@name.setter
def name(self, value):
self._name = value

多个装饰器的堆叠

多个装饰器 从下到上依次执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def decorator1(func):
def wrapper():
print("Decorator 1")
func()
return wrapper

def decorator2(func):
def wrapper():
print("Decorator 2")
func()
return wrapper

@decorator1
@decorator2
def say_hello():
print("Hello!")

say_hello()

输出:

1
2
3
Decorator 1
Decorator 2
Hello!

核心总结

装饰器 = 函数包装函数 + 不修改原代码扩展功能

  • @语法本质是函数替换
  • wrapper 才是真正执行的函数
  • 推荐使用 *args, **kwargs 提高通用性
  • 支持函数、类、甚至带参数的装饰器

模块

Python 中的模块(Module)是一个包含 Python 定义和语句的文件,文件名就是模块名加上 .py 后缀。

模块可以包含函数、类、变量以及可执行的代码。通过模块,可以将代码组织成可重用的单元,便于管理和维护。

模块的作用

  • 代码复用:将常用的功能封装到模块中,可以在多个程序中重复使用。
  • 命名空间管理:模块可以避免命名冲突,不同模块中的同名函数或变量不会互相干扰。
  • 代码组织:将代码按功能划分到不同的模块中,使程序结构更清晰。

模块的实例:

1
2
3
4
5
6
#!/usr/bin/python3
# Filename: support.py

def print_func( par ):
print ("Hello : ", par)
return

上述模块中包含了一个 print_func 函数,可以被其它模块引入使用。

import 语句

想使用 Python 源文件,只需在另一个源文件里执行 import 语句,语法如下:

1
import module1[, module2[,... moduleN]

当解释器遇到 import 语句,如果模块在当前的搜索路径就会被导入。

搜索路径时一个解释器会先进行搜索的所有目录的列表。如想要导入模块 support,需要把命令放在脚本的顶端:

1
2
3
4
5
6
7
8
#!/usr/bin/python3
# Filename: test.py

# 导入模块
import support

# 现在可以调用模块里包含的函数了
support.print_func("Runoob")

一个模块只会被导入一次,不管你执行了多少次 import。这样可以防止导入模块被一遍又一遍地执行。

模块的搜索路径

使用 import 语句的时候,Python 解释器是怎样找到对应的文件的呢?

这就涉及到 Python 的搜索路径,搜索路径是由一系列目录名组成的,Python 解释器就依次从这些目录中去寻找所引入的模块。

当导入一个模块时,Python 会按照以下顺序查找模块:

  1. 当前目录。
  2. 环境变量 PYTHONPATH 指定的目录。
  3. Python 标准库目录。
  4. .pth 文件中指定的目录。

搜索路径是在 Python 编译或安装的时候确定的,安装新的库应该也会修改。

sys.path 变量

在 sys模块中,定义 sys.path变量, 它包含了一个 Python 解释器自动查找所需模块的路径的列表。

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/python3
# 文件名: using_sys.py

import sys

print('命令行参数如下:')
for i in sys.argv:
print(i)

print('\n\nPython 路径为:', sys.path, '\n')

执行结果如下所示:

1
2
3
4
5
6
7
8
$ python using_sys.py 参数1 参数2
命令行参数如下:
using_sys.py
参数1
参数2


Python 路径为: ['/root', '/usr/lib/python3.4', '/usr/lib/python3.4/plat-x86_64-linux-gnu', '/usr/lib/python3.4/lib-dynload', '/usr/local/lib/python3.4/dist-packages', '/usr/lib/python3/dist-packages']

变量说明:

  • import sys 引入 python 标准库中的 sys.py 模块;这是引入某一模块的方法。
  • sys.argv 是一个包含命令行参数的列表。
  • sys.path 包含了一个 Python 解释器自动查找所需模块的路径的列表。

from … import 语句

Python 的 from 语句从模块中导入一个指定的部分到当前命名空间中,语法如下:

1
from modname import name1[, name2[, ... nameN]]

例如:

1
from support import print_func

这个声明不会把整个 support 模块导入到当前的命名空间中,它只会将 support 里的 print_func 函数引入进来。

给模块起别名

使用 as 关键字为模块或函数起别名:

1
2
import numpy as np  # 将 numpy 模块别名设置为 np
from math import sqrt as square_root # 将 sqrt 函数别名设置为 square_root

from … import * 语句

把一个模块的所有内容全都导入到当前的命名空间也是可行的,只需使用如下声明:

1
from modname import *

这提供了一个简单的方法来导入一个模块中的所有项目。

注意:不推荐,容易引起命名冲突。

包是一种管理 Python 模块命名空间的形式,采用"点模块名称"。比如一个模块的名称是 A.B, 那么他表示一个包 A中的子模块 B 。

假设现在要设计一套统一处理声音文件和数据的模块(或者称之为一个"包")。

现存很多种不同的音频文件格式(基本上都是通过后缀名区分的,例如: .wav,:file:.aiff,:file:.au,),所以你需要有一组不断增加的模块,用来在不同的格式之间转换。

并且针对这些音频数据,还有很多不同的操作(比如混音,添加回声,增加均衡器功能,创建人造立体声效果),所以你还需要一组怎么也写不完的模块来处理这些操作。

这里给出了一种可能的包结构(在分层的文件系统中):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
sound/                          顶层包
__init__.py 初始化 sound 包
formats/ 文件格式转换子包
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
effects/ 声音效果子包
__init__.py
echo.py
surround.py
reverse.py
...
filters/ filters 子包
__init__.py
equalizer.py
vocoder.py
karaoke.py
...

在导入一个包的时候,Python 会根据 sys.path 中的目录来寻找这个包中包含的子目录。

__init__.py

目录只有包含一个叫做 __init__.py 的文件才会被认作是一个包,最简单的情况,放一个空的 __init__.py 就可以了。当然这个文件中也可以包含一些初始化代码或者为 __all__ 变量赋值。

引入包下的模块

用户可以每次只导入一个包里面的特定模块,比如:

1
import sound.effects.echo

这将会导入子模块:sound.effects.echo。 必须使用全名去访问:

1
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

还有一种导入子模块的方法是:

1
from sound.effects import echo

这同样会导入子模块: echo,并且不需要那些冗长的前缀,所以可以这样使用:

1
echo.echofilter(input, output, delay=0.7, atten=4)

还有一种变化就是直接导入一个函数或者变量:

1
from sound.effects.echo import echofilter

同样的,这种方法会导入子模块: echo,并且可以直接使用他的 echofilter() 函数:

1
echofilter(input, output, delay=0.7, atten=4)

注意当使用 from package import item 这种形式的时候,对应的 item 既可以是包里面的子模块(子包),或者包里面定义的其他名称,比如函数,类或者变量。

import 语法会首先把 item 当作一个包定义的名称,如果没找到,再试图按照一个模块去导入。如果还没找到,抛出一个 :exc:ImportError 异常。

反之,如果使用形如 import item.subitem.subsubitem 这种导入形式,除了最后一项,都必须是包,而最后一项则可以是模块或者是包,但是不可以是类,函数或者变量的名字。

从一个包中导入 *

如果使用 from sound.effects import * 会发生什么呢?

Python 会进入文件系统,找到这个包里面所有的子模块,然后一个一个的把它们都导入进来。

由于在不同系统中,对目录中的大小写字符处理不同,如在 Windows 平台中,它不区分大小写。这样会导致模块的引用会不惟一,Python 提供一个精确包的索引。

导入语句遵循如下规则:如果包定义文件 __init__.py 存在一个叫做 __all__ 的列表变量,那么在使用 from package import * 的时候就把这个列表中的所有名字作为包内容导入。

作为包的作者,需要在更新包之后对 __all__ 变量进行更新。

以下实例在 sounds/effects/__init__.py 中包含如下代码:

1
__all__ = ["echo", "surround", "reverse"]

这表示当使用 from sound.effects import *这种用法时,只会导入包里面这三个子模块。

__name____main__

在 Python 中,__name____main__ 是两个与模块和脚本执行相关的特殊变量。

__name____main__ 通常用于控制代码的执行方式,尤其是在模块既可以作为独立脚本运行,也可以被其他模块导入时。

__name__ 变量

__name__ 是一个内置变量,用于表示当前模块的名称。

__name__ 的值取决于模块是如何被使用的:

当模块作为主程序运行时:__name__的值被设置为 "__main__"。

当模块被导入时:__name__ 的值被设置为模块的文件名(不包括 .py 扩展名)。

__main__ 的含义

__main__ 是一个特殊的字符串,用于表示当前模块是作为主程序运行的。

__main__ 通常与 __name__ 变量一起使用,以确定模块是被导入还是作为独立脚本运行。

常见模式

在 Python 中,常见的做法是在模块的末尾添加以下代码块:

1
2
3
if __name__ == "__main__":
# 这里的代码只有在模块作为主程序运行时才会执行
main()

这种模式允许模块在被导入时不会执行某些代码,而只有在作为独立脚本运行时才会执行这些代码。

异常处理

运行期检测到的错误被称为异常,大多数的异常都不会被程序处理,都以错误信息的形式展现在这里。

处理流程

在 Python 中,异常捕捉的完整流程如下所示: 异常处理

try 语句按照如下方式工作;

  • 首先,执行 try 子句(在关键字 try 和关键字 except 之间的语句)。
  • 如果没有异常发生,忽略 except 子句,try 子句执行后结束。
  • 如果在执行 try 子句的过程中发生了异常,那么 try 子句余下的部分将被忽略。如果异常的- 类型和 except 之后的名称相符,那么对应的 except 子句将被执行。
  • 如果发生异常或没有匹配到异常,则执行 else 子句。
  • 最后执行 final 字句,final 子句无论如何都会执行。

抛出异常

Python 使用 raise 语句抛出一个指定的异常。 raise语法格式如下:

1
raise [Exception [, args [, traceback]]]

以下实例如果 x 大于 5 就触发异常:

1
2
3
x = 10
if x > 5:
raise Exception('x 不能大于 5。x 的值为: {}'.format(x))

raise 唯一的一个参数指定了要被抛出的异常。它必须是一个异常的实例或者是异常的类(也就是 Exception 的子类)。

如果只想知道这是否抛出了一个异常,并不想去处理它,那么一个简单的 raise 语句就可以再次把它抛出。

1
2
3
4
5
6
7
8
9
10
>>> try:
raise NameError('HiThere') # 模拟一个异常。
except NameError:
print('An exception flew by!')
raise

An exception flew by!
Traceback (most recent call last):
File "<stdin>", line 2, in ?
NameError: HiThere

用户自定义异常

可以通过创建一个新的异常类来拥有自己的异常。异常类继承自 Exception 类,可以直接继承,或者间接继承,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> class MyError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)

>>> try:
raise MyError(2*2)
except MyError as e:
print('My exception occurred, value:', e.value)

My exception occurred, value: 4
>>> raise MyError('oops!')
Traceback (most recent call last):
File "<stdin>", line 1, in ?
__main__.MyError: 'oops!'

在这个例子中,类 Exception 默认的 __init__() 被覆盖。

当创建一个模块有可能抛出多种不同的异常时,一种通常的做法是为这个包建立一个基础异常类,然后基于这个基础类为不同的错误情况创建不同的子类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Error(Exception):
"""Base class for exceptions in this module."""
pass

class InputError(Error):
"""Exception raised for errors in the input.

Attributes:
expression -- input expression in which the error occurred
message -- explanation of the error
"""

def __init__(self, expression, message):
self.expression = expression
self.message = message

class TransitionError(Error):
"""Raised when an operation attempts a state transition that's not
allowed.

Attributes:
previous -- state at beginning of transition
next -- attempted new state
message -- explanation of why the specific transition is not allowed
"""

def __init__(self, previous, next, message):
self.previous = previous
self.next = next
self.message = message

大多数的异常的名字都以 "Error" 结尾,就跟标准的异常命名一样。

面向对象

Python 是一门面向对象的编程语言,在 Python 中创建一个类和对象是很容易的。

面向对象技术简介

  • 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
  • 方法:类中定义的函数。
  • 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
  • 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
  • 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
  • 局部变量:定义在方法中的变量,只作用于当前实例的类。
  • 实例变量:在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
  • 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
  • 实例化:创建一个类的实例,类的具体对象。
  • 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

Python 中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法。

对象可以包含任意数量和类型的数据。

类定义

语法格式如下:

1
2
3
4
5
6
class ClassName:
<statement-1>
.
.
.
<statement-N>

类实例化后,可以使用其属性,实际上,创建一个类之后,可以通过类名访问其属性。

类对象

类对象支持两种操作:属性引用和实例化。

属性引用使用和 Python 中所有的属性引用一样的标准语法:obj.name

类对象创建后,类命名空间中所有的命名都是有效属性名。所以如果类定义是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/python3

class MyClass:
"""一个简单的类实例"""
i = 12345
def f(self):
return 'hello world'

# 实例化类
x = MyClass()

# 访问类的属性和方法
print("MyClass 类的属性 i 为:", x.i)
print("MyClass 类的方法 f 输出为:", x.f())

以上创建了一个新的类实例并将该对象赋给局部变量 x,x 为空的对象

构造方法

类有一个名为 __init__() 的特殊方法(构造方法),该方法在类实例化时会自动调用,像下面这样:

1
2
def __init__(self):
self.data = []

类定义了 __init__() 方法,类的实例化操作会自动调用 __init__() 方法。如下实例化类 MyClass,对应的 __init__() 方法就会被调用:

1
x = MyClass()

__init__() 方法可以有参数,参数通过 __init__() 传递到类的实例化操作上。例如:

1
2
3
4
5
6
7
8
#!/usr/bin/python3

class Complex:
def __init__(self, realpart, imagpart):
self.r = realpart
self.i = imagpart
x = Complex(3.0, -4.5)
print(x.r, x.i) # 输出结果:3.0 -4.5

self

类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self

1
2
3
4
5
6
7
class Test:
def prt(self):
print(self)
print(self.__class__)

t = Test()
t.prt()

以上实例执行结果为

1
2
<__main__.Test instance at 0x100771878>
__main__.Test
从执行结果可以很明显的看出,self 代表的是类的实例,代表当前对象的地址,而 self.class 则指向类。

在 Python中,self 是一个惯用的名称,用于表示类的实例(对象)自身。它是一个指向实例的引用,使得类的方法能够访问和操作实例的属性。

当定义一个类,并在类中定义方法时,第一个参数通常被命名为 self,尽管可以使用其他名称,但强烈建议使用 self,以保持代码的一致性和可读性。

1
2
3
4
5
6
7
8
9
10
11
12
class MyClass:
def __init__(self, value):
self.value = value

def display_value(self):
print(self.value)

# 创建一个类的实例
obj = MyClass(42)

# 调用实例的方法
obj.display_value() # 输出 42

在上面的例子中,self 是一个指向类实例的引用,它在 __init__ 构造函数中用于初始化实例的属性,也在 display_value 方法中用于访问实例的属性。通过使用 self,可以在类的方法中访问和操作实例的属性,从而实现类的行为。

类的方法

在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self, 且为第一个参数,self 代表的是类的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/python3

#类定义
class people:
#定义基本属性
name = ''
age = 0
#定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
#定义构造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 说: 我 %d 岁。" %(self.name,self.age))

# 实例化类
p = people('runoob',10,30)
p.speak()

继承

Python 同样支持类的继承,如果一种语言不支持继承,类就没有什么意义。派生类的定义如下所示:

1
2
3
4
5
6
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>

子类(派生类 DerivedClassName)会继承父类(基类 BaseClassName)的属性和方法。

BaseClassName(实例中的基类名)必须与派生类定义在一个作用域内。除了类,还可以用表达式,基类定义在另一个模块中时这一点非常有用:

1
class DerivedClassName(modname.BaseClassName):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/python3

#类定义
class people:
#定义基本属性
name = ''
age = 0
#定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
#定义构造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 说: 我 %d 岁。" %(self.name,self.age))

#单继承示例
class student(people):
grade = ''
def __init__(self,n,a,w,g):
#调用父类的构函
people.__init__(self,n,a,w)
self.grade = g
#覆写父类的方法
def speak(self):
print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))



s = student('ken',10,60,3)
s.speak()

多继承

Python同样有限的支持多继承形式。多继承的类定义形如下例:

1
2
3
4
5
6
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>

需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/usr/bin/python3

#类定义
class people:
#定义基本属性
name = ''
age = 0
#定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
#定义构造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 说: 我 %d 岁。" %(self.name,self.age))

#单继承示例
class student(people):
grade = ''
def __init__(self,n,a,w,g):
#调用父类的构函
people.__init__(self,n,a,w)
self.grade = g
#覆写父类的方法
def speak(self):
print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))

#另一个类,多继承之前的准备
class speaker():
topic = ''
name = ''
def __init__(self,n,t):
self.name = n
self.topic = t
def speak(self):
print("我叫 %s,我是一个演说家,我演讲的主题是 %s"%(self.name,self.topic))

#多继承
class sample(speaker,student):
a =''
def __init__(self,n,a,w,g,t):
student.__init__(self,n,a,w,g)
speaker.__init__(self,n,t)

test = sample("Tim",25,80,4,"Python")
test.speak() #方法名同,默认调用的是在括号中参数位置排前父类的方法

方法重写

如果父类方法的功能不能满足需求,可以在子类重写父类的方法,实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/python3

class Parent: # 定义父类
def myMethod(self):
print ('调用父类方法')

class Child(Parent): # 定义子类
def myMethod(self):
print ('调用子类方法')

c = Child() # 子类实例
c.myMethod() # 子类调用重写方法
super(Child,c).myMethod() #用子类对象调用父类已被覆盖的方法

super() 函数是用于调用父类(超类)的一个方法。 执行以上程序输出结果为:

1
2
调用子类方法
调用父类方法

类属性与方法

类的私有属性

__private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/python3

class JustCounter:
__secretCount = 0 # 私有变量
publicCount = 0 # 公开变量

def count(self):
self.__secretCount += 1
self.publicCount += 1
print (self.__secretCount)

counter = JustCounter()
counter.count()
counter.count()
print (counter.publicCount)
print (counter.__secretCount) # 报错,实例不能访问私有变量

类的方法

在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数,self 代表的是类的实例。

self 的名字并不是规定死的,也可以使用 this,但是最好还是按照约定使用 self

类的私有方法

__private_method:两个下划线开头,声明该方法为私有方法,只能在类的内部调用,不能在类的外部调用。内部调用使用:self.__private_methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/python3

class Site:
def __init__(self, name, url):
self.name = name # public
self.__url = url # private

def who(self):
print('name : ', self.name)
print('url : ', self.__url)

def __foo(self): # 私有方法
print('这是私有方法')

def foo(self): # 公共方法
print('这是公共方法')
self.__foo()

x = Site('菜鸟教程', 'www.runoob.com')
x.who() # 正常输出
x.foo() # 正常输出
x.__foo() # 报错

类的专有方法

  • __init__ : 构造函数,在生成对象时调用
  • __del__ : 析构函数,释放对象时使用
  • __repr__ : 打印,转换
  • __setitem__ : 按照索引赋值
  • __getitem__: 按照索引获取值
  • __len__: 获得长度
  • __cmp__: 比较运算
  • __call__: 函数调用
  • __add__: 加运算
  • __sub__: 减运算
  • __mul__: 乘运算
  • __truediv__: 除运算
  • __mod__: 求余运算
  • __pow__: 乘方

运算符重载

Python同样支持运算符重载,可以对类的专有方法进行重载,实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/python3

class Vector:
def __init__(self, a, b):
self.a = a
self.b = b

def __str__(self):
return 'Vector (%d, %d)' % (self.a, self.b)

def __add__(self,other):
return Vector(self.a + other.a, self.b + other.b)

v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)

类型注解

类型注解(Type Hints) 在编程中就扮演着如下的角色——它是一种为代码添加"说明标签"的技术,明确地指出变量、函数参数和返回值应该是什么数据类型。

简单来说,类型注解就是在代码中注明数据类型的语法,它的核心目的是:

  • 提高代码可读性:让他人(以及未来的你)一眼就能看懂代码的意图
  • 便于静态检查:在运行代码前,通过工具发现潜在的类型错误
  • 增强IDE支持:让代码编辑器提供更准确的自动补全和提示

一个简单的例子:

1
2
3
4
5
6
7
# 没有类型注解
def greet(name):
return f"Hello, {name}"

# 有类型注解
def greet(name: str) -> str:
return f"Hello, {name}"

第二段代码明确指出了 name 应该是字符串类型(str),函数会返回一个字符串(-> str)

为什么需要类型注解?

Python 以其动态类型特性而闻名——不需要提前声明变量的类型,解释器会在运行时自动推断。这虽然灵活,但也带来了问题:

  1. 代码难以理解:看到一个函数时,不清楚应该传入什么类型的数据
  2. 隐藏的bug:可能不小心传入了错误类型,直到运行时才报错
  3. 开发效率低:IDE无法提供准确的代码提示和补全

类型注解通过提供可选的类型信息来解决这些问题,让代码更加健壮和可维护。

基础语法详解

变量注解

从 Python 3.6 开始,可以直接为变量添加类型注解:

1
2
3
4
5
6
7
8
9
10
11
# 没有类型注解的代码
name = "Alice"
age = 30
is_student = False
scores = [95, 88, 91]

# 有类型注解的代码
name: str = "Alice" # 注解为字符串 (str)
age: int = 30 # 注解为整数 (int)
is_student: bool = False # 注解为布尔值 (bool)
scores: list = [95, 88, 91] # 注解为列表 (list)

说明:name: str 读作 "变量 name 的类型是 str"

函数注解

在函数参数后加 : 类型

1
2
3
4
5
6
7
8
9
# 没有类型注解的函数
def greet(first_name, last_name):
full_name = first_name + " " + last_name
return "Hello, " + full_name

# 有类型注解的函数
def greet(first_name: str, last_name: str) -> str:
full_name = first_name + " " + last_name
return "Hello, " + full_name

解读这个函数:

  • first_name: str:参数 first_name 应该是字符串。
  • last_name: str:参数 last_name 应该是字符串。
  • -> str:这个函数执行后会返回一个字符串。

现在,任何人调用这个函数时,都能清晰地知道需要传递什么,以及会得到什么。

函数注解是类型注解最常见的应用场景。

1
2
3
4
5
6
7
def add_numbers(a: int, b: int) -> int:
"""将两个整数相加并返回结果"""
return a + b

# 调用函数
result = add_numbers(5, 3) # 正确:两个整数
# result = add_numbers("5", "3") # 可能有问题:虽然能运行,但类型检查器会警告

参数默认值

可以同时使用类型注解和默认值:

1
2
3
4
5
6
def say_hello(name: str, times: int = 1) -> str:
"""向某人问好指定次数"""
return " ".join([f"Hello, {name}!"] * times)

print(say_hello("Bob")) # 输出:Hello, Bob!
print(say_hello("Alice", 3)) # 输出:Hello, Alice! Hello, Alice! Hello, Alice!

复杂类型注解

基本的 str, int, list 很好用,但如果想表达"一个由整数组成的列表"该怎么办?这时就需要 Python 的 typing 模块提供更强大的工具。

列表、字典等容器类型

1
2
3
4
5
6
7
8
9
10
11
12
13
from typing import List, Dict, Tuple, Set

# List[int] 表示这是一个只包含整数的列表
numbers: List[int] = [1, 2, 3, 4, 5]

# Dict[str, int] 表示这是一个键为字符串、值为整数的字典
student_scores: Dict[str, int] = {"Alice": 95, "Bob": 88}

# Tuple[int, str, bool] 表示这是一个包含整数、字符串、布尔值的元组
person_info: Tuple[int, str, bool] = (25, "Alice", True)

# Set[str] 表示这是一个只包含字符串的集合
unique_names: Set[str] = {"Alice", "Bob", "Charlie"}

可选类型(Optional)

当值可能是某种类型或者是 None 时使用:

1
2
3
4
5
6
7
8
from typing import Optional

def find_student(name: str) -> Optional[str]:
"""根据名字查找学生,可能找到也可能返回None"""
students = {"Alice": "A001", "Bob": "B002"}
return students.get(name) # 可能返回字符串或None

# 等价于 Union[str, None]

联合类型(Union)

当值可能是多种类型之一时使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from typing import Union

def process_input(data: Union[str, int, List[int]]) -> None:
"""处理可能是字符串、整数或整数列表的输入"""
if isinstance(data, str):
print(f"字符串: {data}")
elif isinstance(data, int):
print(f"整数: {data}")
elif isinstance(data, list):
print(f"列表: {data}")

process_input("hello") # 输出:字符串: hello
process_input(42) # 输出:整数: 42
process_input([1, 2, 3]) # 输出:列表: [1, 2, 3]

虚拟环境

虚拟环境是一个独立的 Python 运行空间,拥有自己的解释器、安装包和配置,与系统全局环境完全隔离。

Python 虚拟环境(Virtual Environment)是一个独立的 Python 运行环境,它允许你在同一台机器上为不同的项目创建隔离的 Python 环境。

不同项目可以在各自的虚拟环境中并行运行,互不干扰。

每个虚拟环境都有自己的:

  • Python 解释器
  • 安装的包/库
  • 环境变量 虚拟环境

虚拟环境让每个项目拥有独立的依赖空间,彻底消除版本冲突。

为什么需要虚拟环境:

  • 项目隔离:不同项目可使用不同版本的 Python 和第三方库
  • 避免污染:安装的包只影响当前环境,不污染全局 Python
  • 依赖可控:通过 requirements.txt 精确记录和复现环境
  • 安全测试:可以放心升级或试用新包,不影响其他项目

场景举例:

  • 项目 A 需要 Django 3.2 版本
  • 项目 B 需要 Django 4.0 版本
  • 如果在系统全局安装,两个版本会冲突

虚拟环境工具

工具名称 类型 Python版本支持 安装方式 特点 适用场景
venv(推荐) 内置模块 ≥ 3.3 无需安装,内置 轻量级、官方推荐、使用简单 通用开发、日常项目
virtualenv 第三方工具 2.x 和 3.x pip install virtualenv 功能丰富、兼容多版本 需要兼容旧版本或高级功能
conda Anaconda自带 2.x 和 3.x 随 Anaconda/Miniconda 安装 跨语言包管理、数据科学生态 数据科学、机器学习项目

可以根据需要使用对应的虚拟环境工具。

----- 未完待续 -----

参考:


1. Python 3 教程