变量类型

变量通常由字母、数字和下划线构成,不能以数字开头,区分大小写

1
2
# 交换变量值,不使用中间变量
x, y = y, x

整数长度不受限制;采用IEEE754的标准存储浮点数,有误差,可以用 decimal 模块;复数的实部和虚部是以浮点数的形式存放的,x.real x.imag

布尔值

逻辑运算符:and, or, not ,优先级为 not, and, or

逻辑运算符遵循短路逻辑,即从左往右,只有当第一个操作数的值无法确定逻辑运算的结果时,才对第二个操作数进行求值

1
2
3
4
5
3 and 4
4

3 or 4
3

运算

转义字符

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

转义字符 描述
(在行尾时) 续行符
\ 反斜杠符号
' 单引号
" 双引号
\a 响铃
\b 退格(Backspace)
\e 转义
\000
\n 换行
\v 纵向制表符
\t 横向制表符
\r 回车
\f 换页
\oyy 八进制数,y 代表 0~7 的字符,例如:\012 代表换行。
\xyy 十六进制数,以 \x 开头,yy代表的字符,例如:\x0a代表换行
\other 其的字符以普通格式输出
1
2
3
print("D:\\Downloads\\media")
# 原始字符串,加上 r 表示不启用转义字符
print(r"D:\Downloads\media")

运算优先级

优先级越

分支与循环

条件语句

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
if condition1:
statements
elif condition2:
statements
elif condition3:
statements
elif condition4:
statements
else:
statements

score = input("请输入你的分数:")
score = int(score)
if 0 <= score <60:
print("D")
elif 60 <= score < 80:
print("C")
elif 80 <= score < 90:
print("B")
elif 90 <= score < 100:
print("A")
elif score == 100:
print("S")
else:
print("错误,请重新输入")

嵌套:

1
2
3
4
5
6
if condition1:
if condition2:
statements
else:
else:
statements

条件表达式

1
2
# 两者取小
small = a if a < b else b

while

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
while condition:
statements

无限循环
while True:
statements

while condition1:
if condition2:
break
statements

while i < 10:
i += 1
if i % 2 == 0:
continue
print(i)
# 只打印奇数

break 跳出一层循环体

continue 跳出本次循环,回到循环体开头

当循环条件不再为真时,else 语句才会被执行。用 break 语句跳出循环是仍为真,else 语句不会执行

1
2
3
4
5
6
7
8
9
i = 1
while i < 5:
print("循环内,i的值是:", i)
if i == 2:
break
i += 1
else:
print("循环外,i的值是:", i)

for

1
2
3
4
5
6
7
8
9
10
for 变量 in 可迭代对象:
statements


for each in "Fishc":
print(each)

i < 0
for i < len("Fishc"):
print("Fishc[i]")

range(start, stop, step) ,[start, stop),step是步长

1
2
3
4
sum = 0
for i in range(100000):
sum += i
print(sum)
1
2
3
4
5
6
7
for n in range(2,100):
for i in range(2,n//2):
if n % i == 0:
print(n, "=", i, "*", n//i )
break
else:
print(n, "是一个素数")

列表

列表的数据项不需要具有相同的类型

1
2
3
list = ['physics', 'chemistry', 1997, 2000, 3.14, 3 + 4j, True]
for each in list:
print(each)

索引:正值表示正序,负值表示逆序

1
2
3
4
5
6
7
list = ['physics', 'chemistry', 1997, 2000, 3.14, 3 + 4j, True]
length = len(list)
i = 0
for i < length:
print("list[i]")
# 最后一项
print(list[-1])

切片

1
2
3
4
5
6
7
8
9
10
前三项
list[0:3]
list[:3]
第三到六项
list[3:6]

list[3:]
步长
list[::2]
list[::-2]

添加元素

1
2
3
list = ["Google", "Bing"]
list.append("Baidu")
list[len(list):]=[6,7,8,9]

extend() 的参数必须是一个可迭代对象,新的内容是追加到原列表最后一个元素后面

1
2
3
heros = ["钢铁侠", "绿巨人"]
heros.extend(["黑寡妇", "雷神", "灭霸"])
heros[len(heros):] = ["黑寡妇", "雷神", "灭霸"]

插入元素

1
2
3
4
s = [1, 3, 4, 5]
s.insert(1, 2) # 第一个参数是插入的位置,第二个参数是插入的元素
s.insert(0, 0)
s.insert(len(s), 6)

删除元素

1
2
3
4
5
6
7
8
9
10
11
heros = ["黑寡妇", "雷神", "绿巨人", "灭霸", "灭霸"]
# 1.如果列表中存在多个匹配的元素,只会删除第一个
# 2.指定删除的元素不存在会报错
heros.remove("灭霸")
heros.remove("123")

# 通过索引删除
heros.pop(0)

# 清空列表
heros.clear()

改变元素

1
2
3
heros = ["黑寡妇", "雷神", "绿巨人", "鹰眼", "灭霸"]
heros[4] = "钢铁侠"
heros[3:] = ["林冲", "宋江", "吴用"]

排序

1
2
3
4
5
nums = [3,1,5,7,8,3,5,9]
# 按大小排序,顺序
nums.sort()
# 按大小排序,逆序
num.sort(reverse=True)

1
2
3
4
5
6
7
8
nums = [3,1,5,7,8,3,5,9,5]
nums.count(5) # 列表中"5"的个数

# 查询元素的索引值,如果有多个,默认则返回第一个的索引值
index(x, start, end)
heros.index("绿巨人")
nums[nums.index(5)] = 10 # 不知道元素索引值的情况下修改元素
nums.index(5, 6, 7) # 返回最后一个5的索引值s

浅拷贝

1
2
3
4
5
6
7
8
9
10
# 错误做法,指向同一个列表
nums = [1, 2, 3]
y = nums
num[0] = 4
y
[4, 2, 3] # 列表y也发生了改变

# 正确做法:拷贝整个列表,而不是引用,适用于一维列表
nums_copy1 = nums.copy()
nums_copy2 = nums[:]

加法、乘法

1
2
3
4
5
6
7
8
9
10
11
12
13
s = [1,2,3]
s
[1, 2, 3]

t=[4,5,6]
t
[4, 5, 6]

s + t
[1, 2, 3, 4, 5, 6]

s * 3
[1, 2, 3, 1, 2, 3, 1, 2, 3]

列表推导式 [expression for target in iterable if condition]

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
oho = [1, 2, 3, 4, 5]
oho2 = [i * 2 for i in oho]
oho2
[2, 4, 6, 8, 10]

x = [i for i in range(10)]
x
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

y = [i + 1 for i in range(15)]
y
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

z = [s * 2 for s in "Meng"]
z
['MM', 'ee', 'nn', 'gg']

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
col2 = [row[1] for row in matrix]
col2
[2, 5, 8]
diag = [matrix[i][i] for i in range(len(matrix))]
diag
[1, 5, 9]
An_diag = [matrix[i][len(matrix) - i - 1] for i in range(len(matrix))]
An_diag
[3, 5, 7]

even = [i for i in range(10) if i % 2 == 0]
even
[0, 2, 4, 6, 8]

words = ["Great", "Good", "Fantistic", "Finger", "Excellent"]
fwords = [w for w in words if w[0] == "F"]
fwords
['Fantistic', 'Finger']

嵌套列表推导式

1
2
3
4
[expression for target1 in iterable1 if condition1
for target2 in iterable2 if condition2
for target3 in iterable3 if condition3
for targetN in iterableN if conditionN]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 降维
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flatten = [col for row in matrix for col in row]
flatten
[1, 2, 3, 4, 5, 6, 7, 8, 9]

A = [x + y for x in "BASKETBALL" for y in "basketball"]
A
['Bb', 'Ba', 'Bs', 'Bk', 'Be', 'Bt', 'Bb', 'Ba', 'Bl', 'Bl', 'Ab', 'Aa', 'As', 'Ak', 'Ae', 'At', 'Ab', 'Aa', 'Al', 'Al', 'Sb', 'Sa', 'Ss', 'Sk', 'Se', 'St', 'Sb', 'Sa', 'Sl', 'Sl', 'Kb', 'Ka', 'Ks', 'Kk', 'Ke', 'Kt', 'Kb', 'Ka', 'Kl', 'Kl', 'Eb', 'Ea', 'Es', 'Ek', 'Ee', 'Et', 'Eb', 'Ea', 'El', 'El', 'Tb', 'Ta', 'Ts', 'Tk', 'Te', 'Tt', 'Tb', 'Ta', 'Tl', 'Tl', 'Bb', 'Ba', 'Bs', 'Bk', 'Be', 'Bt', 'Bb', 'Ba', 'Bl', 'Bl', 'Ab', 'Aa', 'As', 'Ak', 'Ae', 'At', 'Ab', 'Aa', 'Al', 'Al', 'Lb', 'La', 'Ls', 'Lk', 'Le', 'Lt', 'Lb', 'La', 'Ll', 'Ll', 'Lb', 'La', 'Ls', 'Lk', 'Le', 'Lt', 'Lb', 'La', 'Ll', 'Ll']

B = [[x ,y] for x in range(10) if x % 2 == 0 for y in range(10) if y % 3 == 0]
B
[[0, 0], [0, 3], [0, 6], [0, 9], [2, 0], [2, 3], [2, 6], [2, 9], [4, 0], [4, 3], [4, 6], [4, 9], [6, 0], [6, 3], [6, 6], [6, 9], [8, 0], [8, 3], [8, 6], [8, 9]]

# 用循环语句
_ = []
for x in range(10):
if x % 2 == 0:
for y in range(10):
if y % 3 == 0:
_.append([x ,y])

嵌套列表

乘法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
for i in matrix:
for j in i:
print(j, end=" ")
print() # 换行
matrix[0][0]
matrix[1][2]

# 注意嵌套列表的乘法
# 试图对同一个列表进行拷贝,实际上拷贝的只是对同一个列表的引用
B = [[0] * 3] * 3
B[1][1] = 1
B
[[0, 1, 0], [0, 1, 0], [0, 1, 0]]

lists = [[]] * 3
lists[0].append(3)
lists
[[3], [3], [3]]
# 正确做法
for i in range(3):
A[i] = [0] * 3
# 列表推导式
A = [[0] * 3 for i in range(3)]

深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
x = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
y = x.copy()
x[1][1] = 0
y
[[1, 2, 3], [4, 0, 6], [7, 8, 9]] # 嵌套列表y也发生了改变

import copy
# 浅拷贝
y = copy.copy(x) # 拷贝引用

# 深拷贝
y = copy.deepcopy(x) # 拷贝整个嵌套列表

元组

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
rhyme = (1, 2, 3, 4, 5, "fish")
rhyme
(1, 2, 3, 4, 5, 'fish')

rhyme[0]
1

rhyme[-1]
'fish'

rhyme[:3]
(1, 2, 3)

rhyme[2:]
(3, 4, 5, 'fish')

rhyme[::2]
(1, 3, 5)

rhyme[::-1]
('fish', 5, 4, 3, 2, 1)

heros = ["蜘蛛侠", "绿巨人", "黑寡妇"]
heros.index("黑寡妇")
2

s = (1, 2, 3)
t = (4, 5, 6)
s + t
(1, 2, 3, 4, 5, 6)
s * 3
(1, 2, 3, 1, 2, 3, 1, 2, 3)

# 嵌套
w = (s, t)
w
((1, 2, 3), (4, 5, 6))

# 生成一个元素的元组
x = (520,) #加逗号

# 打包解包
t = (123, "fish", 3.14)
x, y, z = t # 注意变量的数量对应
x
123
y
'fish'
z
3.14

# 数量不对应的情况下加 *
a, b, *c = "fish"
a
'f'
b
'i'
c
['s', 'h']


# 不可以修改元组的元素
rhyme[1] = 10
Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
rhyme[1] = 10
TypeError: 'tuple' object does not support item assignment

# 如果元组中的元素指向一个可变的列表时,是可以修改列表中的内容的
s = [1, 2, 3]
t = [4, 5, 6]
w = (s, t)
w
([1, 2, 3], [4, 5, 6])
w[0][0] = 0
w
([0, 2, 3], [4, 5, 6])

字符串

在 Python 中,字符串是不可变的序列,可以使用多种内置函数和方法来操作字符串。以下是 Python 字符串的一些常用函数和方法的总结:

字符串创建

  • str(): 将其他类型转换为字符串。

字符串基本操作

  • len(s): 返回字符串 s 的长度。
  • s[index]: 访问字符串 s 中的第 index 个字符(从 0 开始)。

字符串方法

大小写

  • s.lower(): 将字符串转换为小写。
  • s.upper(): 将字符串转换为大写。
  • s.title(): 将字符串的每个单词的首字母转换为大写。
  • s.capitalize(): 将字符串的首字母转换为大写。
  • s.swapcase(): 将字符串中的大写字母转换为小写字母,反之亦然。
  • s.casefold(): 用于将字符串转换为小写,与 lower() 方法不同,casefold() 更加严格,特别是在处理某些语言的字符时
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    s = "I love Python"
    s.lower()
    'i love python'
    s.upper()
    'I LOVE PYTHON'
    s.title()
    'I Love Python'
    s.capitalize()
    'I love python'
    s.swapcase()
    'i LOVE pYTHON'
    s.casefold()
    'i love python'

左中右对齐

默认用空格填充

  • s.center(width, fillchar=''): 返回一个居中对齐的字符串,使用 fillchar 填充到指定的 width
  • s.ljust(width, fillchar=''): 返回一个左对齐的字符串,使用指定的填充字符填充到指定的宽度 width
  • s.rjust(width, fillchar=''): 返回一个右对齐的字符串,使用指定的填充字符填充到指定的宽度 width
  • s.zfill(width): 返回一个字符串,前面填充零 (0) 直到达到指定的宽度 width,并且如果字符串是负数,负号会在零的前面。一般用于数据报表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    s = "有内鬼,终止交易!"
    s
    '有内鬼,终止交易!'
    len(s)
    9
    s.center(15)
    ' 有内鬼,终止交易! '
    s.ljust(15)
    '有内鬼,终止交易! '
    s.rjust(15)
    ' 有内鬼,终止交易!'
    s.zfill(15)
    '000000有内鬼,终止交易!'

    s.center(15, "淦")

    '淦淦淦有内鬼,终止交易!淦淦淦'
    s.ljust(15, "淦")

    '有内鬼,终止交易!淦淦淦淦淦淦'
    s.rjust(15, "淦")

    '淦淦淦淦淦淦有内鬼,终止交易!'

查找

  • s.find(sub, start, end): 返回子串 sub 在字符串中的最低索引,未找到返回 -1
  • s.rfind(sub, start, end): 从右侧开始查找子串 sub 的最低索引,未找到会引发 ValueError
  • s.index(sub, start, end): 返回子串 sub 在字符串中的最低索引,未找到会引发 ValueError
  • s.rindex(sub, start, end): 从右侧开始查找子串 sub 的最低索引,未找到会引发 ValueError
  • s.count(sub, start, end): 计算子串 sub 在字符串中出现的次数。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    s = "上海自来水来自海上"
    s.count("海")
    2
    s.count("海", 0, 5)
    1
    s.find("海")
    1
    s.rfind("海")
    7
    s.index("海")
    1
    s.rindex("海")
    7
    s.find("贵")
    -1

替换

  • s.expandtabs(tabsize=8): 返回一个字符串,将字符串中的所有制表符 (\t) 替换为适当数量的空格,默认情况下每个制表符占 8 个空格,可以通过 tabsize 参数指定其他的空格数。

  • s.replace(old, new, count=-1): 返回一个新字符串,替换字符串 s 中的所有子字符串 oldnewcount 参数指定替换的最大次数,默认为 -1,表示替换所有出现的 old

  • s.translate(table): 返回一个新字符串,其中的字符根据给定的字符映射表 table 进行替换。该 table 通常是通过 str.maketrans() 创建的,用于定义要替换的字符及其对应的替换字符。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    code = """
    print("I love Python")
    print("I love Python")
    """
    newcode = code.expandtabs(8)
    print(newcode)

    print("I love Python")
    print("I love Python")


    s = "I love Python"
    s.replace("love", "hate")
    'I hate Python'

    s.translate(str.maketrans("OPQ", "123"))
    'I love 2ython'

判断

  • s.startswith(prefix, start, end): 检查字符串是否以 prefix 开头。
  • s.endswith(suffix, start, end): 检查字符串是否以 suffix 结尾。
  • s.islower(): 如果字符串中至少有一个字符且所有字符都是小写,则返回 True。
  • s.isupper(): 如果字符串中至少有一个字符且所有字符都是大写,则返回 True。
  • s.isalnum(): 如果字符串至少有一个字符并且所有字符都是字母或数字,则返回 True。
  • s.isalpha(): 如果字符串至少有一个字符并且所有字符都是字母,则返回 True。
  • s.isascii(): 如果字符串中的所有字符都是 ASCII 字符,则返回 True。
  • s.isdecimal(): 如果字符串只包含十进制字符(即数字字符),并且至少有一个字符,则返回 True。十进制字符包括 0-9。
  • s.isdigit(): 如果字符串只包含数字字符,则返回 True。
  • s.isnumeric(): 如果字符串只包含数字字符,则返回 True。
  • s.isdentifier(): 如果字符串是一个有效的标识符(符合 Python 的标识符规则),则返回 True。有效的标识符必须以字母或下划线开头,后续字符可以是字母、数字或下划线。
  • s.isprintable(): 如果字符串是可打印的,则返回 True。
  • s.isspace(): 如果字符串只包含空白字符,则返回 True。
  • s.istitle(): 如果字符串是标题格式(每个单词的首字母大写),则返回 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
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    s = "我爱Python"
    s.startswith("我")
    True
    s.startswith("他")
    False
    s.endswith("Python")
    True
    s. endswith("Py")
    False
    s.startswith("我", 1)
    False
    s.startswith("爱", 1)
    True
    s.endswith("Py", 0, 4)
    True
    x = "她爱Python"
    if x.startswith(("我", "他", "她")):
    print("总有人喜欢Pyhon")


    总有人喜欢Pyhon
    s = "I love Python"
    s.istitle()
    False
    s.isupper()
    False
    s.upper().isupper()
    True
    s.isalpha()
    False
    " \n".isspace()
    True
    "I love Python\n".isprintable()
    False

    s = "I love Python"
    s.istitle()
    False
    s.isupper()
    False
    s.upper().isupper()
    True
    s.isalpha()
    False
    " \n".isspace()
    True
    "I love Python\n".isprintable() # 转义字符不能打印
    False

    # 是否是python的保留标识符
    import keyword
    keyword.iskeyword("if")
    True
    keyword.iskeyword("while")
    True
    keyword.iskeyword("py")
    False

截取

  • s.strip(): 去除字符串两端的空白字符。
  • s.lstrip(): 去除字符串左端的空白字符。
  • s.rstrip(): 去除字符串右端的空白字符。
  • s.removeprefix(prefix): 返回一个新字符串,如果字符串 s 以指定的前缀 prefix 开头,则移除该前缀;否则返回原字符串。
  • s.removesuffix(prefix): 返回一个新字符串,如果字符串 s 以指定的后缀 suffix 结尾,则移除该后缀;否则返回原字符串。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    "      左侧不要留白".lstrip()
    '左侧不要留白'
    "右侧不要留白 ".rstrip()
    '右侧不要留白'
    " 两侧不要留白 ".strip()
    '两侧不要留白'
    "www.mengkr.asia".lstrip("wcom.")
    'engkr.asia'
    "www.mengkr.asia".rstrip("wcom.")
    'www.mengkr.asia'
    "www.mengkr.asia".strip("wcom.")
    'engkr.asia'
    "www.mengkr.asia".removeprefix("www.")
    'mengkr.asia'
    "www.mengkr.asia".removesuffix(".asia")
    'www.mengkr'

分割

  • s.split(sep=None, maxsplit=-1): 将字符串 s 按照指定的分隔符 sep 进行分割,返回一个列表。maxsplit 参数指定最大分割次数,默认为 -1,表示分割所有。
  • s.rsplit(sep=None, maxsplit=-1): 从右侧开始分割字符串 s,与 split() 方法类似,返回一个列表。
  • s.splitlines(keepends=False): 将字符串 s 按照分割(解决不同操作系统换行符不同的问题),返回一个列表。keepends 参数如果为 True,则保留换行符。
  • s.partition(sep): 将字符串 s 按照指定的分隔符 sep 分割为三部分:分隔符前的部分、分隔符本身和分隔符后的部分,返回一个包含这三部分的元组。
  • s.rpartition(sep): 从右侧开始分割字符串 s,与 partition() 方法类似,返回一个包含三部分的元组。
  • s.join(iterable): 将可迭代对象中的字符串连接成一个字符串。
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
"www.mengkr.asia".partition(".")
('www', '.', 'mengkr.asia')

"www.mengkr.asia/python".rpartition("/")
('www.mengkr.asia', '/', 'python')

"苟日新,日日新,又日新".split(",")
['苟日新,日日新,又日新']

"苟日新,日日新,又日新".split(",")
['苟日新', '日日新', '又日新']

"苟日新,日日新,又日新".rsplit(",")
['苟日新', '日日新', '又日新']

"苟日新,日日新,又日新".split(",", 1)
['苟日新', '日日新,又日新']

"苟日新,日日新,又日新".rsplit(",", 1)
['苟日新,日日新', '又日新']

"苟日新\r日日新\r\n又日新".splitlines()
['苟日新', '日日新', '又日新']

"苟日新\n日日新\r\n又日新".splitlines()
['苟日新', '日日新', '又日新']

"苟日新\r日日新\r\n又日新".splitlines(True)
['苟日新\r', '日日新\r\n', '又日新']

".".join(["www", "mengkr", "asia"])
'www.mengkr.asia'

"^".join(["www", "mengkr", "asia"])
'www^mengkr^asia'

# 用join连接字符串的效率比 + 高
s = "FishC"
s += s
s
'FishCFishC'

"".join(["FishC", "FishC"])
'FishCFishC'

格式化字符串

  • str.format(): 格式化字符串。
  • f"{}": 使用 f-string 进行格式化(Python 3.6+)。
  • s.format_map(mapping): 使用字典 mapping 格式化字符串。

基本格式化

  • 位置和关键字参数

    • {0}, {1}, …:根据位置引用参数。
  • {name}:根据关键字引用参数。

格式说明符

在大括号内,可以使用冒号 : 后跟格式说明符来控制输出格式:

  • 类型
    • d:整数(十进制)。
    • f:浮点数,默认精度为6。
    • **F**:表示浮点数的格式,类似于 f,默认精度为6,但在输出时总是使用科学计数法(例如,1.23E+02)。
    • s:字符串。
    • x:十六进制整数(小写)。
    • X:十六进制整数(大写)。
    • b:二进制整数。
    • o:八进制整数。
    • e:科学计数法(小写)。
    • E:科学计数法(大写)。
    • g:通用格式,根据值选择 fe 格式,小数以f形式输出,大数以e形式输出
    • **G**:通用格式,表示浮点数的格式,自动选择 F 或科学计数法(E)格式,具体取决于数值的大小和精度。例如,如果数值较大或较小,则会使用科学计数法;否则,使用普通的浮点数格式。
    • **%**:用于表示百分比。将数值乘以 100 并添加 % 符号。例如,{:.2%} 将格式化为百分比形式,并保留两位小数。
    • **None**:在格式化字符串中,None 本身不会有特定的格式说明符。如果你在格式化时传入 None,会被转换为字符串 'None'。例如,print("Value: {}".format(None)) 将输出 Value: None
  • 宽度
    • {:<10}:左对齐,宽度为 10。
    • {:>10}:右对齐,宽度为 10。
    • {:^10}:居中对齐,宽度为 10。
    • {:.2f}:浮点数保留两位小数。
  • 填充
    • {:0=10}:用0填充,宽度为 10。只对数字有效,感知正负号
    • {:_<10}:用下划线填充左对齐,宽度为 10。
    • {:.^10}:用点填充居中对齐,宽度为 10。
  • 千位分隔符
    • {:,}:使用逗号作为千位分隔符。
    • {:_}:使用下划线作为千位分隔符。

示例

以下是一些使用格式化选项的示例:

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
name = "Alice"
age = 30
height = 5.6789

# 基本格式化
"Name: {}".format(name)
'Name: Alice'

"Age: {}".format(age)
'Age: 30'

# 使用位置和关键字参数
"Name: {0}, Age: {1}".format(name, age)
'Name: Alice, Age: 30'

"Name: {name}, Age: {age}".format(name=name, age=age)
'Name: Alice, Age: 30'

# 保留{}
"{}, {}, {}".format(1, "{}", 2)
'1, {}, 2'

"{}, {{}}, {}".format(1, 2)
'1, {}, 2'

# 正负号
"{:+} {:-}".format(520, -250)
'+520 -250'

# 对齐,宽度
"Age: {:>5}".format(age) # 右对齐
'Age: 30'

"{1:>10}{0:<10}".format(520, 250) # 520左对齐,250右对齐
' 250520 '

"{:0=10}".format(520) # 填充0
'0000000520'

"{:0=10}".format(-520) # 感知正负号
'-000000520'

# 保留
"Height: {:.2f}".format(height) # 保留两位小数
'Height: 5.68'

"{:.2g}".format(3.1415)
'3.1'

"{:.6}".format("I love Pyhthon")
'I love'

# 千位分隔符
"{:,}".format(1234)
'1,234'

"{:_}".format(1234)
'1_234'

"{:,}".format(123)
'123' # 位数不够不显示分隔符

"Formatted number: {:,}".format(1000000)
'Formatted number: 1,000,000'

# 进制转化,进#号显示前缀
"{:b}".format(80)
'1010000'

"{:#b}".format(80)
'0b1010000'

"{:#d}".format(80)
'80'

"{:#o}".format(80)
'0o120

"{:#x}".format(80)
'0x50'

# 科学计数法
"{:e}".format(3.1415)
'3.141500e+00'

"{:E}".format(3.1415)
'3.141500E+00'

# 通过关键字设置
"{:.{prec}f}".format(3.1415, prec=2)
'3.14'

"{:{fill}{align}{width}.{prec}{ty}}".format(3.1415, fill='+', align='^', width=10, prec=3, ty='g')
'+++3.14+++'

# 其他
"{:f}".format(3.1415)

"{:g}".format(1234.56789)
'1234.57'

"{:g}".format(123456789)
'1.23457e+08'

"{:%}".format(0.98)
'98.000000%'

"{:.2%}".format(0.98)
'98.00%'

f-字符串(python3.6以上)

可以看作 format 方法的语法糖

1
2
3
4
5
6
7
8
9
10
11
f"1+2={1+2},2的平方是{2*2}"
'1+2=3,2的平方是4'

f"{-520:010}"
'-000000520'

f"{123456789:,}"
'123,456,789'

f"{3.1415:.2f}"
'3.14'

字符串编码和解码

  • s.encode(encoding): 将字符串编码为字节。
  • b.decode(encoding): 将字节解码为字符串。

序列

列表、元组和字符串都为序列。列表为可变序列,元组和字符串则是不可变序列。

迭代器与迭代对象

一个迭代器肯定是一个迭代对象,可迭代对象可以重复使用而迭代器是一次性的

可迭代对象 vs 迭代器

概念 要求 示例
可迭代对象 实现 __iter__ list, tuple, dict
迭代器 实现 __iter____next__ file对象, generator
  • 可迭代对象:可以被迭代的对象(可以不是迭代器本身)
  • 迭代器:实际执行迭代的对象(必须也是可迭代对象)
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
mapped = map(ord, "FishC") # map返回一个迭代器
for each in mapped:
print(each)
70
105
115
104
67

list(mapped)
[] # 说明只能使用一次

# 可迭代对象变成迭代器
x = [1, 2, 3, 4, 5]
y = iter(x) # 变成迭代器
type(x)
<class 'list'>
type(y)
<class 'list_iterator'>
# next()逐个将迭代器中的元素提取出来
next(y, "已经掏空了")
1
next(y, "已经掏空了")
2
next(y, "已经掏空了")
3
next(y, "已经掏空了")
4
next(y, "已经掏空了")
5
next(y, "已经掏空了")
'已经掏空了'

方法

id

id() 函数用于返回对象的唯一标识符(ID)。这个标识符是一个整数,代表对象在内存中的地址。具体来说,id() 函数的返回值具有以下特点:

  1. 唯一性:对于一个对象,id() 返回的值在其生命周期内是唯一的。也就是说,在对象存在的期间,id() 返回的值不会改变。
  2. 内存地址id() 返回的整数值通常是对象在内存中的地址(在 CPython 实现中),但这个值并不一定是直接可用的内存地址。
  3. 对象的生命周期:当对象被销毁后,id() 返回的值可能会被其他对象重新使用。

isis not 是用于比较对象身份的运算符。用于判断两个对象是否是同一个对象(即内存地址是否相同)。下面是详细说明和使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
s = [1, 2, 3]
id(s)
2647838351424
s *= 2
id(s)
2647838351424 # 不变

t = (1, 2, 3)
id(t)
2647838478144
t *= 2
id(t)
2647833158912 # 改变了

is 运算符

  • 作用:判断两个对象是否是同一个对象。换句话说,检查两个变量是否引用同一个内存地址。
  • 返回值:如果两个对象是同一个对象,返回 True;否则返回 False

is not 运算符

  • 作用:判断两个对象是否不是同一个对象。是 is 的否定形式。
  • 返回值:如果两个对象不是同一个对象,返回 True;否则返回 False

innot in 是用于检查某个元素是否存在于可迭代对象(如列表、元组、字符串、字典等)中的运算符。具体用法和功能如下:

1
2
3
4
5
6
7
8
9
x = "Meng"
y = "Meng"
x is y
True # 说明同一地址

x = [1, 2, 3]
y = [1, 2, 3]
x is y
False

in 运算符

  • 作用:检查一个元素是否存在于指定的可迭代对象中。
  • 返回值:如果元素存在,返回 True;否则返回 False

not in 运算符

  • 作用:检查一个元素是否不存在于指定的可迭代对象中。
  • 返回值:如果元素不存在,返回 True;否则返回 False
1
2
3
4
5
"M" in "Meng"
True

"M" not in "Meng"
False

del 的用法

  1. 删除变量:可以使用 del 删除一个变量,使其不再引用任何对象。
  2. 删除列表中的元素:可以使用 del 删除列表中的特定元素或切片。
  3. 删除字典中的键:可以使用 del 删除字典中的特定键及其对应的值。
  4. 删除对象的属性:可以使用 del 删除对象的某个属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 删除变量
x = "Well"
y = [1, 2, 3]
del x, y

# 删除元素
x = [1, 2, 3, 4, 5]
del x[1:4]
[1, 5]

y = [1, 2, 3, 4, 5] # 利用切片删除
y[1:4] = []
[1, 5]

x = [1, 2, 3, 4, 5]
del x[::2] # 这里用x[::2] = [] 会报错
[2, 4]

# 清空列表
x.clear()
del x[::]

min() 函数

  • 作用:返回可迭代对象中的最小值。

  • 语法

    1
    min(iterable, *[, key, default])
    • iterable:要查找最小值的可迭代对象。
    • key:可选参数,一个函数,用于从每个元素中提取比较值。
    • default:可选参数,当可迭代对象为空时返回的默认值。

max() 函数

  • 作用:返回可迭代对象中的最大值。

  • 语法:

    1
    max(iterable, *[, key, default])
    • iterable:要查找最大值的可迭代对象。
    • key:可选参数,一个函数,用于从每个元素中提取比较值。
    • default:可选参数,当可迭代对象为空时返回的默认值。
1
2
s = []
min(s, default="列表没有内容")

len() 函数

  • 作用:返回对象(如字符串、列表、元组、字典等)的长度或元素个数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 计算字符串的长度
my_string = "Hello, world!"
string_length = len(my_string)
print("字符串长度:", string_length) # 输出: 字符串长度: 13

# 计算列表的长度
my_list = [1, 2, 3, 4, 5]
list_length = len(my_list)
print("列表长度:", list_length) # 输出: 列表长度: 5

# 计算字典的长度(键的数量)
my_dict = {'name': 'Alice', 'age': 30, 'city': 'New York'}
dict_length = len(my_dict)
print("字典长度:", dict_length) # 输出: 字典长度: 3

# 计算元组的长度
my_tuple = (1, 2, 3)
tuple_length = len(my_tuple)
print("元组长度:", tuple_length) # 输出: 元组长度: 3

sum() 函数

  • 作用:计算可迭代对象中所有元素的总和。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sum(iterable, start=0)
# iterable:要计算总和的可迭代对象(如列表、元组等)。
# start:可选参数,指定起始值,默认为 0。

# 计算列表中所有数字的总和
numbers = [1, 2, 3, 4, 5]
total_sum = sum(numbers)
print("总和:", total_sum) # 输出: 总和: 15

# 计算元组中所有数字的总和
numbers_tuple = (10, 20, 30)
total_sum_tuple = sum(numbers_tuple)
print("元组的总和:", total_sum_tuple) # 输出: 元组的总和: 60

# 使用 start 参数
total_sum_with_start = sum(numbers, 10) # 从 10 开始累加
print("总和(从 10 开始):", total_sum_with_start) # 输出: 总和(从 10 开始): 25

sorted() 函数

  • 作用:返回一个新的排序列表,而不会修改原始可迭代对象。

  • 语法:

    1
    sorted(iterable, *, key=None, reverse=False)
    • iterable:要排序的可迭代对象(如列表、元组、字符串等)。
    • key:可选参数,用于指定一个函数,从每个元素中提取用于比较的值。
    • reverse:可选参数,如果设置为 True,则以降序排序;默认为 False(升序)。
1
2
3
4
# 使用 key 参数进行排序
words = ['apple', 'banana', 'cherry', 'date']
sorted_by_length = sorted(words, key=len) # 根据单词长度排序,相同长度按字母排序
print("按长度排序:", sorted_by_length) # 输出: 按长度排序: ['date', 'apple', 'banana', 'cherry']

reversed() 函数

  • 作用:返回一个反转的迭代器,而不会修改原始可迭代对象。

  • 语法

    1
    reversed(seq)
    • seq:要反转的可迭代对象(如列表、元组、字符串等)。
1
2
3
4
5
6
7
8
9
10
11
# 反转列表
s = [1, 2, 5, 8, 0]
reversed(s)
<list_reverseiterator object at 0x000002687EC8B460>
list(reversed(s)) # 转化成列表
[0, 8, 5, 2, 1]

# 反转字符串
my_string = "Hello"
reversed_string = ''.join(reversed(my_string)) # 转换为字符串
print("反转后的字符串:", reversed_string) # 输出: 反转后的字符串: olleH

all() 函数

  • 作用:如果可迭代对象中的所有元素都为真(即非零或非空),则返回 True;否则返回 False

  • 语法

    1
    all(iterable)
    • iterable:要检查的可迭代对象(如列表、元组、集合等)。

any() 函数

  • 作用:如果可迭代对象中至少有一个元素为真,则返回 True;如果所有元素都为假,则返回 False

  • 语法

    1
    any(iterable)
    • iterable:要检查的可迭代对象(如列表、元组、集合等)。
1
2
3
4
5
6
7
8
9
10
x = [1, 1, 0]
all(x)
False
y = [1, 1, 9]
all(x)
True
any(x)
True
any(x)
True

enumerate() 函数

  • 作用:将可迭代对象(如列表、元组等)转换为一个枚举对象,返回每个元素的索引及其对应的值。

  • 语法

    1
    enumerate(iterable, start=0)
    • iterable:要枚举的可迭代对象。
    • start:可选参数,指定索引的起始值,默认为 0。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 示例 1:基本用法
fruits = ['apple', 'banana', 'cherry']
for index, fruit in enumerate(fruits):
print(index, fruit)
# 输出:
# 0 apple
# 1 banana
# 2 cherry

# 示例 2:指定起始索引
for index, fruit in enumerate(fruits, start=1):
print(index, fruit)
# 输出:
# 1 apple
# 2 banana
# 3 cherry

# 示例 3:将 enumerate 转换为列表
enumerated_fruits = list(enumerate(fruits))
print("枚举后的列表:", enumerated_fruits) # 输出: 枚举后的列表: [(0, 'apple'), (1, 'banana'), (2, 'cherry')]

zip() 函数

  • 作用:将多个可迭代对象(如列表、元组等)“打包”成一个元组的列表。每个元组包含来自所有可迭代对象的对应元素。

  • 语法

    1
    zip(*iterables)
    • iterables:要打包的可迭代对象,可以是两个或多个。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 示例 1:基本用法
names = ['Alice', 'Bob', 'Charlie']
scores = [85, 90, 95]

zipped = zip(names, scores)
for name, score in zipped:
print(name, score)
# 输出:
# Alice 85
# Bob 90
# Charlie 95

# 示例 2:将 zip 转换为列表
zipped_list = list(zip(names, scores))
print("打包后的列表:", zipped_list)
# 输出: 打包后的列表: [('Alice', 85), ('Bob', 90), ('Charlie', 95)]

# 示例 3:处理不同长度的可迭代对象
# 如果传入的可迭代对象长度不一致,以最短的那个为准
names = ['Alice', 'Bob']
scores = [85, 90, 95] # 比 names 多一个元素
zipped_diff_length = list(zip(names, scores))
print("不同长度的打包结果:", zipped_diff_length)
# 输出: 不同长度的打包结果: [('Alice', 85), ('Bob', 90)]

map() 函数

  • 作用:将指定函数应用于可迭代对象的每个元素,返回一个迭代器(在 Python 3 中)。

  • 语法

    1
    map(function, iterable, ...)
    • function:要应用的函数,可以是内置函数或用户自定义函数。
    • iterable:一个或多个可迭代对象(如列表、元组等)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# ord 求Unicode编码
mapped = map(ord, "FishC")
list(mapped)
[70, 105, 115, 104, 67]

list(map(max, [1, 3, 5], [2, 3, 8], [0, 3, 10, 9]))
[2, 3, 10]

# 示例 2:使用 lambda 函数
numbers = [1, 2, 3, 4, 5]
squared_numbers = map(lambda x: x * x, numbers)
squared_list = list(squared_numbers)
print("平方后的列表:", squared_list) # 输出: 平方后的列表: [1, 4, 9, 16, 25]

# 示例 3:对多个列表中的元素进行操作
numbers1 = [1, 2, 3]
numbers2 = [4, 5, 6]
# 将两个列表中的元素相加
summed_numbers = map(lambda x, y: x + y, numbers1, numbers2)
summed_list = list(summed_numbers)
print("相加后的列表:", summed_list) # 输出: 相加后的列表: [5, 7, 9]

filter() 函数

  • 作用:从可迭代对象中筛选出符合条件的元素,返回一个迭代器(在 Python 3 中)。

  • 语法

    1
    filter(function, iterable)
    • function:用于测试每个元素的函数。该函数应该返回布尔值(TrueFalse)。
    • iterable:要过滤的可迭代对象(如列表、元组等)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 示例 1:过滤出列表中的偶数
def is_even(n):
return n % 2 == 0
numbers = [1, 2, 3, 4, 5, 6]
even_numbers = filter(is_even, numbers)
# 将 filter 对象转换为列表以查看结果
even_numbers_list = list(even_numbers)
print("偶数列表:", even_numbers_list)
# 输出: 偶数列表: [2, 4, 6]

# 示例 2:使用 lambda 函数过滤偶数
numbers = [1, 2, 3, 4, 5, 6]
even_numbers = filter(lambda x: x % 2 == 0, numbers)
even_numbers_list = list(even_numbers)
print("偶数列表:", even_numbers_list)
# 输出: 偶数列表: [2, 4, 6]

# 示例 3:过滤非空字符串
strings = ["apple", "", "banana", None, "cherry", " "]
# 过滤掉空字符串和 None
filtered_strings = filter(lambda s: s and s.strip(), strings)
filtered_strings_list = list(filtered_strings)
print("非空字符串列表:", filtered_strings_list)
# 输出: 非空字符串列表: ['apple', 'banana', 'cherry', ' ']s

转换

列表与元组之间的转换

  • 列表转换为元组:使用 tuple() 函数。
  • 元组转换为列表:使用 list() 函数。

示例

1
2
3
4
5
6
7
8
9
# 列表转换为元组
my_list = [1, 2, 3, 4, 5]
my_tuple = tuple(my_list)
print(my_tuple) # 输出: (1, 2, 3, 4, 5)

# 元组转换为列表
my_tuple = (1, 2, 3, 4, 5)
my_list = list(my_tuple)
print(my_list) # 输出: [1, 2, 3, 4, 5]

列表与字符串之间的转换

  • 列表转换为字符串:使用 join() 方法。
  • 字符串转换为列表:使用 split() 方法。

示例

1
2
3
4
5
6
7
8
9
# 列表转换为字符串
my_list = ['Hello', 'world', 'Python']
my_string = ' '.join(my_list) # 使用空格连接
print(my_string) # 输出: Hello world Python

# 字符串转换为列表
my_string = "Hello world Python"
my_list = my_string.split() # 默认以空格分隔
print(my_list) # 输出: ['Hello', 'world', 'Python']

元组与字符串之间的转换

  • 元组转换为字符串:使用 join() 方法(需要先将元组转换为列表或直接使用字符串)。
  • 字符串转换为元组:使用 tuple() 函数。

示例

1
2
3
4
5
6
7
8
9
# 元组转换为字符串
my_tuple = ('H', 'e', 'l', 'l', 'o')
my_string = ''.join(my_tuple) # 连接元组中的字符
print(my_string) # 输出: Hello

# 字符串转换为元组
my_string = "Hello"
my_tuple = tuple(my_string) # 将字符串转换为元组,每个字符为一个元素
print(my_tuple) # 输出: ('H', 'e', 'l', 'l', 'o')

可哈希性

可哈希性(Hashable)是Python中一个重要的概念,决定了对象能否作为字典的键集合的元素

可哈希对象的特点:

  1. 不可变性:大多数Python的不可变对象都是可哈希的
    • 数字类型:int, float, decimal.Decimal, fractions.Fraction
    • 字符串:str
    • 元组:tuple(只有当包含的所有元素都是可哈希的)
    • 冻结集合:frozenset
  2. 自定义类:默认情况下,用户定义的类的实例是可哈希的
    • 哈希值基于对象ID
    • 如果定义了__eq__()方法,默认的__hash__()会被设置为None,除非显式定义

不可哈希对象:

  • 可变容器:list, dict, set
  • 包含可变元素的元组
  • 自定义类(如果定义了__eq__()但没有定义__hash__()

检查对象是否可哈希:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from collections.abc import Hashable

def check_hashability(obj):
if isinstance(obj, Hashable):
try:
hash(obj)
return True
except TypeError:
return False
return False

print(check_hashability(42)) # True
print(check_hashability("hello")) # True
print(check_hashability([1, 2])) # False

字典

字典是python中唯一实现映射关系的内置类型

创建字典

1
2
3
4
5
6
7
# 几种生成字典的方式
a = {"刘备":"左将军", "关羽":"汉寿亭侯", "吕布":"平东将军"}
b = dict(刘备="左将军", 关羽="汉寿亭侯", 吕布="平东将军")
c = dict([("刘备", "左将军"), ("关羽", "汉寿亭侯"), ("吕布", "平东将军")])
d = dict(zip(["刘备", "关羽", "吕布"], ["左将军", "汉寿亭侯", "平东将军"]))
a == b == c == d
True

查/访问

1
2
3
4
5
6
value = my_dict['name']  # 如果键不存在会抛出KeyError
value = my_dict.get('name') # 安全获取,键不存在返回None
value = my_dict.get('name', 'default') # 键不存在返回'default'

# 键存在则返回值,不存在则设置键值对,并返回默认值
my_dict.setdefault('key', 'default_value')

添加/修改元素

1
2
my_dict['new_key'] = 'new_value'  # 添加或修改
my_dict.update({'key1': 'val1', 'key2': 'val2'}) # 批量更新

删除元素

1
2
3
4
5
del my_dict['key']  # 删除指定键,键不存在会抛出KeyError
value = my_dict.pop('key') # 删除并返回对应的值,键不存在会抛出KeyError
value = my_dict.pop('key', 'default') # 键不存在返回'default'
my_dict.popitem() # 删除并返回最后一个键值对(Python 3.7+有序)
my_dict.clear() # 清空字典

拷贝

1
new_dict = my_dict.copy()  # 浅拷贝

映射到相同的值

1
2
3
4
5
6
7
# 返回一个新的字典,其中包含来自可迭代对象的键,所有键都映射到相同的值
dict.fromkeys(iterable[, value])

keys = ['name', 'age', 'gender']
new_dict = dict.fromkeys(keys, 'unknown')
print(new_dict)
# 输出: {'name': 'unknown', 'age': 'unknown', 'gender': 'unknown'}

字典视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
keys = my_dict.keys()  # 获取所有键的视图
values = my_dict.values() # 获取所有值的视图
items = my_dict.items() # 获取所有键值对的视图

my_dict = {"刘备":"左将军", "关羽":"汉寿亭侯", "吕布":"平东将军"}
keys = my_dict.keys()
values = my_dict.values()
items = my_dict.items()
keys
dict_keys(['刘备', '关羽', '吕布'])
values
dict_values(['左将军', '汉寿亭侯', '平东将军'])
items
dict_items([('刘备', '左将军'), ('关羽', '汉寿亭侯'), ('吕布', '平东将军')])
my_dict.pop("吕布")
'平东将军'
keys
dict_keys(['刘备', '关羽'])
values
dict_values(['左将军', '汉寿亭侯'])
items
dict_items([('刘备', '左将军'), ('关羽', '汉寿亭侯')])

嵌套

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
# 直接创建嵌套字典
person = {
'name': 'Alice',
'age': 30,
'address': {
'street': '123 Main St',
'city': 'New York',
'zip': '10001'
},
'contacts': {
'email': 'alice@example.com',
'phone': '555-1234'
}
}

# 动态创建嵌套字典
company = {}
company['name'] = 'Tech Corp'
company['departments'] = {}
company['departments']['engineering'] = {'employees': 50, 'manager': 'Bob'}
company['departments']['marketing'] = {'employees': 20, 'manager': 'Alice'}

# 访问嵌套字典
print(person['name']) # 输出: Alice
print(person['address']['city']) # 输出: New York
print(company['departments']['engineering']['manager']) # 输出: Bob
# 使用get方法安全访问
city = person.get('address', {}).get('city', 'Unknown')
print(city) # 输出: New York

# 和列表嵌套
d = {"小明":[60, 70, 80], "小红":[90, 100, 110]}
d["小明"][1]
70

字典推导式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{key_expression: value_expression for item in iterable}
{key_expression: value_expression for item in iterable if condition} # 带有条件判断

numbers = [1, 2, 3, 4]
squared_dict = {x: x**2 for x in numbers}
# 结果: {1: 1, 2: 4, 3: 9, 4: 16}

numbers = [1, 2, 3, 4, 5]
even_squares = {x: x**2 for x in numbers if x % 2 == 0}
# 结果: {2: 4, 4: 16}

word = "hello"
letter_counts = {letter: word.count(letter) for letter in word}
# 结果: {'h': 1, 'e': 1, 'l': 2, 'o': 1}

original = {'a': 1, 'b': 2, 'c': 3}
inverted = {v: k for k, v in original.items()}
# 结果: {1: 'a', 2: 'b', 3: 'c'}

matrix = [[1, 2], [3, 4], [5, 6]]
flattened = {i+1: row[i] for i in range(2) for row in matrix}
# 结果: {1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6}

错误示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{x:y for x in [1, 3, 5] for y in [2, 4, 6]}
{1: 6, 3: 6, 5: 6}

这是因为字典推导式中的嵌套循环工作原理:
外层循环 for x in [1, 3, 5] 会依次取 1, 3, 5
对于每个x值,内层循环 for y in [2, 4, 6] 会完整地遍历一遍 [2, 4, 6]
字典的键是唯一的,所以每个x只会保留最后一个y值

实际执行顺序:
x=1 → y=2 → {1:2}
x=1 → y=4 → {1:4} (覆盖前一个)
x=1 → y=6 → {1:6} (覆盖前一个)
x=3 → y=2 → {1:6, 3:2}
x=3 → y=4 → {1:6, 3:4}
x=3 → y=6 → {1:6, 3:6}
x=5 → y=2 → {1:6, 3:6, 5:2}
x=5 → y=4 → {1:6, 3:6, 5:4}
x=5 → y=6 → {1:6, 3:6, 5:6}

# 正确做法
{x:y for x, y in zip([1, 3, 5], [2, 4, 6])}

集合

  1. 无序性:集合中的元素没有固定顺序
  2. 唯一性:集合中的元素都是唯一的(自动去重)
  3. 可变性:集合本身是可变的,可以添加或删除元素
  4. 元素限制:集合元素必须是不可变类型(数字、字符串、元组等),不能包含列表、字典等可变对象

创建集合

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
fruits = {'apple', 'banana', 'orange'}
print(fruits) # 输出可能是: {'banana', 'apple', 'orange'}(顺序不固定)


# 使用 `set()` 构造函数
numbers = set([1, 2, 3, 2, 1]) # 从列表创建,自动去重
print(numbers) # 输出: {1, 2, 3}

letters = set("hello") # 从字符串创建
print(letters) # 输出: {'h', 'e', 'l', 'o'}

# 创建空集合
empty_set = set() # 正确方式
# empty_set = {} # 错误!这会创建一个空字典而不是集合



# 不可变集合
# 不可变性:创建后不能添加、删除或修改元素
# 可哈希性:可以作为字典的键或集合的元素
# 支持所有集合运算(但不包括修改操作)

# 从可迭代对象创建
fs1 = frozenset([1, 2, 3, 2, 1])
print(fs1) # 输出: frozenset({1, 2, 3})
# 从字符串创建
fs2 = frozenset("hello")
print(fs2) # 输出: frozenset({'h', 'e', 'l', 'o'})
# 空 frozenset
empty_fs = frozenset()

添加元素

1
2
3
4
5
6
7
8
9
fruits = {'apple', 'banana'}
fruits.add('orange') # 添加单个元素
fruits.update(['kiwi', 'mango']) # 添加多个元素
print(fruits) # 输出类似: {'apple', 'banana', 'orange', 'kiwi', 'mango'}

s = set()
s.add("23") # 整个元素加入到集合
s.update("45") # 迭代获取每个元素加入集合
# 结果 {'23', '5', '4'}

删除元素

1
2
3
4
5
numbers = {1, 2, 3, 4, 5}
numbers.remove(3) # 移除元素,不存在则引发KeyError
numbers.discard(10) # 移除元素,不存在也不报错
popped = numbers.pop() # 随机移除并返回一个元素
numbers.clear() # 清空集合

基本集合运算

并集 (Union)

1
2
3
4
5
6
7
8
9
10
11
12
a = {1, 2, 3}
b = {3, 4, 5}

# 方法1: 使用 | 运算符
print(a | b) # 输出: {1, 2, 3, 4, 5}

# 方法2: 使用 union() 方法
print(a.union(b)) # 输出: {1, 2, 3, 4, 5}

# 合并多个集合
c = {5, 6, 7}
print(a | b | c) # 输出: {1, 2, 3, 4, 5, 6, 7}

交集 (Intersection)

1
2
3
4
5
6
7
8
# 方法1: 使用 & 运算符
print(a & b) # 输出: {3}

# 方法2: 使用 intersection() 方法
print(a.intersection(b)) # 输出: {3}

# 多个集合的交集
print(a & b & {3, 6}) # 输出: {3}

差集 (Difference)

1
2
3
4
5
6
7
8
# 方法1: 使用 - 运算符
print(a - b) # 输出: {1, 2} (在a中但不在b中)

# 方法2: 使用 difference() 方法
print(a.difference(b)) # 输出: {1, 2}

# 多个集合的差集
print(a - b - {1}) # 输出: {2}

对称差集 (Symmetric Difference)

1
2
3
4
5
# 方法1: 使用 ^ 运算符
print(a ^ b) # 输出: {1, 2, 4, 5} (仅在a或仅在b中的元素)

# 方法2: 使用 symmetric_difference() 方法
print(a.symmetric_difference(b)) # 输出: {1, 2, 4, 5}

集合比较运算

子集检查

1
2
3
4
5
6
7
8
9
10
11
12
x = {1, 2}
y = {1, 2, 3}

# 方法1: 使用 <= 运算符
print(x <= y) # 输出: True (x是y的子集)

# 方法2: 使用 issubset() 方法
print(x.issubset(y)) # 输出: True

# 真子集检查 (x是y的子集且x != y)
print(x < y) # 输出: True
print(x < x) # 输出: False

超集检查

1
2
3
4
5
6
7
8
# 方法1: 使用 >= 运算符
print(y >= x) # 输出: True (y是x的超集)

# 方法2: 使用 issuperset() 方法
print(y.issuperset(x)) # 输出: True

# 真超集检查
print(y > x) # 输出: True

不相交检查

1
2
3
4
5
set1 = {1, 2}
set2 = {3, 4}

print(set1.isdisjoint(set2)) # 输出: True (没有共同元素)
print(set1 & set2 == set()) # 等效检查方式

更新集合的运算

更新并集

1
2
3
a = {1, 2}
a.update({3, 4}) # 等同于 a |= {3, 4}
print(a) # 输出: {1, 2, 3, 4}

更新交集

1
2
3
a = {1, 2, 3}
a.intersection_update({2, 3, 4}) # 等同于 a &= {2, 3, 4}
print(a) # 输出: {2, 3}

更新差集

1
2
3
a = {1, 2, 3}
a.difference_update({2, 4}) # 等同于 a -= {2, 4}
print(a) # 输出: {1, 3}

更新对称差集

1
2
3
a = {1, 2, 3}
a.symmetric_difference_update({2, 3, 4}) # 等同于 a ^= {2, 3, 4}
print(a) # 输出: {1, 4}

函数

定义/调用/返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def function_name(parameters):
"""docstring"""
# 函数体
return [expression] # 返回值

def div(x, y):
if y == 0:
return "除数不能为零!"
return x / y
div(4, 2)
# 输出: 2.0
div(4, 0)
# 输出: 除数不能为零!

def myfunc():
pass
print(myfunc())
# 输出: None
# 说明函数没有返回值的情况下,默认返回None

参数

位置参数

1
2
3
4
def power(base, exponent):
return base ** exponent

print(power(2, 3)) # 输出: 8

关键字参数

1
print(power(exponent=3, base=2))  # 输出: 8

限制参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def abc(a, /, b, c):
print(a, b, c)

abc(a=1, 2, 3) # '/'左侧的参数只能是位置参数
# 输出: SyntaxError: positional argument follows keyword argument
abc(1, 2, 3)
# 输出: 1 2 3

def abc(a, *, b, c):
print(a, b, c)

abc(1, 2, 3) # '*'右侧的只能是关键字参数
Traceback (most recent call last):
File "<pyshell#5>", line 1, in <module>
abc(1, 2, 3)
TypeError: abc() takes 1 positional argument but 3 were given

abc(1, b=2, c=3)
# 输出: 1 2 3

默认参数

1
2
3
4
5
6
# 默认参数要放在其他参数后边
def power(base, exponent=2):
return base ** exponent

print(power(3)) # 输出: 9
print(power(3, 3)) # 输出: 27

可变长度参数

  • *args - 接收任意数量的位置参数(元组)
  • **kwargs - 接收任意数量的关键字参数(字典)
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
# 打包
def print_info(*args, **kwargs):
print(args)
print(kwargs)
for arg in args:
print(arg)
for key, value in kwargs.items():
print(f"{key}: {value}")

print_info(1, 2, 3, name="Alice", age=25)
(1, 2, 3) # 是元组
{'name': 'Alice', 'age': 25} # 是字典
1
2
3
name: Alice
age: 25


def myfunc(a, *b, **c):
print(a, b, c)

myfunc(1, 2, 3, 4, x=5, y=6)
# 输出: 1 (2, 3, 4) {'x': 5, 'y': 6}

# 解包
def myfunc(a, b, c, d):
print(a, b, c, d)

args = (1, 2, 3, 4)
myfunc(*args) # '*'解包
# 输出:1 2 3 4

kwargs = {'a':1, 'b':2, 'c':3, 'd':4}
myfunc(**kwargs)
# 输出:1 2 3 4

变量作用域

  • 局部变量:函数内部定义的变量
  • 全局变量:函数外部定义的变量
  • 使用global关键字在函数内修改全局变量,不推荐这种做法
1
2
3
4
5
6
count = 0  # 全局变量

def increment():
global count
count += 1
local_var = 10 # 局部变量

嵌套

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
def funcA():
x = 520
def funcB():
x = 888
print("In funcB, x =", x)
funcB()
print("In funcA, x =", x)

funcB() # 不能直接调用funcB()
Traceback (most recent call last):
File "<pyshell#19>", line 1, in <module>
funcB()
NameError: name 'funcB' is not defined. Did you mean: 'funA'?

funcA()
In funcB, x = 888
In funcA, x = 520

def funcA():
x = 520
def funcB():
nonlocal x # 修改嵌套作用域变量
x = 888
print("In funcB, x =", x)
funcB()
print("In funcA, x =", x)

funcA()
In funcB, x = 888
In funcA, x = 888

LEGB

  • L (Local) - 局部作用域
  • E (Enclosing) - 嵌套函数的非局部作用域
  • G (Global) - 全局作用域
  • B (Built-in) - 内置作用域

Python 查找变量时按照 L → E → G → B 的顺序依次查找。

Local (局部作用域)

在函数内部定义的变量,只在函数内部可见。

1
2
3
4
5
6
def my_func():
x = 10 # 局部变量
print(x)

my_func() # 输出: 10
print(x) # 报错: NameError

Enclosing (嵌套作用域)

在嵌套函数中,内部函数可以访问外部函数的变量。

1
2
3
4
5
6
7
8
9
def outer():
y = 20 # 嵌套作用域变量

def inner():
print(y) # 可以访问外部函数的变量

inner()

outer() # 输出: 20

Global (全局作用域)

在模块级别定义的变量,整个模块可见。

1
2
3
4
5
6
z = 30  # 全局变量

def my_func():
print(z) # 可以访问全局变量

my_func() # 输出: 30

Built-in (内置作用域)

Python 内置的函数和异常等,如 print(), len(), ValueError 等。

1
2
3
4
def my_func():
print(len("hello")) # 使用内置函数len

my_func() # 输出: 5

闭包

闭包指的是一个函数能够记住并访问其词法作用域中的变量,即使该函数在其词法作用域之外执行

工作原理:当外部函数执行完毕后,其命名空间通常会被销毁,但如果内部函数引用了外部函数的变量,这些变量会被保留下来,形成闭包

闭包需要满足三个条件:

  1. 必须有嵌套函数(函数内部定义函数)
  2. 内部函数必须引用外部函数的变量
  3. 外部函数必须返回内部函数

基本示例

1
2
3
4
5
6
7
8
9
def outer_func(x):
# 外部函数的变量x将被内部函数记住
def inner_func(y):
return x + y # 内部引用外部函数的变量
return inner_func # 返回内部函数,不执行

closure = outer_func(10) # closure现在是一个闭包
print(closure(5)) # 输出: 15 (10 + 5)
print(closure(10)) # 输出: 20 (10 + 10)

保持状态

nonlocal 是 Python 中用于在嵌套函数(函数内部的函数)中声明一个变量不是局部变量,而是来自外层函数作用域(但不是全局作用域)的关键字

1
2
3
4
5
6
7
8
9
10
11
12
def counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment

c = counter()
print(c()) # 1
print(c()) # 2
print(c()) # 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
origin = (0, 0) # 原点
legal_x = [-100, 100] # 限定x轴范围
legal_y = [-100, 100] # 限定y轴范围
def create(post_x = 0, post_y = 0):
def moving(direction, step):
nonlocal post_x, post_y
new_x = post_x + direction[0] * step
new_y = post_y + direction[1] * step

if new_x < legal_x[0]:
post_x = legal_x[0] - (new_x - legal_x[0])
elif new_x > legal_x[1]:
post_x = legal_x[1] - (new_x - legal_x[1])
else:
post_x = new_x

if new_y < legal_y[0]:
post_y = legal_y[0] - (new_y - legal_y[0])
elif new_y > legal_y[1]:
post_y = legal_y[1] - (new_y - legal_y[1])
else:
post_y = new_y
return post_x, post_y
return moving

创建特定功能的函数

1
2
3
4
5
6
7
8
9
10
11
12
def power(exp):
def exp_of(base):
return base ** exp
return exp_of

square = power(2)
cube = power(3)

square(4)
16
cube(4)
64

装饰器

装饰器是Python中一种强大的语法特性,允许在不修改原函数代码的情况下,动态地扩展函数的功能。装饰器本质上是一个高阶函数,接受一个函数作为参数并返回一个新的函数

基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def my_decorator(func):
def wrapper():
print("Before function call")
func()
print("After function call")
return wrapper

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

say_hello()
Before function call
Hello!
After function call

等价形式:

1
2
3
4
5
6
7
8
@my_decorator # 语法糖
def say_hello():
print("Hello!")

# 等价于
def say_hello():
print("Hello!")
say_hello = my_decorator(say_hello)

装饰器叠加

装饰器可以叠加使用,执行顺序是从下往上:

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

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

@decorator1
@decorator2
def my_function():
print("Original function")

my_function()

输出:

1
2
3
4
5
Decorator 1 before
Decorator 2 before
Original function
Decorator 2 after
Decorator 1 after

“带参数的函数”装饰

1
2
3
4
5
6
7
8
9
10
11
def decorator(func):
def wrapper(*args, **kwargs):
print("Decorator is working")
return func(*args, **kwargs) # 可变长度参数
return wrapper

@decorator
def greet(name): # 有参数'name'
print(f"Hello, {name}!")

greet("Alice")

带参数的装饰器

装饰器本身也可以接受参数(加一层嵌套):

1
2
3
4
5
6
7
8
def decorator_with_args(decorator_arg1, decorator_arg2, ...):
def decorator(func):
def wrapper(*args, **kwargs):
# 使用装饰器参数 decorator_arg1, decorator_arg2
# 执行被装饰函数 func(*args, **kwargs)
return result
return wrapper
return decorator
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
import time
def logger(msg):
def time_master(func):
def call_func():
start = time.time()
func
stop = time.time()
print(f"[{msg}]一共耗费了{(stop-start):.2f}")
return call_func
return time_master

@logger(msg="A")
def funcA():
time.sleep(1)
print("正在调用funcA")

@logger(msg="B")
def funcB():
time.sleep(1)
print("正在调用funcB")

funcA()
funcB()

# 等价于:
funA = logger(msg="A")(funcA)
funB = logger(msg="B")(funcB)

Lambda 表达式(匿名函数)

Lambda 表达式(也称为匿名函数)是 Python 中一种简洁的函数定义方式,可以用一行代码创建小型函数而无需使用 def 关键字

1
lambda arguments: expression
  • lambda:关键字,表示定义一个 lambda 表达式
  • arguments:函数的参数,可以有多个,用逗号分隔
  • expression:单个表达式,作为函数的返回值

基本示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
square = lambda x: x**2
print(square(5)) # 输出: 25

# 多参数
add = lambda a, b: a + b
print(add(3, 4)) # 输出: 7

# 无参数
get_answer = lambda: 42
print(get_answer()) # 输出: 42

# 返回两个数中较大的数
max_num = lambda a, b: a if a > b else b
print(max_num(10, 20)) # 输出: 20

常见使用场景

与高阶函数配合使用

map() 中使用

1
2
3
numbers = [1, 2, 3, 4]
squared = list(map(lambda x: x**2, numbers))
print(squared) # 输出: [1, 4, 9, 16]

filter() 中使用

1
2
3
numbers = [1, 2, 3, 4, 5, 6]
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # 输出: [2, 4, 6]

sorted() 中使用

1
2
3
pairs = [(1, 'one'), (3, 'three'), (2, 'two')]
sorted_pairs = sorted(pairs, key=lambda pair: pair[0])
print(sorted_pairs) # 输出: [(1, 'one'), (2, 'two'), (3, 'three')]

作为函数参数

1
2
3
4
5
def operate(func, x, y):
return func(x, y)

result = operate(lambda a, b: a * b, 5, 3)
print(result) # 输出: 15

生成器

生成器是 Python 中一种特殊的迭代器,允许你按需生成值,而不是一次性生成所有值并存储在内存中。这种惰性求值的特性使得生成器非常适合处理大数据流或无限序列

惰性求值 状态保持 内存高效

生成器函数

生成器函数使用 yield 语句而不是 return 来返回值

1
2
3
4
5
6
7
8
9
10
def count_up_to(max):
count = 1
while count <= max:
yield count # 每次调用next()时执行到这里并返回count的值
count += 1

# 创建生成器对象
counter = count_up_to(5)
print(next(counter)) # 输出: 1
print(next(counter)) # 输出: 2
  1. 当调用生成器函数时,返回一个生成器对象但不立即执行函数体
  2. 第一次调用 next() 时,执行到第一个 yield 语句并暂停
  3. 再次调用 next() 时,从上次暂停的位置继续执行,直到下一个 yield
  4. 当函数结束时(或遇到 return),抛出 StopIteration 异常
1
2
3
4
5
6
7
8
9
10
# 斐波那契数列
def fib():
a1, a2 = 0, 1
while True:
yield a1
a1, a2 = a2, a1 + a2
next(fib)
0
next(fib)
1

生成器表达式

1
2
3
squares = (x**2 for x in range(5))
print(next(squares)) # 输出: 0
print(next(squares)) # 输出: 1

递归

递归是一种函数调用自身的编程技术,特别适合解决可以分解为相似子问题的问题

递归函数包含两个部分:

  • 基准条件(Base Case):递归终止的条件
  • 递归条件(Recursive Case):函数调用自身的条件
1
2
3
4
5
def recursive_function(params):
if base_case_condition(params): # 基准条件
return base_case_value
else: # 递归条件
return recursive_function(modified_params)

基本示例

阶乘运算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def factorial(n):
if n == 1: # 基准条件
return 1
else: # 递归条件
return n * factorial(n-1)

print(factorial(5)) # 输出: 120

递归过程:
factorial(5)
5 * factorial(4)
5 * (4 * factorial(3))
5 * (4 * (3 * factorial(2)))
5 * (4 * (3 * (2 * factorial(1))))
5 * (4 * (3 * (2 * 1)))
5 * (4 * (3 * 2))
5 * (4 * 6)
5 * 24
120

斐波那契数列:

1
2
3
4
5
def fibonacci(n):
if n <= 1:
return n
else:
return fibonacci(n-1) + fibonacci(n-2)

汉诺塔问题

3根柱子(通常称为A、B、C),n个大小不一的圆盘,最初全部叠放在柱子A上,圆盘从下到上按大小递减排列

目标:将所有圆盘从柱子A移动到柱子C

游戏规则

  1. 一次只能移动一个圆盘
  2. 每次移动时,将最上面的圆盘移动到某一根柱子上
  3. 任何时候都不能将较大的圆盘放在较小的圆盘上面

对于n个圆盘的汉诺塔问题:

  1. 将上面的n-1个圆盘从A移动到B(借助C)
  2. 将第n个(最大的)圆盘从A移动到C
  3. 将那n-1个圆盘从B移动到C(借助A)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def hanoi(n, source, target, auxiliary):
"""
汉诺塔递归解法

参数:
n: 圆盘数量
source: 起始柱子
target: 目标柱子
auxiliary: 辅助柱子
"""
if n > 0:
# 步骤1:将n-1个圆盘从source移动到auxiliary(借助target)
hanoi(n-1, source, auxiliary, target)

# 步骤2:移动第n个圆盘到target
print(f"移动圆盘 {n}{source}{target}")

# 步骤3:将n-1个圆盘从auxiliary移动到target(借助source)
hanoi(n-1, auxiliary, target, source)

# 示例:3个圆盘,从A移动到C,借助B
hanoi(3, 'A', 'C', 'B')

递归与迭代的比较

特性 递归 (Recursion) 迭代 (Iteration)
定义 函数直接或间接调用自身 通过循环结构重复执行代码块
终止条件 必须有基准条件(base case)来终止递归 通过循环条件控制终止
实现方式 通过函数调用栈实现 通过循环变量和条件判断实现
维度 递归 迭代
时间复杂度 可能较高(如朴素斐波那契为O(2^n)) 通常更优(斐波那契迭代为O(n))
空间复杂度 需要维护调用栈,O(n) 通常只需要常数空间,O(1)
函数调用开销 每次递归都有函数调用开销 无额外函数调用开销
内存限制 受限于调用栈深度(Python默认约1000层) 不受此限制

函数文档

help()查看函数文档

单行:

1
2
3
def add(a, b):
"""返回两个数字的和。"""
return a + b

多行:

1
2
3
4
5
6
7
8
9
10
11
def calculate_area(radius):
"""
计算圆的面积。

参数:
radius (float): 圆的半径,必须为正数

返回:
float: 圆的面积
"""
return 3.14159 * radius ** 2

标准文档格式(Google风格):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def fetch_data(url, retries=3):
"""
从指定URL获取数据。

参数:
url (str): 要获取数据的URL地址
retries (int, optional): 重试次数,默认为3

返回:
dict: 包含获取的数据的字典

抛出:
ConnectionError: 当连接失败时抛出
ValueError: 当URL无效时抛出

示例:
>>> data = fetch_data("https://api.example.com/data")
>>> print(data.keys())
"""
# 函数实现...

类型注释

变量类型注释:

1
2
3
4
name: str = "Alice"
age: int = 25
is_student: bool = True
scores: list[float] = [90.5, 88.0, 92.5] # Python 3.9+

函数类型注释:

1
2
3
4
5
6
7
8
9
10
def greet(name: str) -> str:
return f"Hello, {name}"

def calculate_area(radius: float, pi: float = 3.14159) -> float:
return pi * radius ** 2

def myfunc(s:dict[str: int], n:int = 3):
return list(s.keys()) * n
myfunc({'A':1, 'B':2, 'C':3})
# 输出:['A', 'B', 'C', 'A', 'B', 'C', 'A', 'B', 'C']

函数内省

内省是指程序在运行时检查对象类型和属性的能力

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def example(a: int, b: float = 3.14) -> str:
"""示例函数"""
return f"{a} and {b}"

# 获取函数名称
print(example.__name__)
# 'example'

# 获取函数文档字符串
print(example.__doc__)
# '示例函数'

# 获取函数注解
print(example.__annotations__)
# {'a': <class 'int'>, 'b': <class 'float'>, 'return': <class 'str'>}

# 获取函数参数信息
import inspect
sig = inspect.signature(example)
print(sig)
# (a: int, b: float = 3.14) -> str

高阶函数

高阶函数满足以下至少一个条件:

  1. 接受一个或多个函数作为参数
  2. 返回一个函数作为结果

内置高阶函数

Python 内置了几个常用的高阶函数:

map(function, iterable)

对可迭代对象的每个元素应用函数:

1
2
3
4
5
6
numbers = [1, 2, 3, 4]
squared = map(lambda x: x**2, numbers)
print(list(squared)) # [1, 4, 9, 16]

# 等效的列表推导式
squared = [x**2 for x in numbers]

filter(function, iterable)

过滤可迭代对象中满足条件的元素:

1
2
3
4
5
6
numbers = [1, 2, 3, 4, 5, 6]
evens = filter(lambda x: x % 2 == 0, numbers)
print(list(evens)) # [2, 4, 6]

# 等效的列表推导式
evens = [x for x in numbers if x % 2 == 0]

functools.reduce(function, iterable[, initializer])

对可迭代对象进行累积计算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from functools import reduce

def add(x, y):
return x + y

reduce(add, [1, 2, 3, 4, 5])
# 15
# 相当于 add(add(add(add(1, 2), 3), 4), 5)

numbers = [1, 2, 3, 4]
product = reduce(lambda x, y: x * y, numbers)
print(product) # 24 (1*2*3*4)

# 带初始值
sum_squared = reduce(lambda acc, x: acc + x**2, numbers, 0)
print(sum_squared) # 30 (0 + 1 + 4 + 9 + 16)

sorted(iterable, key=None, reverse=False)

根据 key 函数对可迭代对象排序:

1
2
3
words = ["banana", "pie", "apple", "orange"]
sorted_words = sorted(words, key=lambda x: len(x))
print(sorted_words) # ['pie', 'apple', 'banana', 'orange']

自定义高阶函数

接受函数作为参数

1
2
3
4
5
6
def apply_operation(func, a, b):
"""应用给定函数到两个参数"""
return func(a, b)

result = apply_operation(lambda x, y: x + y, 5, 3)
print(result) # 8

返回函数

1
2
3
4
5
6
7
8
def make_multiplier(factor):
"""创建乘法器函数"""
def multiplier(x):
return x * factor
return multiplier

times2 = make_multiplier(2)
print(times2(5)) # 10

装饰器函数
装饰器是高阶函数的典型应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def log_time(func):
"""记录函数执行时间的装饰器"""
def wrapper(*args, **kwargs):
import time
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} executed in {end-start:.4f}s")
return result
return wrapper

@log_time
def slow_function():
import time
time.sleep(1)

slow_function() # 输出执行时间

偏函数

偏函数是指通过固定一个函数的部分参数,创建一个新的函数。这个新函数只需要传入剩余的参数即可执行

使用 functools.partial

1
2
3
4
5
6
7
8
9
10
from functools import partial

# 原始函数
def power(base, exponent):
return base ** exponent

# 创建偏函数 - 固定 exponent 参数为2
square = partial(power, exponent=2)

print(square(5)) # 25 (相当于 power(5, exponent=2))

@wraps 装饰器

@wraps 是 Python 标准库 functools 模块提供的一个装饰器,用于解决装饰器使用过程中的元信息丢失问题

元信息丢失

当使用装饰器时,原始函数的 __name____doc__ 等元信息会被包装函数的元信息覆盖:

1
2
3
4
5
6
7
8
9
10
11
12
13
def my_decorator(func):
def wrapper(*args, **kwargs):
"""包装函数的文档字符串"""
return func(*args, **kwargs)
return wrapper

@my_decorator
def example():
"""原始函数的文档字符串"""
pass

print(example.__name__) # 输出: 'wrapper' 而不是'example'
print(example.__doc__) # 输出: '包装函数的文档字符串'

引发的问题:

  1. 调试困难:堆栈跟踪显示的是包装函数名
  2. 文档丢失:原始函数的文档字符串不可见
  3. 自省失效:help() 等工具显示错误信息

使用 @wraps 保留元信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from functools import wraps

def my_decorator(func):
@wraps(func) # 保留原始函数的元信息
def wrapper(*args, **kwargs):
"""包装函数的文档字符串"""
return func(*args, **kwargs)
return wrapper

@my_decorator
def example():
"""原始函数的文档字符串"""
pass

print(example.__name__) # 输出: 'example'
print(example.__doc__) # 输出: '原始函数的文档字符串'

@wraps 会复制以下属性到包装函数:

  • __name__
  • __doc__
  • __module__
  • __annotations__
  • __dict__(原始函数的其他属性)

永久存储

open()函数

1
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

参数说明:

  1. file (必需)
    • 文件路径(相对或绝对路径)
    • 可以是字符串或字节对象
  2. mode (可选)
    • 指定打开文件的模式,默认为 'r' (只读)
    • 常用模式:
      • 'r' - 只读(默认)
      • 'w' - 写入,会覆盖已有文件
      • 'x' - 独占创建,如果文件已存在则失败
      • 'a' - 追加,写入内容到文件末尾
      • 'b' - 二进制模式
      • 't' - 文本模式(默认)
      • '+' - 更新(可读可写)
    • 组合模式示例:
      • 'rb' - 以二进制格式只读
      • 'w+' - 可读可写,会覆盖已有文件
      • 'a+' - 可读可追加
  3. buffering (可选)
    • 设置缓冲策略
    • 0 - 关闭缓冲(仅在二进制模式下)
    • 1 - 行缓冲(仅在文本模式下)
    • 大于1的整数 - 指定缓冲区大小(字节)
    • 不指定或-1 - 使用默认缓冲策略
  4. encoding (可选)
    • 指定文件的编码格式(如 'utf-8', 'gbk' 等)
    • 仅在文本模式下使用
  5. errors (可选)
    • 指定编码错误处理方式
    • 'strict' (默认), 'ignore', 'replace'
  6. newline (可选)
    • 控制换行符行为
    • None (默认) - 通用换行模式
    • '' - 不转换换行符
    • '\n', '\r', '\r\n' - 指定换行符

注意事项

文件覆盖问题

:使用 'w' 模式会直接覆盖原有文件内容

1
2
with open('data.txt', 'w') as f:
f.write('New content') # 原文件内容完全丢失

解决方案

  • 需要追加内容时使用 'a' 模式
1
2
3
4
5
6
7
import os
if os.path.exists('data.txt'):
# 先备份
os.rename('data.txt', 'data.bak.txt')

with open('data.txt', 'w') as f:
f.write('New content')

编码问题

:不指定编码可能导致乱码(特别是Windows平台)

1
2
3
# 在Windows上可能出问题
with open('data.txt', 'w') as f:
f.write('中文内容') # 可能保存为GBK编码

解决方案

  • 始终明确指定文件编码(推荐UTF-8)
  • 处理非ASCII字符时特别注意
1
2
with open('data.txt', 'w', encoding='utf-8') as f:
f.write('中文内容') # 正确保存为UTF-8编码

换行符问题

:不同操作系统换行符不同(Windows: \r\n, Unix: \n

1
2
3
# 在Windows上写入
with open('data.txt', 'w') as f:
f.write('Line1\nLine2') # 实际保存为Line1\r\nLine2

解决方案

  • 使用 newline 参数控制换行符
  • 需要跨平台一致时指定 newline='\n'
1
2
with open('data.txt', 'w', newline='\n') as f:
f.write('Line1\nLine2') # 强制使用\n作为换行符

路径问题

:相对路径导致的文件写入位置不确定

1
2
3
# 当前工作目录变化时,文件可能写入到意外位置
with open('data/output.txt', 'w') as f:
f.write('some data')

解决方案

  • 使用绝对路径
  • os.path 处理路径
  • 检查目录是否存在
1
2
3
4
5
6
7
8
9
import os

output_dir = 'data'
if not os.path.exists(output_dir):
os.makedirs(output_dir) # 创建目录

file_path = os.path.join(output_dir, 'output.txt')
with open(file_path, 'w') as f:
f.write('some data')

文件对象常用方法

读取相关方法

read(size=-1)

  • 功能:从文件中读取指定大小的数据
  • 参数:
    • size:可选,要读取的字节数(文本模式为字符数)
      • 负数或省略:读取全部内容
      • 正数:读取指定大小的数据
  • 返回:字符串(文本模式)或字节对象(二进制模式)
1
2
3
4
with open('example.txt', 'r') as f:
content = f.read() # 读取整个文件
f.seek(0) # 回到文件开头
first_100 = f.read(100) # 读取前100个字符

readline(size=-1)

  • 功能:读取文件中的一行
  • 参数:
    • size:可选,如果指定则读取该行的前size个字符
  • 返回:字符串(包含换行符)
1
2
3
with open('example.txt', 'r') as f:
first_line = f.readline() # 读取第一行
second_line = f.readline() # 读取第二行

readlines(hint=-1)

  • 功能:读取所有行并返回列表
  • 参数:
    • hint:可选,如果指定则读取大约hint字节的数据(不是精确值)
  • 返回:包含所有行的列表(每行包含换行符)
1
2
with open('example.txt', 'r') as f:
all_lines = f.readlines() # 获取包含所有行的列表

写入相关方法

write(string)

  • 功能:将字符串写入文件
  • 参数:
    • string:要写入的字符串(文本模式)或字节对象(二进制模式)
  • 返回:写入的字符/字节数
1
2
3
with open('output.txt', 'w') as f:
f.write('Hello, World!\n') # 写入一行
f.write('Second line') # 写入第二行

writelines(sequence)

  • 功能:将字符串序列写入文件
  • 参数:
    • sequence:字符串序列(列表、元组等)
  • 注意:不会自动添加换行符
1
2
3
lines = ['First line\n', 'Second line\n', 'Third line\n']
with open('output.txt', 'w') as f:
f.writelines(lines) # 写入多行

文件指针操作

seek(offset, whence=0)

  • 功能:移动文件指针到指定位置
  • 参数:
    • offset:偏移量
    • whence
      • 0(默认):从文件开头计算
      • 1:从当前位置计算
      • 2:从文件末尾计算
  • 注意:文本模式下只支持从开头(0)的查找
1
2
3
with open('example.txt', 'rb') as f:  # 二进制模式支持更多seek选项
f.seek(10) # 移动到第10字节
f.seek(-5, 2) # 移动到文件末尾前5字节

tell()

  • 功能:返回当前文件指针的位置
  • 返回:整数,表示指针位置(字节数)
1
2
3
4
with open('example.txt', 'r') as f:
pos = f.tell() # 获取当前位置(开始时为0)
f.read(10)
new_pos = f.tell() # 读取10字符后的位置

其他实用方法

flush()

  • 功能:强制将缓冲区内容写入磁盘
  • 说明:通常文件关闭或缓冲区满时会自动flush,但有时需要手动立即写入
1
2
3
with open('important.log', 'a') as f:
f.write('Critical error occurred!\n')
f.flush() # 确保立即写入磁盘

truncate(size=None)

  • 功能:截断文件到指定大小
  • 参数:
    • size:可选,要截断到的大小(字节)。如果省略,则截断到当前位置
  • 注意:文件必须以可写模式打开
1
2
3
with open('data.txt', 'r+') as f:
f.seek(100)
f.truncate() # 截断文件到前100字节

close()

  • 功能:关闭文件
  • 说明:使用 with 语句时会自动调用,通常不需要手动调用
1
2
3
4
5
f = open('example.txt', 'r')
try:
content = f.read()
finally:
f.close() # 确保文件被关闭

文件对象属性

closed

  • 功能:检查文件是否已关闭
  • 返回:布尔值
1
2
3
4
f = open('example.txt', 'r')
print(f.closed) # False
f.close()
print(f.closed) # True

mode

  • 功能:返回文件打开时的模式
1
2
with open('example.txt', 'r') as f:
print(f.mode) # 输出 'r'

name

  • 功能:返回打开的文件名
1
2
with open('example.txt', 'r') as f:
print(f.name) # 输出 'example.txt'

pathlib 模块

pathlib 是 Python 3.4+ 引入的面向对象(路径不再是字符串,而是 Path 对象)的文件系统路径操作模块,比传统的 os.path 更直观、更易用

创建Path对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 导入模块
from pathlib import Path

# 当前目录
p = Path() # 相当于 Path('.')

# 相对路径
p = Path('dir/sub_dir/file.txt')

# 绝对路径
p = Path('/usr/local/bin')

# 家目录
p = Path.home() # 如: /home/user 或 C:\Users\user

# 当前工作目录
p = Path.cwd()

路径拼接

使用 / 操作符拼接路径:

1
2
3
dir_path = Path('/home/user')
file_path = dir_path / 'documents' / 'file.txt'
print(file_path) # /home/user/documents/file.txt

使用 joinpath() 方法:

1
file_path = dir_path.joinpath('documents', 'file.txt')

路径解析

获取路径各部分:

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
p = Path('/home/user/documents/file.txt')

# 路径的各个部分
print(p.parts) # ('/', 'home', 'user', 'documents', 'file.txt')

# 父目录
print(p.parent) # /home/user/documents

# 父目录的父目录
print(p.parent.parent) # /home/user

# 父目录序列
print(p.parents) # Path.parents 返回一个包含所有父目录的序列,从直接父目录开始,一直到根目录
for parent in p.parents:
print(parent)

/home/user/documents
/home/user
/home
/

# 文件名(包含后缀)
print(p.name) # file.txt

# 文件名(不包含后缀)
print(p.stem) # file

# 后缀名
print(p.suffix) # .txt

# 后缀名列表(对于多重后缀)
p = Path('archive.tar.gz')
print(p.suffixes) # ['.tar', '.gz']

转为绝对路径:

1
2
3
p = Path('file.txt')
abs_p = p.absolute()
print(abs_p)

解析符号链接:

1
2
p = Path('/some/symlink')
real_p = p.resolve() # 返回真实路径

相对路径计算:

1
2
3
p1 = Path('/path/to/file')
p2 = Path('/path')
print(p1.relative_to(p2)) # to/file

路径修改

1
2
3
4
5
6
7
8
9
10
11
12
13
p = Path('/home/user/file.txt')

# 修改文件名但保留后缀
new_p = p.with_name('newfile.txt')
print(new_p) # /home/user/newfile.txt

# 修改后缀
new_p = p.with_suffix('.pdf')
print(new_p) # /home/user/file.pdf

# 修改主干名(文件名不带后缀)
new_p = p.with_stem('new_stem')
print(new_p) # /home/user/new_stem.txt

路径测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
p = Path('example.txt')

# 检查路径是否存在
print(p.exists()) # True/False

# 检查是否是文件
print(p.is_file())

# 检查是否是目录
print(p.is_dir())

# 检查是否是绝对路径
print(p.is_absolute())

# 检查是否是符号链接
print(p.is_symlink())

文件系统操作

创建目录:

1
2
3
4
5
6
7
8
# 创建单个目录
p = Path('new_dir')
p.mkdir() # 如果目录已存在会报错
p.mkdir(exist_ok=True) # 如果目录已存在不报错

# 创建多级目录
p = Path('dir/sub_dir/sub_sub_dir')
p.mkdir(parents=True, exist_ok=True)

删除目录或文件:

1
2
3
4
5
6
7
8
9
10
11
12
# 删除文件
p = Path('file.txt')
p.unlink() # 删除文件
p.unlink(missing_ok=True) # 文件不存在时不报错

# 删除目录(必须为空)
p = Path('empty_dir')
p.rmdir()

# 删除目录及内容(非空目录)
import shutil
shutil.rmtree(p) # 需要导入shutil模块

重命名/移动:

1
2
3
4
5
p = Path('old_name.txt')
p.rename('new_name.txt')

# 也可以移动到其他目录
p.rename(Path('other_dir') / 'new_name.txt')

文件操作

读写文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
p = Path('file.txt')

# 写入文件(会覆盖)
p.write_text('Hello, world!')

# 追加内容(需要手动打开文件)
with p.open('a') as f:
f.write('\nAppended text')

# 读取文件内容
content = p.read_text()
print(content)

# 读取二进制文件
data = p.read_bytes()

# 写入二进制文件
p.write_bytes(b'binary data')

文件统计信息:

1
2
3
4
5
6
7
8
9
10
11
p = Path('file.txt')
p.stat()
# 输出: os.stat_result(st_mode=33279, st_ino=844424930136614, st_dev=1777915557932014948, st_nlink=1, st_uid=0, st_gid=0, st_size=103704, st_atime=1751880929, st_mtime=1725625928, st_ctime=1725625928)

# 文件大小(字节)
print(p.stat().st_size)

# 最后修改时间
print(p.stat().st_mtime) # 时间戳
from datetime import datetime
print(datetime.fromtimestamp(p.stat().st_mtime)) # 转换

目录遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
p = Path('/path/to/dir')

# 遍历直接子项
for child in p.iterdir():
print(child)

# 递归遍历所有文件和目录
for item in p.glob('**/*'):
print(item)

# 查找特定类型的文件
for py_file in p.glob('*.py'):
print(py_file)

# 递归查找特定类型的文件
# rglob 等价于 glob('**/*')
for py_file in p.rglob('*.py'):
print(py_file)

with 语句与上下文管理器

with 语句是 Python 中用于资源管理的重要语法结构,通过上下文管理器(Context Manager)自动管理资源的获取和释放

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

常见用途:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 文件操作 - 自动关闭文件
with open('file.txt', 'r') as f:
content = f.read()
# 文件会在代码块结束后自动关闭

# 锁操作 - 自动释放锁
with threading.Lock():
# 线程安全操作
pass

# 数据库连接 - 自动关闭连接
with sqlite3.connect('db.sqlite') as conn:
cursor = conn.cursor()
# 操作数据库

对比:

1
2
3
4
5
6
7
8
9
10
f = open("demo.txt", "W")
f.write("Hello!")
1/0 # 这里会报错
f.close() # 不会执行到这句,所有不会保存

# 使用with语句
with open("demo.txt", "w") as f:
f.write("hello!")
1/0
# 即使报错也会正常保存

pickle 模块

pickle 是 Python 的标准模块,用于实现 Python 对象的序列化反序列化,可以将内存中的 Python 对象转换为字节流(序列化),也可以将字节流还原为 Python 对象(反序列化)

序列化(dump)

1
2
3
4
5
6
7
8
9
10
11
import pickle

data = {
'name': 'Alice',
'age': 25,
'scores': [88, 92, 95]
}

# 序列化到文件
with open('data.pkl', 'wb') as f: # 必须使用二进制模式
pickle.dump(data, f)

反序列化(load)

1
2
3
4
5
# 从文件反序列化
with open('data.pkl', 'rb') as f:
loaded_data = pickle.load(f)

print(loaded_data) # {'name': 'Alice', 'age': 25, 'scores': [88, 92, 95]}

内存中的序列化(dumps/loads)

1
2
3
4
5
# 序列化为字节对象
serialized = pickle.dumps(data)

# 从字节对象反序列化
deserialized = pickle.loads(serialized)

可序列化的对象类型

pickle 可以序列化大多数 Python 对象,包括:

  • 基本数据类型:int, float, bool, str, bytes, None
  • 容器类型:list, tuple, dict, set
  • 函数和类(只序列化名称,不序列化代码)
  • 类的实例(默认序列化实例的 __dict__

异常

常见内置异常:

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
BaseException
├── SystemExit
├── KeyboardInterrupt
├── GeneratorExit
└── Exception
├── ArithmeticError
│ ├── FloatingPointError
│ ├── OverflowError
│ └── ZeroDivisionError
├── AssertionError
├── AttributeError
├── BufferError
├── EOFError
├── ImportError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── MemoryError
├── NameError
├── OSError
│ ├── FileNotFoundError
│ ├── PermissionError
│ └── TimeoutError
├── RuntimeError
│ └── NotImplementedError
├── SyntaxError
├── TypeError
└── ValueError

try-except块

1
2
3
4
5
6
7
try:
# 可能引发异常的代码
result = 10 / 0
except ZeroDivisionError as e:
# 处理特定异常
print("不能除以零!")
print(f"错误详情:{e}")

处理多个异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
try:
# 可能引发多种异常的代码
value = int("abc")
result = 10 / value
except (ValueError, ZeroDivisionError) as e: # 使用元组
print(f"发生错误: {e}")


try:
1 / 0
"demo" + 520
except ZeroDivisionError:
print("除数不能为零!")
except ValueError:
print("值不正确!")
except TypeError:
print("类型不正确")

完整try-except-else-finally块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
try:
# 可能引发异常的代码块
risky_operation()
except ExceptionType1:
# 处理特定异常类型1
handle_exception1()
except ExceptionType2 as e:
# 处理特定异常类型2,并获取异常对象
handle_exception2(e)
except (ExceptionType3, ExceptionType4) as e:
# 处理多种异常类型
handle_multiple_exceptions(e)
except:
# 捕获所有未被前面处理的异常(不推荐常规使用)
handle_unexpected_errors()
else:
# 当try块没有引发异常时执行
operation_when_no_error()
finally:
# 无论是否发生异常都会执行
cleanup_resources()

各部分详解:

try

  • 包含可能引发异常的代码
  • 执行顺序:首先运行 try 块中的代码
  • 最佳实践:
    • 只包含可能引发异常的代码
    • 避免过于庞大的 try 块
1
2
3
4
try:
file = open('data.txt', 'r')
data = file.read()
processed = int(data)

except

  • 捕获并处理特定异常
  • 可以有多个 except 块处理不同类型的异常
  • 最佳实践:
    • 从具体到一般排列 except 块
    • 尽量捕获具体异常而非所有异常
1
2
3
4
5
6
except FileNotFoundError:
print("文件不存在,将使用默认值")
processed = 0
except ValueError as e:
print(f"文件内容不是有效数字: {e}")
processed = None

else

  • 当 try 块没有引发任何异常时执行
  • 执行顺序:在 try 块成功完成后,在 finally 之前
  • 最佳实践:
    • 将不直接关联异常检查的代码放在 else 中
    • 避免在 else 中放入可能引发新异常的代码
1
2
3
else:
print(f"成功读取并处理数据: {processed}")
save_to_database(processed)

finally

  • 无论是否发生异常都会执行
  • 常用于资源清理(如关闭文件、释放锁等)
  • 即使有 return、break 或 continue 也会执行
  • 最佳实践:
    • 确保释放所有获取的资源
    • 避免在 finally 中使用 return(会抑制异常)
1
2
3
4
finally:
if 'file' in locals() and file:
file.close()
print("资源清理完成")

执行流程示意图:

1
2
3
4
5
6
7
8
9
10
11
开始
|
└─ 执行 try 块
|
├─ 无异常 → 执行 else 块 → 执行 finally 块 → 继续后续代码
|
└─ 有异常 → 匹配 except 块
|
├─ 找到匹配 → 执行 except 块 → 执行 finally 块 → 继续后续代码
|
└─ 未找到匹配 → 执行 finally 块 → 异常向上传播

raise 语句

raise 是 Python 中用于主动抛出异常的关键字,允许程序员在检测到错误或特殊情况时中断正常程序流程并引发异常

抛出内置异常:

1
2
3
4
5
6
7
8
9
def calculate_average(numbers):
if not numbers:
raise ValueError("数字列表不能为空")
return sum(numbers) / len(numbers)

try:
avg = calculate_average([])
except ValueError as e:
print(f"错误: {e}") # 输出: 错误: 数字列表不能为空

重新抛出当前异常:

1
2
3
4
5
6
try:
# 可能引发异常的代码
risky_operation()
except SomeError:
print("发生了错误,但需要进一步处理")
raise # 重新抛出当前捕获的异常

assert 语句

assert 是 Python 中的断言语句,用于在程序中设置检查点,验证某个条件是否为真。如果条件为假,则会引发 AssertionError 异常

常用于调试

1
assert condition, "optional error message"
  • condition:要测试的条件表达式
  • optional error message:可选,当断言失败时显示的错误信息

示例:

1
2
3
4
5
6
7
def calculate_average(numbers):
assert len(numbers) > 0, "数字列表不能为空"
return sum(numbers) / len(numbers)

# 测试
print(calculate_average([1, 2, 3])) # 正常执行
print(calculate_average([])) # 引发 AssertionError: 数字列表不能为空

利用异常来实现goto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
try:
while True:
while True:
while True:
while True:
for i in range(10):
if i > 3:
raise # 抛出异常
print(i)
print("跳过")
print("跳过")
print("跳过")
print("跳过")
print("跳过")
except:
print("跳到这里啦!")

类和对象

类是面向对象编程(OOP)的核心概念,Python 作为一门面向对象的语言,提供了完整的类与对象支持

属性

1
2
3
4
5
6
7
8
9
10
class Dog:
species = "Canis familiaris" # 类属性,所有实例共享

def __init__(self, name, age):
self.name = name # 实例属性
self.age = age

buddy = Dog("Buddy", 9)
print(buddy.name) # Buddy
print(buddy.species) # Canis familiaris

方法

当通过实例调用方法时,Python 会自动将实例作为第一个参数(self)传递给方法

1
2
3
4
5
6
7
class MyClass:
def instance_method(self):
print(f"实例方法被调用,self: {self}")

obj = MyClass()
obj.instance_method() # 自动绑定obj到self
# 输出: 实例方法被调用,self: <__main__.MyClass object at 0x...>
1
2
3
4
5
6
7
8
9
class Dog:
def __init__(self, name):
self.name = name

def bark(self): # 实例方法
print(f"{self.name} says woof!")

dog = Dog("Rex")
dog.bark() # Rex says woof!

存取款示例:

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
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance

# 实例方法 - 存款
def deposit(self, amount):
if amount > 0:
self.balance += amount
print(f"存入 {amount},当前余额: {self.balance}")
else:
print("存款金额必须大于0")

# 实例方法 - 取款
def withdraw(self, amount):
if 0 < amount <= self.balance:
self.balance -= amount
print(f"取出 {amount},当前余额: {self.balance}")
else:
print("取款金额无效")

# 使用示例
account = BankAccount("Alice", 1000)
account.deposit(500) # 存入 500,当前余额: 1500
account.withdraw(200) # 取出 200,当前余额: 1300
account.withdraw(2000) # 取款金额无效

私有变量/方法

在 Python 中,私有变量是一种约定大于强制的方式来实现封装。Python 没有真正的私有变量,但提供了几种方式来限制对类内部数据的访问

单下划线前缀 _variable

  • 约定:表示”受保护”的变量,外部可以访问但不建议
  • 作用:提示开发者这是内部使用的变量
1
2
3
4
5
6
7
8
9
10
class MyClass:
def __init__(self):
self._protected_var = 10 # 受保护变量

def _protected_method(self): # 受保护方法
print("这是受保护方法")

obj = MyClass()
print(obj._protected_var) # 可以访问但不建议
obj._protected_method() # 可以调用但不建议

双下划线前缀 __variable

  • **名称修饰(Name Mangling)**:Python 会重写变量名:__variable → _ClassName__variable
  • 作用:避免子类意外覆盖父类属性
1
2
3
4
5
6
7
8
9
10
11
12
class MyClass:
def __init__(self):
self.__private_var = 20 # 私有变量

def __private_method(self): # 私有方法
print("这是私有方法")

obj = MyClass()
# print(obj.__private_var) # 直接访问会报错
print(obj._MyClass__private_var) # 20 (可以但不应该这样访问)
# obj.__private_method() # 报错
obj._MyClass__private_method() # 这是私有方法 (可以但不应该这样调用)

私有变量的访问控制:

1
2
3
4
5
6
7
class C:
def __init__(self, x):
self.__x = x
def get_x(self):
print(self.__x)
def set_x(self, x):
self.__x = x

封装

封装是面向对象编程的三大特性之一,隐藏对象的内部实现细节,仅对外暴露必要的接口

  1. 最小暴露原则
    • 只暴露必要的接口
    • 使用单下划线 _ 表示受保护成员
    • 使用双下划线 __ 表示私有成员(名称修饰)
  2. 使用属性装饰器
    • 控制属性的访问和修改
    • 添加验证逻辑
    • 实现计算属性
  3. 保持方法单一职责
    • 每个方法只做一件事
    • 复杂操作拆分为多个私有方法
  4. 防御式编程
    • 验证输入参数
    • 处理边界情况
    • 提供清晰的错误信息
  5. 文档化接口
    • 为公共方法添加docstring
    • 说明参数、返回值和可能抛出的异常

使用命名约定实现封装

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
class BankAccount:
def __init__(self, account_holder, initial_balance=0):
self.account_holder = account_holder # 公开属性
self._balance = initial_balance # 受保护属性(单下划线约定)
self.__transaction_history = [] # 私有属性(双下划线名称修饰)

def deposit(self, amount):
"""公开方法 - 存款"""
if amount > 0:
self._balance += amount
self.__add_transaction(f"存款: +{amount}")
return True
return False

def withdraw(self, amount):
"""公开方法 - 取款"""
if 0 < amount <= self._balance:
self._balance -= amount
self.__add_transaction(f"取款: -{amount}")
return True
return False

def __add_transaction(self, record):
"""私有方法 - 记录交易"""
self.__transaction_history.append(record)

def get_balance(self):
"""公开方法 - 获取余额"""
return self._balance

def get_recent_transactions(self, count=5):
"""公开方法 - 获取最近交易记录"""
return self.__transaction_history[-count:]

# 使用示例
account = BankAccount("张三", 1000)
account.deposit(500)
account.withdraw(200)

print(f"当前余额: {account.get_balance()}") # 1300
print("最近交易:", account.get_recent_transactions())
# 输出: ['存款: +500', '取款: -200']

# 注意: Python中私有属性仍可被访问(但不推荐)
print(account._BankAccount__transaction_history) # 不推荐这样访问

继承

继承是面向对象编程的三大特性之一,允许基于已有的类创建新类

基本示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Animal:
def __init__(self, name):
self.name = name

def speak(self):
print("动物发出声音")

class Dog(Animal): # 继承Animal类
pass

# 使用
dog = Dog("旺财")
print(dog.name) # 旺财
dog.speak() # 动物发出声音

方法重写

1
2
3
4
5
6
class Cat(Animal):
def speak(self): # 重写父类方法
print(f"{self.name}说: 喵喵~")

cat = Cat("小花")
cat.speak() # 小花说: 喵喵~

使用 super() 调用父类方法

1
2
3
4
5
6
7
8
9
10
class LoudDog(Dog):
def speak(self):
super().speak() # 先调用父类方法
print("汪汪汪!!!") # 再添加新行为

loud_dog = LoudDog("大黄")
loud_dog.speak()
# 输出:
# 动物发出声音
# 汪汪汪!!!

扩展init

1
2
3
4
5
6
7
class Bird(Animal):
def __init__(self, name, can_fly):
super().__init__(name) # 调用父类初始化
self.can_fly = can_fly # 添加新属性

bird = Bird("小蓝", True)
print(f"{bird.name} 能飞吗? {'能' if bird.can_fly else '不能'}")

多重继承

一个类同时继承多个父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Parent1:
def method1(self):
print("Parent1的方法")

class Parent2:
def method2(self):
print("Parent2的方法")

class Child(Parent1, Parent2): # 多重继承
def child_method(self):
print("子类的方法")

# 使用
child = Child()
child.method1() # 调用Parent1的方法
child.method2() # 调用Parent2的方法
child.child_method()

方法解析顺序(MRO)

有三种方式查看类的 MRO:

  1. 使用 __mro__ 属性
  2. 使用 mro() 方法
  3. 使用 inspect.getmro()
1
2
3
4
print(D.__mro__)
print(D.mro())
import inspect
print(inspect.getmro(D))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 同名冲突
class A:
def method(self):
print("A")

class B:
def method(self):
print("B")

class C(A, B):
pass

c = C()
c.method() # 输出"A",按照MRO顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A:
def method(self):
print("A的方法")

class B(A):
def method(self):
print("B的方法")

class C(A):
def method(self):
print("C的方法")

class D(B, C):
pass

print(D.__mro__)
# 输出: (<class '__main__.D'>, <class '__main__.B'>,
# <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

d = D()
d.method() # 输出"B的方法",按照MRO顺序

在多重继承中,super()按照MRO顺序调用父类方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class A:
def __init__(self):
print("A的初始化")
super().__init__()

class B:
def __init__(self):
print("B的初始化")
super().__init__()

class C(A, B):
def __init__(self):
print("C的初始化")
super().__init__()

c = C()
# 输出:
# C的初始化
# A的初始化
# B的初始化

菱形继承问题(钻石继承)

钻石继承或称菱形继承是多重继承中的一个经典问题,当多个子类继承自同一个父类,而另一个类又同时继承这些子类时,就会形成钻石形状的继承结构

问题演示:

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
class A:
def __init__(self):
print("A的初始化")

class B(A):
def __init__(self):
print("B的初始化")
A.__init__(self) # 直接调用父类

class C(A):
def __init__(self):
print("C的初始化")
A.__init__(self) # 直接调用父类

class D(B, C):
def __init__(self):
print("D的初始化")
B.__init__(self)
C.__init__(self)

d = D()
# 输出:
# D的初始化
# B的初始化
# A的初始化
# C的初始化
# A的初始化 # A被初始化了两次!

为什么需要 super()
super() 按照 MRO 顺序调用父类方法,确保钻石继承中每个类只被调用一次

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
class A:
def method(self):
print("A的方法")

class B(A):
def method(self):
print("B的方法")
super().method()

class C(A):
def method(self):
print("C的方法")
super().method()

class D(B, C):
def method(self):
print("D的方法")
super().method()

d = D()
d.method()
# 输出:
# D的方法
# B的方法
# C的方法
# A的方法

继承关系图:

1
2
3
4
5
  A
/ \
B C
\ /
D

组合

将其他类的实例作为类的成员变量来实现功能复用

汽车:

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
class Engine:
def start(self):
print("引擎启动")

def stop(self):
print("引擎停止")

class Car:
def __init__(self):
self.engine = Engine() # 组合 - Car有一个Engine

def start(self):
self.engine.start()
print("汽车启动")

def stop(self):
self.engine.stop()
print("汽车停止")

# 使用
my_car = Car()
my_car.start()
# 输出:
# 引擎启动
# 汽车启动

游戏角色装备系统:

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
48
49
50
51
52
class Weapon:
def __init__(self, name, damage):
self.name = name
self.damage = damage

def attack(self):
print(f"使用 {self.name} 造成 {self.damage} 点伤害")

class Armor:
def __init__(self, name, defense):
self.name = name
self.defense = defense

def defend(self):
print(f"穿戴 {self.name} 提供 {self.defense} 点防御")

class Character:
def __init__(self, name):
self.name = name
self.weapon = None # 初始没有武器
self.armor = None # 初始没有护甲

def equip_weapon(self, weapon):
self.weapon = weapon
print(f"{self.name} 装备了 {weapon.name}")

def equip_armor(self, armor):
self.armor = armor
print(f"{self.name} 穿戴了 {armor.name}")

def fight(self):
if self.weapon:
self.weapon.attack()
else:
print(f"{self.name} 赤手空拳攻击")

def show_defense(self):
if self.armor:
self.armor.defend()
else:
print(f"{self.name} 没有任何防护")

# 使用
sword = Weapon("圣剑", 50)
shield = Armor("龙鳞盾", 30)

hero = Character("勇者")
hero.equip_weapon(sword)
hero.equip_armor(shield)

hero.fight() # 使用 圣剑 造成 50 点伤害
hero.show_defense() # 穿戴 龙鳞盾 提供 30 点防御

GUI组件系统:

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
class Button:
def __init__(self, text):
self.text = text

def render(self):
print(f"渲染按钮: {self.text}")

class TextField:
def __init__(self, placeholder):
self.placeholder = placeholder

def render(self):
print(f"渲染文本框: {self.placeholder}")

class Panel:
def __init__(self):
self.components = [] # 组合多个组件

def add_component(self, component):
self.components.append(component)

def render(self):
print("开始渲染面板 ---")
for component in self.components:
component.render()
print("--- 面板渲染结束")

# 使用
login_panel = Panel()
login_panel.add_component(TextField("用户名"))
login_panel.add_component(TextField("密码"))
login_panel.add_component(Button("登录"))

login_panel.render()

混入类(Mixin)

混入类(Mixin)是一种特殊的设计模式,通过多重继承为类添加特定功能,而不影响类的主继承层次。Mixin 类通常不是独立存在的,而是用来”混入”其他类中

Mixin 基本特征:

  1. 单一功能:每个 Mixin 只提供一种特定功能
  2. 不独立使用:Mixin 类本身通常不实例化
  3. 不继承其他类:Mixin 通常只继承 object
  4. **无 __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
31
32
33
34
35
class JsonMixin:
"""添加JSON序列化功能"""
def to_json(self):
import json
return json.dumps(self.__dict__)

class XmlMixin:
"""添加XML序列化功能"""
def to_xml(self):
from xml.etree.ElementTree import Element, tostring
el = Element(self.__class__.__name__)
for k, v in self.__dict__.items():
child = Element(k)
child.text = str(v)
el.append(child)
return tostring(el)

class Person:
def __init__(self, name, age):
self.name = name
self.age = age

class JsonPerson(Person, JsonMixin):
pass

class UniversalPerson(Person, JsonMixin, XmlMixin):
pass

# 使用
p1 = JsonPerson("Alice", 25)
print(p1.to_json()) # {"name": "Alice", "age": 25}

p2 = UniversalPerson("Bob", 30)
print(p2.to_json())
print(p2.to_xml())
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
# 值得分析
class Displayer: # 基础显示类
def display(self, message):
print(message) # 简单打印消息

class LoggerMixin: # 日志混入类
def log(self, message, filename="logfile.txt"): # 默认日志文件
with open(filename, 'a') as f:
f.write(message)

def display(self, message):
super().display(message) # 调用父类的display
self.log(message) # 记录日志

class MySubClass(LoggerMixin, Displayer): # 多重继承
def log(self, message):
super().log(message, filename="subclasslog.txt") # 重写日志文件名

subclass = MySubClass()
subclass.display("This is a test")

print(MySubClass.mro())
[<class '__main__.MySubClass'>, <class '__main__.LoggerMixin'>, <class '__main__.Displayer'>, <class 'object'>] # 方法解析顺序(MRO):MySubClass > LoggerMixin > Displayer > object

执行流程
当调用subclass.display("This is a test")时:

首先找到LoggerMixin的display()方法

super().display()会沿着MRO链找到Displayer.display()并执行打印

然后调用self.log(),此时会使用子类重写的log()方法

子类的log()通过super()调用父类LoggerMixin.log(),但传入了新的文件名参数

命名约定:

1
2
3
4
5
6
7
# Mixin 类通常以 Mixin 或 Ability 结尾

class LoggableMixin:
pass

class SerializableAbility:
pass

方法覆盖处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class BaseMixin:
def method(self):
print("BaseMixin")
if hasattr(super(), 'method'):
super().method()

class OtherMixin:
def method(self):
print("OtherMixin")
if hasattr(super(), 'method'):
super().method()

class MyClass(BaseMixin, OtherMixin):
def method(self):
print("MyClass")
super().method()

obj = MyClass()
obj.method()
# 输出:
# MyClass
# BaseMixin
# OtherMixin

多态

多态是面向对象编程的三大特性之一,指同一操作作用于不同类的实例时,能产生不同的执行结果(根据不同的对象执行不同的操作)

  1. 基于继承的多态:子类重写父类方法
  2. 鸭子类型多态:不依赖继承,只要对象有相应方法即可
  3. 运算符重载:通过特殊方法实现多态行为
1
2
3
4
5
6
7
8
9
10
11
12
13
# 运算符的多态
# 根据不同的对象执行不同的操作
3 + 5
8

3 * 5
15

"My" + "Class"
'MyClass'

"My" * 3
'MyMyMy'

计算面积(基于继承的多态):

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
class Shape:
def __init__(self, name):
self.name = name
def area(self):
pass
class Square(Shape):
def __init__(self, length):
self.name = "正方形"
self.length = length
def area(self):
return self.length ** 2
class Circle(Shape):
def __init__(self, radius):
self.name = "圆形"
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
class Triangle(Shape):
def __init__(self, base, height):
self.name = "三角形"
self.base = base
self.height = height
def area(self):
return self.base * self.height / 2
def Calculate_area(FlatGraphics):
FlatGraphics.area

s = Square(5)
c= Circle(6)
t = triangle(3, 4)
s.name
'正方形'
c.name
'圆形'
t.name
'三角形'
s.area()
25
c.area()
113.03999999999
t.area
6.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Animal:
def speak(self):
raise NotImplementedError("子类必须实现speak方法")

class Dog(Animal):
def speak(self):
return "汪汪!"

class Cat(Animal):
def speak(self):
return "喵喵~"

def animal_speak(animal):
animal.speak()

# 多态调用
animal_speak(Dog()) # 汪汪!
animal_speak(Cat()) # 喵喵~

鸭子类型多态:

鸭子类型是 Python 的重要特性,源自名言:

“如果走起来像鸭子,叫起来像鸭子,那么就是鸭子”

鸭子类型的特点

  1. 不检查类型:关注对象的行为而非类型
  2. 不需要继承:只要对象有需要的方法/属性即可
  3. 更灵活:不强制要求类关系
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
class Duck:
def quack(self):
print("鸭子叫: 嘎嘎嘎")

def fly(self):
print("鸭子飞")

class Person:
def quack(self):
print("人模仿鸭子叫: 呱呱呱")

def fly(self):
print("人挥动手臂")

def in_the_forest(obj):
# 不检查类型,只要对象有quack和fly方法
obj.quack()
obj.fly()

# 都可以调用
in_the_forest(Duck())
# 鸭子叫: 嘎嘎嘎
# 鸭子飞

in_the_forest(Person())
# 人模仿鸭子叫: 呱呱呱
# 人挥动手臂

多态与鸭子类型的比较

特性 传统多态 鸭子类型
类型检查 基于继承层次 基于方法/属性存在性
灵活性 较低,需要预先定义继承关系 高,任何对象只要实现方法即可
显式关系 需要显式继承 不需要显式声明关系
典型语言 Java, C++ Python, Ruby
错误发现时机 编译时 运行时

__slots__ 详解

__slots__ 是 Python 中一个特殊的类属性,用于显式声明类实例可以拥有的属性,从而优化内存使用和提高属性访问速度

普通类使用 __dict__ 字典存储属性,占用更多内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class C:
def __init__(self, x):
self.x = x

c = C(250)
c.__dict__
{'x': 250}
c.y = 520
c.__dict__
{'x': 250, 'y': 520}
c.__dict__['z'] = 666
c.z
666
# 传统类:字典以空间换时间

使用 __slots__ 的类用固定大小的数组存储属性,节省内存(限制实例只能拥有预定义的属性,适用于将来不会动态添加属性的类):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person:
__slots__ = ['name', 'age'] # 只允许这两个属性

def __init__(self, name, age):
self.name = name
self.age = age

p = Person("张三", 30)
print(p.name) # 张三
print(p.age) # 30

# p.gender = "男" # 报错: AttributeError

class D:
__slots__ = ["x", "y"]
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
d = D(3, 4, 5) # AttributeError: 'D' object has no attribute 'z'

继承行为:

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
class Parent:
__slots__ = ['name']

class Child(Parent):
pass # 不继承__slots__,实例会有__dict__

class StrictChild(Parent):
__slots__ = [] # 继承父类__slots__,不添加新属性

class ExtendedChild(Parent):
__slots__ = ['age'] # 继承父类__slots__并添加新属性

p = Parent()
c = Child()
sc = StrictChild()
ec = ExtendedChild()

c.new_attr = 1 # 可以,因为Child有__dict__
c.__slots__
# ['name']
c.__dict__
# {'new_attr': 1} # 会添加到__dict__
# p.new_attr = 1 # 报错
# sc.new_attr = 1 # 报错
# ec.new_attr = 1 # 报错
ec.age = 10 # 可以

魔法方法(Magic Methods)详解

魔法方法(也称为特殊方法或双下方法)是 Python 中由双下划线(__)包围的特殊方法,允许类实现并响应语言的各种内置操作

__new__方法

  1. 第一个被调用的方法:在实例创建时最先执行
  2. 必须返回实例:通常返回父类的 __new__ 结果
  3. 控制实例创建:可以决定是否创建新实例

__new__ 是 Python 中真正创建实例的方法,是一个静态方法(不需要 @staticmethod 装饰器),在 __init__ 之前被调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyClass:
def __new__(cls, *args, **kwargs):
print("__new__ 被调用")
instance = super().__new__(cls) # 必须返回实例
return instance

def __init__(self, value):
print("__init__ 被调用")
self.value = value

obj = MyClass(10)
# 输出:
# __new__ 被调用
# __init__ 被调用
1
2
3
4
5
6
7
8
class CapStr(str):
def __new__(cls, string):
string = string.uppper()
return supper().__new__(cls, string)

cs = CapStr("demo")
cs
'DEMO'

__del__ 方法

__del__ 是对象的析构方法,当对象被垃圾回收时调用(不推荐依赖此方法进行资源清理)

  1. 不确定性:调用时机由垃圾回收器决定
  2. 不保证执行:程序退出时可能不会调用
  3. 不应用于关键资源释放:应使用上下文管理器或显式释放
1
2
3
4
5
6
7
8
9
10
11
12
class Resource:
def __init__(self, name):
self.name = name
print(f"资源 {self.name} 初始化")

def __del__(self):
print(f"资源 {self.name} 被释放")

res = Resource("文件句柄")
# 当res被垃圾回收时输出: 资源 文件句柄 被释放
del res
# 资源 文件句柄 被释放

循环引用问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Node:
def __init__(self, value):
self.value = value
self.next = None

def __del__(self):
print(f"删除节点 {self.value}")

# 创建循环引用
a = Node(1)
b = Node(2)
a.next = b
b.next = a

# 即使del a和b,__del__也不会被调用,因为循环引用

对象的重生(创建一个该实例的新引用来推迟其销毁)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def E:
def __init__(self, name, func):
self.name = name
self.func = func
def __del__(self):
self.func(self)
def outter():
x = 0
def inner(y=None):
if(y):
x = y
else:
return x
return innner

f = outter()
e = E("Jerry", f)
del e
g = f()
g.name
# 'Jerry'

运算符重载魔法方法

运算符类别 方法名 对应运算符 描述 示例
一元运算符 __neg__ - 负号 -obj
__pos__ + 正号 +obj
__abs__ abs() 绝对值 abs(obj)
__invert__ ~ 按位取反 ~obj
算术运算符 __add__ + 加法 obj1 + obj2
__sub__ - 减法 obj1 - obj2
__mul__ * 乘法 obj1 * obj2
__truediv__ / 真除法 obj1 / obj2
__floordiv__ // 地板除法 obj1 // obj2
__mod__ % 取模 obj1 % obj2
__pow__ ** 幂运算 obj1 ** obj2
反向算术运算 __radd__ + 反向加法 obj2 + obj1
__rsub__ - 反向减法 obj2 - obj1
__rmul__ * 反向乘法 obj2 * obj1
__rtruediv__ / 反向真除法 obj2 / obj1
__rfloordiv__ // 反向地板除法 obj2 // obj1
__rmod__ % 反向取模 obj2 % obj1
__rpow__ ** 反向幂运算 obj2 ** obj1
增量赋值运算 __iadd__ += 增量加法 obj1 += obj2
__isub__ -= 增量减法 obj1 -= obj2
__imul__ *= 增量乘法 obj1 *= obj2
__itruediv__ /= 增量真除法 obj1 /= obj2
__ifloordiv__ //= 增量地板除法 obj1 //= obj2
__imod__ %= 增量取模 obj1 %= obj2
__ipow__ **= 增量幂运算 obj1 **= obj2
比较运算符 __eq__ == 等于 obj1 == obj2
__ne__ != 不等于 obj1 != obj2
__lt__ < 小于 obj1 < obj2
__le__ <= 小于等于 obj1 <= obj2
__gt__ > 大于 obj1 > obj2
__ge__ >= 大于等于 obj1 >= obj2
类型转换 __int__ int() 转换为整数 int(obj)
__float__ float() 转换为浮点数 float(obj)
__bool__ bool() 转换为布尔值 bool(obj)
__str__ str() 转换为字符串 str(obj)
__repr__ repr() 官方字符串表示 repr(obj)
容器操作 __len__ len() 获取长度 len(obj)
__getitem__ [] 获取元素 obj[key]
__setitem__ []= 设置元素 obj[key] = value
__delitem__ del 删除元素 del obj[key]
__contains__ in 成员测试 item in obj
调用操作 __call__ () 使实例可调用 obj(args)
上下文管理 __enter__ with 进入上下文 with obj as x:
__exit__ with 退出上下文 with obj as x:

拦截字符串的“加法”操作(继承重写__add__):

1
2
3
4
5
6
7
8
9
10
11
class S(str):
def __add__(self, other):
return len(self) + len(other)
s1 = S("demo")
s2 = S("other")
s1 + s2 # 相当于 s1.__add__(s2),调用的是s1的方法(第一个)
# 9
s1 + "python" # 调用的是s1的方法
# 10
"python" + s2 # 调用的是"python"字符串的方法
'pythonother'

__radd__ 是 Python 中的反向加法(reverse addition)魔法方法,用于处理当左操作数不支持加法操作时的加法运算

1
2
3
4
5
6
7
8
9
10
class S1(str):
def __add__(self, other):
return NotImplemented
class S2(str):
def __radd__(self, other):
return len(self) + len(other)
s1 = S1("Apple")
s2 = S2("Banana")
s1 + s2
11

属性访问相关魔法方法

Python 提供了一系列魔法方法来控制对对象属性的访问,这些方法可以实现属性访问的拦截、计算属性、属性保护等功能

Python 属性访问的完整查找顺序:

  1. 数据描述符 (__get____set__)
  2. 实例属性 (obj.__dict__)
  3. 非数据描述符 (只有 __get__)
  4. __getattr__ (如果前面都找不到)

__getattr__(self, name)

  • 调用时机:当访问不存在的属性时调用
  • 典型用途:实现属性动态计算或惰性加载
1
2
3
4
5
6
7
class DynamicAttributes:
def __getattr__(self, name):
print(f"访问不存在的属性: {name}")
return name.upper() # 返回属性名的大写形式

obj = DynamicAttributes()
print(obj.undefined_attr) # 访问不存在的属性: undefined_attr → UNDEFINED_ATTR

__setattr__(self, name, value)

  • 调用时机:当设置任何属性时调用(包括 __init__ 中的赋值)
  • 典型用途:实现属性验证或触发副作用
1
2
3
4
5
6
7
8
9
10
class ValidatedAttributes:
def __setattr__(self, name, value):
if name == 'age' and value < 0:
raise ValueError("年龄不能为负数")
super().__setattr__(name, value) # 必须调用父类方法
# self.__dict__[name] = value

person = ValidatedAttributes()
person.age = 25 # 正常
# person.age = -5 # ValueError

__delattr__(self, name)

  • 调用时机:当删除属性时调用
  • 典型用途:防止重要属性被删除或执行清理操作
1
2
3
4
5
6
7
8
9
10
11
12
class ProtectedAttributes:
def __init__(self):
self.important_data = "重要数据"

def __delattr__(self, name):
if name == 'important_data':
raise AttributeError("不能删除重要属性")
super().__delattr__(name)
# del self.__dict__[name]

obj = ProtectedAttributes()
# del obj.important_data # AttributeError

__getattribute__(self, name)

  • 调用时机:访问任何属性时都会调用(包括存在的属性)
  • 注意:容易导致无限递归,必须谨慎使用
1
2
3
4
5
6
7
8
class AttributeLogger:
def __getattribute__(self, name):
print(f"访问属性: {name}")
return super().__getattribute__(name) # 必须使用super()

logger = AttributeLogger()
logger.x = 10
print(logger.x) # 先打印"访问属性: x",然后输出10

__dir__(self)

  • 调用时机:当调用 dir(obj)
  • 典型用途:自定义对象的属性列表
1
2
3
4
5
6
class CustomDir:
def __dir__(self):
return ['attr1', 'attr2', 'method1']

obj = CustomDir()
print(dir(obj)) # ['attr1', 'attr2', 'method1']

常见问题

__setattr__ 无限递归:

1
2
3
4
5
6
7
8
# 错误
class BadExample:
def __setattr__(self, name, value):
self.name = value # 会再次调用__setattr__!
# 正确
class GoodExample:
def __setattr__(self, name, value):
super().__setattr__(name, value) # 使用super()或直接操作__dict__

索引与切片相关魔法方法

__getitem__(self, key)

  • 作用:实现 obj[key] 的读取操作
  • 参数key 可以是整数、切片对象或其他类型
  • 返回值:根据 key 返回对应的元素
1
2
3
4
5
6
7
8
9
10
11
class MySequence:
def __init__(self, data):
self.data = list(data)

def __getitem__(self, index):
print(f"读取索引/切片: {index}")
return self.data[index]

seq = MySequence(range(10))
print(seq[3]) # 读取索引/切片: 3 → 3
print(seq[1:5]) # 读取索引/切片: slice(1, 5, None) → [1, 2, 3, 4]

__setitem__(self, key, value)

  • 作用:实现 obj[key] = value 的赋值操作
  • 参数key 可以是索引或切片,value 是要设置的值
1
2
3
4
5
6
7
8
9
10
11
12
class ModifiableSequence:
def __init__(self, data):
self.data = list(data)

def __setitem__(self, index, value):
print(f"设置索引/切片: {index} = {value}")
self.data[index] = value

mseq = ModifiableSequence(range(5))
mseq[2] = 100 # 设置索引/切片: 2 = 100
mseq[1:3] = [55, 66] # 设置索引/切片: slice(1, 3, None) = [55, 66]
print(mseq.data) # [0, 55, 66, 3, 4]

__delitem__(self, key)

  • 作用:实现 del obj[key] 的删除操作
  • 参数key 可以是索引或切片
1
2
3
4
5
6
7
8
9
10
11
12
13
class DeletableSequence:
def __init__(self, data):
self.data = list(data)

def __delitem__(self, index):
print(f"删除索引/切片: {index}")
del self.data[index]

dseq = DeletableSequence(range(5))
del dseq[3] # 删除索引/切片: 3
print(dseq.data) # [0, 1, 2, 4]
del dseq[1:3] # 删除索引/切片: slice(1, 3, None)
print(dseq.data) # [0, 4]

迭代相关魔法方法

可迭代对象 vs 迭代器

概念 要求 示例
可迭代对象 实现 __iter__ list, tuple, dict
迭代器 实现 __iter____next__ file对象, generator
  • 可迭代对象:可以被迭代的对象(可以不是迭代器本身)
  • 迭代器:实际执行迭代的对象(必须也是可迭代对象)

__iter__(self)

  • 作用:返回一个迭代器对象
  • 调用时机:当对象被用于 for 循环或 iter() 函数时
  • 返回值:必须返回实现了 __next__ 的迭代器对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class CountDown:
def __init__(self, start):
self.start = start

def __iter__(self):
self.current = self.start
return self # 返回自身,因为实现了__next__

def __next__(self):
if self.current <= 0:
raise StopIteration
value = self.current
self.current -= 1
return value

for num in CountDown(5):
print(num) # 输出: 5 4 3 2 1

__next__(self)

  • 作用:返回迭代的下一个值
  • 调用时机:每次迭代时由 next() 函数或 for 循环调用
  • 结束条件:抛出 StopIteration 异常表示迭代结束
1
2
3
4
5
6
7
8
9
10
x = [1, 2, 3, 4, 5]
next(x)
# TypeError: 'list' object is not an iterator
for i in x: # for调用__iter__
print(i, end=' ')
1 2 3 4 5

_ = iter(x) # 直接调用
next(_)
# 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Double:
def __init__(self, start, stop):
self.value = start - 1
self.stop = stop
def __iter__(self):
return self
def __next__(self):
if self.value = self.stop:
raise StopIteration
self.value += 1
return self.value * 2
d = Double(1, 5)
for i in d:
print(i, end = ' ')
2 4 6 8 10

与比较运算相关的魔法方法

修改为比较字符串长度:

1
2
3
4
5
6
7
8
9
10
11
12
13
class S(str):
def __lt__(self, other):
return len(self) < len(other)
def __le__(self, other):
return len(self) <= len(other)
def __gt__(self, other):
return len(self) > len(other)
def __ge__(self, other):
return len(self) >= len(other)
def __eq__(self, other):
return len(self) == len(other)
def __ne__(self, other):
return len(self) != len(other)

__eq__(self, other)

  • 对应操作符:==

  • 用于判断两个对象是否相等。

  • 示例:

    1
    2
    def __eq__(self, other):
    return self.value == other.value

__ne__(self, other)

  • 对应操作符:!=

  • 用于判断两个对象是否不相等。

  • 如果没有实现 __ne__,Python 会尝试调用 __eq__ 并取反。

  • 示例:

    1
    2
    def __ne__(self, other):
    return not (self == other) # 通常直接调用 __eq__ 并取反

__lt__(self, other)

  • 对应操作符:<

  • 用于判断当前对象是否小于另一个对象。

  • 示例:

    1
    2
    def __lt__(self, other):
    return self.value < other.value

__gt__(self, other)

  • 对应操作符:>

  • 用于判断当前对象是否大于另一个对象。

  • 示例:

    1
    2
    def __gt__(self, other):
    return self.value > other.value

__le__(self, other)

  • 对应操作符:<=

  • 用于判断当前对象是否小于或等于另一个对象。

  • 示例:

    1
    2
    def __le__(self, other):
    return self.value <= other.value

__ge__(self, other)

  • 对应操作符:>=

  • 用于判断当前对象是否大于或等于另一个对象。

  • 示例:

    1
    2
    def __ge__(self, other):
    return self.value >= other.value

对象调用相关的魔法方法

对象调用相关的魔法方法(Magic Methods)允许对象像函数一样被调用,或者控制对象的创建、初始化、销毁等行为

__call__(self, \*args, \**kwargs)

  • 作用:使对象可以像函数一样被调用(obj())。

  • 触发时机:当对象被当作函数调用时(如 obj()obj(arg1, arg2))。

  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class Adder:
    def __init__(self, base):
    self.base = base

    def __call__(self, x):
    return self.base + x

    add5 = Adder(5)
    print(add5(10)) # 输出 15(相当于 add5.__call__(10))

    class Power:
    def __init__(self, exp):
    self.exp = exp
    def __call__(self, base):
    return base ** self.exp
    square = Power(2)
    cube = Power(3)
    square(5)
    # 25
    cube(5)
    # 125

__init_subclass__(cls)

  • 作用:当类被继承时自动调用(Python 3.6+)。

  • 触发时机:定义子类时。

  • 示例

    1
    2
    3
    4
    5
    6
    7
    class Base:
    def __init_subclass__(cls, **kwargs):
    print(f"子类 {cls.__name__} 被创建")
    super().__init_subclass__(**kwargs)

    class Child(Base): # 输出:"子类 Child 被创建"
    pass
  • 应用场景

    • 注册子类(如插件系统)。
    • 强制子类实现某些方法。

__class_getitem__(cls, item)

  • 作用:支持泛型类型注解(Python 3.7+)。

  • 触发时机:当类被索引时(如 list[int])。

  • 示例

    1
    2
    3
    4
    5
    class GenericBox:
    def __class_getitem__(cls, item):
    return f"Box of {item}"

    print(GenericBox[int]) # 输出 "Box of int"
  • 应用场景

    • 自定义泛型类(如 typing 模块)。

对象字符串表示的魔法方法

__str__(self)

  • 作用:返回对象的用户友好字符串表示,用于 print()str()

  • 触发时机

    • print(obj)
    • str(obj)
    • f"{obj}"(f-string)
  • 特点

    • 目标是可读性,适合展示给最终用户。
    • 如果未定义 __str__,Python 会调用 __repr__ 作为备用。
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Person:
    def __init__(self, name, age):
    self.name = name
    self.age = age

    def __str__(self):
    return f"Person(name={self.name}, age={self.age})"

    p = Person("Alice", 25)
    print(p) # 输出:Person(name=Alice, age=25)

__repr__(self)

  • 作用:返回对象的明确且无歧义的字符串表示,用于调试和开发。

  • 触发时机

    • repr(obj)
    • 交互式环境(如 REPL)直接输入对象名时。
    • 如果 __str__ 未定义,print(obj) 也会调用 __repr__
  • 特点

    • 目标是明确性,通常返回一个合法的 Python 表达式,能用于 eval() 重建对象。
    • 官方建议:eval(repr(obj)) == obj 应尽量成立。
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Person:
    def __init__(self, name, age):
    self.name = name
    self.age = age

    def __repr__(self):
    return f"Person(name='{self.name}', age={self.age})"

    p = Person("Alice", 25)
    print(repr(p)) # 输出:Person(name='Alice', age=25)

__str__ vs __repr__ 对比

特性 __str__ __repr__
目标 用户可读的友好表示 明确的、无歧义的表示
触发场景 print(obj), str(obj), f-string repr(obj), 交互式环境, 调试
默认行为 如果未定义,调用 __repr__ 如果未定义,返回 <__main__.Obj...>
是否应可 eval 不要求 建议返回合法的 Python 表达式
示例返回值 "Person(Alice, 25)" "Person('Alice', age=25)"

代偿

某些方法未被实现时,Python 会尝试使用”代偿”(fallback)机制来提供功能

以迭代为例,迭代协议通过魔法方法 __iter__()__next__() 实现,当这些方法未被实现时,Python 会尝试使用其他方法来提供迭代功能

使用 __getitem__ 实现迭代

  1. 首先查找 __iter__ 方法
  2. 如果不存在,创建一个内置迭代器尝试从0开始的整数索引
  3. 调用 __getitem__ 直到抛出 IndexError
1
2
3
4
5
6
7
8
9
class FallbackIterable:
def __getitem__(self, index):
if index >= 5:
raise IndexError
return index * 2

# 自动支持迭代
for item in FallbackIterable():
print(item) # 0, 2, 4, 6, 8

property()

property() 是一个内置函数,用于将一个方法转换为属性(property),从而可以像访问属性一样调用方法,同时支持 getter、setter 和 deleter 逻辑。property() 可以以函数形式调用,也可以作为装饰器使用(更常见)

1
property(fget=None, fset=None, fdel=None, doc=None)
  • **fget**:获取属性值的方法(getter)
  • **fset**:设置属性值的方法(setter)
  • **fdel**:删除属性时调用的方法(deleter)
  • **doc**:属性的文档字符串(可通过 help() 查看)

为什么使用 property

  • 封装性:外部代码通过 obj.x 访问属性,而不需要知道内部是 _x
  • 灵活性:可以在 getter/setter 中添加额外逻辑(如验证、计算等)。
  • 兼容性:以后可以修改内部实现而不影响外部代码。
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 C:
def __init__(self):
# 初始化实例,设置一个受保护的属性 _x,初始值为 250
# 使用单下划线 _x 表示这是一个内部使用的属性(约定俗成)
self._x = 250

def getx(self):
# Getter 方法:用于获取 _x 的值
return self._x

def setx(self, value):
# Setter 方法:用于设置 _x 的值
self._x = value

def delx(self):
# Deleter 方法:用于删除 _x 属性
del self._x

# 使用 property() 函数将 getx, setx, delx 方法转换为属性 x 的访问器
# 这样可以通过 obj.x 来访问,而不是 obj.getx()
# property() 的参数依次是:fget (getter), fset (setter), fdel (deleter)
# 外部代码通过 obj.x 访问属性,而不需要知道内部是 _x
x = property(getx, setx, delx, "I'm the 'x' property.") # 不用装饰器的情况

obj = C() # 初始化,obj._x = 250
print(obj.x) # 250 (调用 getx)
obj.x = 100 # 调用 setx(100),obj._x 现在是 100
print(obj.x) # 100
del obj.x # 调用 delx(),删除 obj._x
# print(obj.x) # 现在会报错,因为 _x 已被删除

装饰器形式(更常用)

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
class Circle:
def __init__(self, radius):
self._radius = radius

@property
def radius(self):
"""Getter:获取半径"""
return self._radius
# radius = property(radius),所以下面是@radius.setter和@radius.deleter

@radius.setter
def radius(self, value):
"""Setter:设置半径"""
if value <= 0:
raise ValueError("半径必须为正数")
self._radius = value

@radius.deleter
def radius(self):
"""Deleter:删除半径"""
print("警告:删除半径")
del self._radius

c = Circle(5)
# 外部代码通过 obj.radius 访问属性,而不需要知道内部是 _radius
print(c.radius) # 5 (调用 get_radius)
c.radius = 10 # 调用 set_radius
# c.radius = -1 # 报错:ValueError
# del c.radius # 调用 del_radius,输出 "警告:删除半径"

只读属性

如果只定义 @property 而不定义 setter,则属性是只读的

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

@property
def area(self):
"""动态计算面积(只读)"""
return self._side ** 2

s = Square(4)
print(s.area) # 16
# s.area = 25 # 报错:AttributeError(没有 setter)

类方法(@classmethod

特点

  • 绑定到类本身,而不是实例。
  • **第一个参数是 cls**(类对象),而不是 self
  • 可以访问和修改类属性,但不能直接访问实例属性。
  • 通常用于工厂方法操作类级别的数据
1
2
3
4
5
class MyClass:
@classmethod
def my_class_method(cls, arg1, arg2):
# cls 是类本身(如 MyClass)
pass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class C:
count = 0
def __init__(self):
self.count += 1
@classmethod
def get_count(cls):
print("该类一共实例化了{cls.count}个对象")

c1 = C()
c2 = C()
c3 = C()
c3.get_count
# 该类一共实例化了3个对象
c3.count = 1
c3.get_count
# 该类一共实例化了3个对象
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
class C:
count = 0
@classmethod
def add(cls):
cls.count += 1
def __init__(self):
self.add()
@classmethod
def get_count(cls):
print("该类一共实例化了{cls.count}个对象")
class D:
count = 0
class E:
count = 0

c1 = C()
d1, d2 = D(), D()
e1, e2, e3 = E(), E(), E()
c1.get_count()
# 该类一共实例化了1个对象
d1.get_count()
# 该类一共实例化了2个对象
e1.get_count()
# 该类一共实例化了3个对象

# 说明是将对应的类分别传入

静态方法

特点

  • 不绑定到类或实例,没有 clsself 参数。
  • 不能访问类属性或实例属性,只是一个普通函数,但逻辑上属于类。
  • 通常用于工具函数与类相关但不依赖类状态的操作

语法

1
2
3
4
5
class MyClass:
@staticmethod
def my_static_method(arg1, arg2):
# 没有 cls 或 self 参数
pass

示例

1
2
3
4
5
6
7
8
9
10
class MathUtils:
@staticmethod
def add(a, b):
"""静态方法:加法工具函数"""
return a + b

@staticmethod
def circle_area(radius):
"""计算圆的面积"""
return 3.14 * radius ** 2

使用场景

  1. 工具函数(如数学计算、格式转换)
  2. 与类相关但不需要访问类状态(如 circle_areaMathUtils 相关,但不依赖类属性)

类方法 vs 静态方法 vs 实例方法

方法类型 装饰器 第一个参数 访问类属性 访问实例属性 典型用途
实例方法 self 可以 可以 操作实例数据
类方法 @classmethod cls 可以 不可以 工厂方法、操作类属性
静态方法 @staticmethod 不可以 不可以 工具函数、与类相关的独立逻

描述符

描述符是 Python 中一个强大的特性,允许你自定义属性访问的行为。描述符协议是 @property@classmethod@staticmethod 等装饰器的基础实现机制

描述符协议由以下三个特殊方法组成:

  1. __get__(self, obj, type=None) - 获取属性值时调用
  2. __set__(self, obj, value) - 设置属性值时调用
  3. __delete__(self, obj) - 删除属性时调用

任何实现了至少一个上述方法的类都称为描述符

根据实现的方法不同,描述符可分为两类:

  1. 数据描述符:实现了 __set____delete__ 方法
  2. 非数据描述符:只实现了 __get__ 方法

Python 属性查找时,数据描述符优先于实例字典中的属性,而非数据描述符则相反

简单描述符示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SimpleDescriptor:
def __get__(self, obj, objtype=None):
print("Getting value")
return 42

def __set__(self, obj, value):
print("Setting value to", value)

class MyClass:
attr = SimpleDescriptor()

obj = MyClass()
print(obj.attr) # 输出: Getting value \n 42
obj.attr = 100 # 输出: Setting value to 100

带存储的描述符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Field:
def __init__(self):
self._name = None

def __set_name__(self, owner, name):
self._name = name

def __get__(self, obj, objtype=None):
if obj is None:
return self
return obj.__dict__.get(self._name, None)

def __set__(self, obj, value):
obj.__dict__[self._name] = value

class Person:
name = Field()
age = Field()

p = Person()
p.name = "Alice"
p.age = 30
print(p.name, p.age) # 输出: Alice 30

验证描述符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class PositiveNumber:
def __init__(self):
self._name = None

def __set_name__(self, owner, name):
self._name = name

def __get__(self, obj, objtype=None):
if obj is None:
return self
return obj.__dict__.get(self._name, None)

def __set__(self, obj, value):
if not isinstance(value, (int, float)) or value <= 0:
raise ValueError("必须是正数")
obj.__dict__[self._name] = value

class Circle:
radius = PositiveNumber()

c = Circle()
c.radius = 5
print(c.radius) # 5
# c.radius = -1 # 抛出 ValueError

type()

获取对象的类型(常用用法)

1
2
3
4
5
6
7
8
x = 42
print(type(x)) # <class 'int'>

s = "hello"
print(type(s)) # <class 'str'>

def func(): pass
print(type(func)) # <class 'function'>

动态创建类(元编程用法)

type() 也可以用来动态创建新的类:

1
class type(name, bases, dict, **kwds)

参数说明:

  • name (字符串)
    • 要创建的类的名称
    • 将成为新类的 __name__ 属性
  • bases (元组)
    • 包含基类的元组
    • 将成为新类的 __bases__ 属性
    • 如果为空,默认继承自 object
  • dict (字典)
    • 包含类属性和方法的命名空间字典
    • 将成为新类的 __dict__ 属性
    • 可以包含方法、类变量等
  • **kwds (关键字参数,Python 3.6+ 新增)
    • 额外的元类控制参数
    • 例如 metaclass 参数可以指定自定义元类
1
2
3
4
5
6
7
8
9
10
11
class C:
def __init_subclass__(cls, value1, value2):
print("父爱如山")
cls.x = value1
cls.y = value2
D = type("D", (C,), dict(x=250), value1=520, value2=666)
# 父爱如山
D.x
# 520
D.y
# 666

元类

元类是 Python 面向对象编程中最深奥的概念之一,被称为”类的类”,用于控制类的创建行为。**type 是所有类的默认元类**

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyClass:
pass

print(type(MyClass))
# <class 'type'>

class MetaC(type):
pass

class C(metaclass=MetaC):
pass

c = C()
type(c)
# <class '__main__.C'>
type(C)
# <class '__main__.MetaC'>
type(MetaC)
# <class 'type'>

创建自定义元类

基本元类结构

1
2
3
4
5
6
7
8
class MyMeta(type):
def __new__(mcs, name, bases, namespace, **kwargs):
# 在类创建前干预
return super().__new__(mcs, name, bases, namespace)

def __init__(cls, name, bases, namespace, **kwargs):
# 在类创建后初始化
super().__init__(name, bases, namespace)

使用元类

1
2
class MyClass(metaclass=MyMeta):
pass

元类核心方法

__new__ 方法

控制类的创建过程,必须返回一个类对象。

1
2
3
4
def __new__(mcs, name, bases, namespace, **kwargs):
# 可以修改命名空间
namespace['version'] = 1.0
return super().__new__(mcs, name, bases, namespace)

__init__ 方法

初始化已创建的类。

1
2
3
4
def __init__(cls, name, bases, namespace, **kwargs):
# 可以添加类属性
cls.author = "Anonymous"
super().__init__(name, bases, namespace)

__prepare__ 方法 (Python 3.6+)

控制类命名空间的创建。

1
2
3
4
@classmethod
def __prepare__(mcs, name, bases, **kwargs):
# 返回一个自定义的字典类
return collections.OrderedDict()

抽象基类

抽象基类(ABC)是 Python 中定义接口和强制子类实现特定方法的一种机制。通过 abc 模块实现,是面向对象设计中重要的工具
特点:

  • 不能被实例化的类
  • 定义了一组必须由子类实现的方法和属性
  • 提供了一种标准化的方式来定义接口
1
2
3
4
5
6
7
8
9
10
from abc import ABC, abstractmethod

class Shape(ABC):
@abstractmethod
def area(self):
pass

@abstractmethod
def perimeter(self):
pass

抽象基类的主要作用:

  1. 定义接口规范
    • 明确规定子类必须实现哪些方法
    • 建立统一的API契约,确保所有子类具有一致的接口
  2. 强制实现约束
    • 确保子类不会遗漏关键方法的实现
    • 防止创建不完整的子类实现
  3. 实现多态
    • 允许不同子类以不同方式实现相同接口
    • 支持”一个接口,多种实现”的面向对象原则
  4. 类型检查
    • 使用isinstance()issubclass()进行类型验证
    • 提供更严格的类型约束机制
  5. 组织代码结构
    • 为相关类建立清晰的层次结构
    • 提高代码的可读性和可维护性

特别适合需要严格接口定义的大型项目或框架开发

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
from abc import ABC, abstractmethod

# 定义抽象基类
class DataProcessor(ABC):
@abstractmethod
def load_data(self, source):
"""从数据源加载数据"""
pass

@abstractmethod
def process_data(self):
"""处理数据"""
pass

@abstractmethod
def save_result(self, destination):
"""保存处理结果"""
pass

# 实现具体子类
class CSVProcessor(DataProcessor):
def load_data(self, source):
print(f"从CSV文件 {source} 加载数据")

def process_data(self):
print("处理CSV数据")

def save_result(self, destination):
print(f"将结果保存到 {destination}")

# 使用
processor = CSVProcessor()
processor.load_data("data.csv")
processor.process_data()
processor.save_result("result.csv")

模块和包

模块

导入模块

1
2
3
4
5
6
7
8
9
10
11
import 模块名称

from 模块名称 import 对象名称

# 导入所有内容(不推荐)
from 模块名称 import * # 小心覆盖
# 对于模块来说,如果没有定义__all__属性,那么from 模块名称 import * 将会导入模块里所有内容

# 多个模块里有相同的方法,后面导入的覆盖前面导入的
import 模块名称 as 关联名称 # 别名
from 模块名称 import 对象名称 as 关联名称 # 一般名字较长时使用

hello.py:

1
2
3
4
def say_Hello():
print("Hello!")
def say_Hi():
print("Hi!")

demo.py:

1
2
3
4
import hello

hello.say_Hello()
hello.say_Hi()
1
2
from hello import say_Hello
say_Hello()

__name__

  • __name__ 是一个内置变量,表示当前模块的名称
  • 当一个模块被直接运行时,__name__ 的值被设置为 "__main__"
  • 当一个模块被导入时,__name__ 的值被设置为模块的文件名(不含 .py 扩展名)

双重用途:允许一个文件既可以被其他模块导入使用,也可以作为独立脚本运行

1
2
3
4
5
6
7
# my_module.py
def my_function():
print("这是一个函数")

if __name__ == "__main__":
print("作为主程序运行")
my_function()

测试代码隔离:将模块的测试代码放在条件下,避免被导入时自动执行

1
2
3
4
5
6
7
# math_utils.py
def add(a, b):
return a + b

if __name__ == "__main__":
# 测试代码
print("测试 add 函数:", add(2, 3)) # 只有直接运行时才会执行

包(Package)是Python模块组织的高级形式,用于将相关的模块组织在一起,形成层次化的命名空间

包是一个包含多个Python模块的特殊目录,具有以下特点:

  • 必须包含__init__.py文件(即使是空文件)
  • 可以包含子包(子目录)
  • 形成层次化的模块命名空间

包结构示例

1
2
3
4
5
6
7
8
9
10
11
my_package/               # 包根目录
│── __init__.py # 包初始化文件
│── module1.py # 模块1
│── module2.py # 模块2
├── subpackage1/ # 子包1
│ │── __init__.py
│ │── submodule1.py
│ └── submodule2.py
└── subpackage2/ # 子包2
│── __init__.py
└── submodule3.py

__init__.py

基本作用:

  • 标识目录为Python包
  • 初始化包级别的代码
  • 定义包级别的变量和函数
  • 控制from package import *的行为

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# my_package/__init__.py

# 包级别变量
VERSION = '1.0'
x = 520

# 导入包中的关键功能到包级别命名空间
from .module1 import main_function

# 控制 from package import * 的行为
__all__ = ['main_function', 'VERSION']
# 对于包来说,如果没有定义__all__,那么from package import * 不会导入包里面的任何模块

# 包初始化代码
print(f"Initializing {__name__} package")

导入方式

基本导入:

1
2
3
4
5
6
7
8
9
10
11
# 导入整个包
import my_package

# 从包中导入特定模块
from my_package import module1

# 从子包中导入模块
from my_package.subpackage1 import submodule1

# 导入模块中的特定内容
from my_package.module2 import some_function

相对导入:

1
2
3
4
5
6
7
8
9
10
# 在my_package/subpackage1/submodule1.py中:

# 导入同级模块
from . import submodule2

# 导入父包中的模块
from .. import module1

# 导入兄弟子包中的模块
from ..subpackage2 import submodule3