Python语言入门
一. Python 语言入门
1.1 Hello, world!
这是一个示例 Python 脚本.
# 定义了一个函数,名为 print_hi# def <函数名>(<形参列表>):# <函数体>def print_hi(name): # 函数体 # 在这里,print 语句中,有一个模板字符串 {<变量名>}, # print 会自动在该函数体中使用相应的变量替换这个模板 # 注意,在字符串前,有一个 f,这代表后面这个字符串是一个模板字符串. print(f"Hello, {name}")
if __name__ == '__main__': print_hi('world')1.2 变量声明
在 Python 中没有真正的私有变量,若要模拟私有变量,只能使用约定式命名.
- 声明”保护”变量:
_变量名,以单下划线开头,约定为”请勿外部直接访问”,但解释器不限制. - 声明”私有”变量:
__变量名,以双下划线开头,会触发解释器的名称重整(name mangling),在类外部可通过_类名__变量名访问.
class Demo: def __init__(self): self._protected = "保护变量" self.__private = "私有变量"
d = Demo()print(d._protected) # 可以访问,仅为约定print(d._Demo__private) # 名称重整后可访问,但不推荐1.3 Python 解释器
常见的 Python 解释器:
- CPython: 官方参考实现,用 C 编写.
- Jython: 运行在 JVM 上的实现,可直接调用 Java 类.
- PyPy: 带 JIT 的另一种实现,运行速度快.
官方解释器下载: www.python.org
- 组成: 纯 Python 解释器,无其它功能.
第三方发行版:
- Anaconda,Miniconda.
- 组成: Python 解释器,虚拟环境管理,常用的第三方包等.
- 功能: 可创建相互隔离的独立 Python 环境.
大约 95% 的 Python 代码都由 CPython 解释执行.
1.4 IDE
常用 IDE:
-
PyCharm(功能完整,适合大型项目)
-
VS Code(轻量,插件丰富).
二. 语法
2.1 数据类型
命名规范: 变量名使用全小写字母 + 下划线的方式命名.
注意: Python 没有真正的常量.若需要”常量”,通常使用全大写变量名作为约定,例如
MAX_SIZE = 100,但解释器不会阻止对其重新赋值.
常用方法
address = id( < 变量名 >) # 获取对象的内存地址(逻辑地址)print(address)数字类型
int_var = 10 # int 整数float_var = 3.14 # float 浮点数
print(int_var + 1) # 11print(float_var / 3) # 1.0466666666666666精度丢失问题及其解决方案
from decimal import Decimal
z = 0.1 + 0.2# 浮点数二进制表示存在误差,期待 0.3,实际输出 0.30000000000000004print(z)
# 使用 Decimal 将 0.1 和 0.2 以字符串字面量包裹起来,避免二进制误差x = Decimal("0.1")y = Decimal("0.2")z = x + yprint(z) # 0.3示例: 金融计算应优先使用
Decimal,避免累计误差.
from decimal import Decimal, getcontext
getcontext().prec = 4 # 设置全局精度为 4 位有效数字print(Decimal("1.0") / Decimal("3.0")) # 0.333布尔类型 (bool)
以下值在逻辑判断时会被视为 False,其它情况为 True:
None""(空字符串)0(数值零)[](空列表){}(空字典)False
bool_var = True
# 推荐: 直接使用布尔值进行真值判断if bool_var: print("为真")
if not bool_var: print("为假")
# 也可以使用 == 进行比较(更明确)if bool_var == True: print("为真")
# 可以使用 is,但存在隐患,不推荐使用,原因见"is 身份运算符"章节if bool_var is True: print("为真")
# 这是一个坑: bool() 只判断传入对象是否为"空"# 虽然传入了 "False" 字面量,但它是非空字符串,所以结果为 Truebool_var = bool("False")print(bool_var) # True
empty_tuple = () # 创建空元组方式一empty_tuple = tuple() # 创建空元组方式二print(bool(empty_tuple)) # False字符串 (str)
字符串的声明方式有三种:
- 单引号
'...' - 双引号
"..." - 三引号
'''...'''或"""..."""
单引号与双引号声明的字符串没有任何区别,仅仅是定界符不同. 三引号声明的字符串支持多行,代码中写成什么样,运行时就是什么样,也常用于函数和类的文档注释(docstring).
字符串是不可变的.尝试修改字符串中的字符会报错;任何”修改”操作都会创建并返回一个新字符串,原字符串变量重新指向新对象( 地址改变).
str_var1 = "hello"str_var2 = 'world'str_var3 = """多行字符串,也常用于函数和类的文档注释"""
print(f"{str_var1 + str_var2 + str_var3}")字符串切片 (slice)
语法: [<起始下标>:<终止下标>:<步长>],从起始下标开始,到终止下标之前(不包含终止下标,左闭右开).
若起始下标为 0 或终止下标为 len(字符串),可以省略.
下标支持负数,从右往左数,最右边元素下标为 -1.
s = "abcdefghij"
print(s[0:5]) # 'abcde' 索引 0 到 4print(s[2:8:2]) # 'ceg' 从索引 2 开始,到 7 结束,步长为 2
print(s[:5]) # 'abcde' 省略 start,默认从 0 开始print(s[5:]) # 'fghij' 省略 stop,默认到末尾print(s[::]) # 'abcdefghij' 省略 step,默认步长为 1
print(s[-1]) # 'j' 最后一个字符print(s[-5:]) # 'fghij' 倒数 5 个字符print(s[::-1]) # 'jihgfedcba' 反转字符串,步长为 -1,常用技巧切片同样适用于列表,元组等序列类型.
字符串常用方法
# 获取字符串长度(字符数,非字节数)str_var = "Hello kulve"print(len(str_var)) # 11
# 注意: 所有"修改"字符串的方法都返回新字符串,原字符串不变text = " Python is a programming language. Welcome to using Python. "print("--- 去除空白 ---")print(text.strip()) # 去除两端空白print(text.lstrip()) # 去除左侧空白print(text.rstrip()) # 去除右侧空白print(text.strip().strip(".")) # 可链式去除指定字符
text = "Python is a programming language. Welcome to using Python."print("--- 大小写转换 ---")print(text.lower()) # 转为小写print(text.upper()) # 转为大写print(text.title()) # 每个单词首字母大写print(text.capitalize()) # 仅第一个字符大写
print("--- 查找 ---")print(text.find("language")) # 返回索引,找不到返回 -1print(text.find("NOT_EXIST")) # -1print(text.index("Python")) # 0,类似 find,但找不到会抛出 ValueError
print("--- 统计出现次数 ---")print(text.count("Python")) # 2
print("--- 替换 ---")print(text.replace("Python", "Java"))# Java is a programming language. Welcome to using Java.
print("--- 分割 ---")words = text.split() # 默认按空白字符分割,返回列表print(words)csv_var = "apple,banana,cherry"print(csv_var.split(',')) # 按指定分隔符分割 ['apple', 'banana', 'cherry']
print("--- 连接 ---")new_text = '-'.join(words) # join 是 split 的逆操作print(new_text)
print("--- 开头和结尾匹配 ---")filename = "report.pdf"print(filename.startswith("report")) # Trueprint(filename.endswith(".pdf")) # True
print("--- 判断字符串内容 ---")print("12345".isdigit()) # True 是否全部由数字组成print("abc123".isalpha()) # False 是否全部由字母组成print("abc123".isalnum()) # True 是否全部由字母或数字组成print(" ".isspace()) # True 是否全部由空白符组成类型转换 (Type Casting)
# --- 字符串转其他类型 ---num_str = "123"float_str = "123.45"bool_str = "True"list_str = "[1, 2, 3]"
num_int = int(num_str) # 123num_float = float(float_str) # 123.45
# 转为布尔值: 非空字符串转为 True,空字符串转为 Falseprint(bool("any string")) # Trueprint(bool("")) # False# 注意: bool("False") 也是 True,因为它只是非空字符串.# 若要按字符串内容解析,使用 ast.literal_evalimport ast
print(ast.literal_eval(bool_str)) # Truelist_obj = ast.literal_eval(list_str) # [1, 2, 3]
# --- 其他类型转字符串 ---num = 42pi = 3.1415is_true = Truemy_list = [1, 2, 3]
print(str(num)) # "42"print(str(pi)) # "3.1415"print(str(is_true)) # "True"print(str(my_list)) # "[1, 2, 3]"字符串格式化
# 格式化方式一: f-string(推荐)name = "Alice"age = 30print(f"My name is {name} and I am {age} years old.")
# 格式化方式二: % 格式化(较旧)print("My name is %s and I am %d years old." % (name, age))
# 格式化方式三: str.format()print("My name is {} and I am {} years old.".format(name, age))字面量拼接(特殊语法)
greeting2 = "Hello" " World" # 结果: "Hello World"(仅限于相邻的字面量)print(greeting2)# 注意: 该语法仅适用于字面量直接相邻,中间不能有变量,编译器会将其合并为一个字符串.列表 (list) - []
列表不等同于其它语言中的”数组”,但用法相似.
不同点:
- 一个列表可以容纳多种不同类型的对象(数字,字符串等).
- 自动扩容 / 缩容.
- 自动垃圾回收.
可以理解为 Java 中
Object类型的ArrayList,同时支持混合类型.
arr = [1, 2, 3, 4]print(arr)
arr[0] = 100 # 修改元素arr.append(5) # 追加元素print(arr)
popped = arr.pop() # 弹出最后一个元素print(popped)列表推导式 (List Comprehension)
语法: [<表达式> for <变量> in <可迭代对象> if <条件>]
# 生成 0..4 的平方squares = [x * x for x in range(5)]print(squares) # [0, 1, 4, 9, 16]
# 等价的传统写法squares2 = []for x in range(5): squares2.append(x * x)列表常用方法
list_var = [1, 2, 3, 4]
# append(<值>): 在末尾追加一个元素list_var.append("#")
# extend(<可迭代对象>): 将可迭代对象中的元素逐个追加(与 a + b 不同,后者返回新列表)list_var.extend((21, 22, 23)) # 追加元组list_var.extend([11, 12, 13]) # 追加列表list_var.extend({31, 32, 33}) # 追加集合(顺序不固定)list_var.append([41, 42, 43]) # append 会把整个列表当作一个元素
# insert(<下标>, <值>): 在指定下标前插入list_var.insert(0, [41, 42, 43])
# remove(<值>): 删除第一个匹配的值,不存在则报错list_var.remove(1)
# count(<值>): 统计出现次数print(list_var.count([41, 42, 43]))
# index(<值>[, <起始下标>]): 查找首次出现的下标print(list_var.index(3, 2))
# copy(): 浅拷贝list_var = [1, 2, 3, 4, [5, 6]]list_var_copy = list_var.copy()list_var_copy[4][1] = 41 # 嵌套列表被修改,会影响原列表(浅拷贝)print(list_var)
# sort([key=...][, reverse=True]): 原地排序list_var = [[1], [2], [1, 2], [0, "2", 3, 4]]list_var.sort(key=len, reverse=True) # 按长度降序
# sorted(<可迭代对象>): 非原地排序,返回新列表sorted_list = sorted([3, 2, 4, 1], reverse=True)
# reverse(): 原地反转list_var = [1, 2, 3, 4]list_var.reverse()
# reversed(<可迭代对象>): 非原地反转,返回迭代器rev = reversed([1, 2, 3, 4])print(list(rev))补充:
pop(-1)移除最后一个元素,pop(-2)移除倒数第二个元素.
集合 (set) - {}
集合是无序,去重的.
def foreach_set(s): for i in s: print(i, end="") print()
set_var = {1, 2, 3, 3} # 重复的 3 被去重foreach_set(set_var)
set_var.add(4)foreach_set(set_var)
set_var.remove(2) # 元素不存在会报错set_var.discard(2) # 元素不存在不会报错声明空集合只有一种方式:
empty_set = set() # 注意: {} 创建的是空字典,不是空集合集合数学运算
a = {1, 2, 3, 4, 5, 5}b = {5, 7}
# & 交集 | 并集 - 差集 ^ 对称差集print(a & b) # {5}print(a | b) # {1, 2, 3, 4, 5, 7}print(a - b) # {1, 2, 3, 4}print(a ^ b) # {1, 2, 3, 4, 7}
# 对称差集等价于 (a | b) - (a & b)print((a | b) - (a & b))集合关系运算
set_x = {1, 2}set_y = {1, 2, 3, 4}
# 子集 / 真子集print(set_x.issubset(set_y)) # Trueprint(set_x <= set_y) # Trueprint(set_x < set_y) # True (真子集)
# 超集 / 真超集print(set_y.issuperset(set_x)) # Trueprint(set_y >= set_x) # Trueprint(set_y > set_x) # True (真超集)不可变集合 (frozenset)
frozenset 是 set 的不可变版本,创建后内容不可改变.因为不可变,它可以作为字典的键,或作为另一个集合的元素.
frozen = frozenset([1, 2, 3, 4])# frozen.add(5) # AttributeError: 'frozenset' 没有 add 方法
dict_with_frozen_key = {frozenset({1, 2}): "value"}print(dict_with_frozen_key)字典 (dict) - {}
字典与 Java 中的 HashMap 很像,用于存储键值对.
person = { "name": "Alice", "age": 20, 1: 2, 1.1: 2.2,}print(person)
person["name"] = "bob" # 修改已有键person["city"] = "New Zealand" # 新增键print(person)若键名重复,保留最后一次出现的值:
d = {"123": 1, "123": 2}print(d) # {'123': 2}高级用法
# 1. 创建字典的多种方式dict1 = {"key1": "value1", "key2": "value2"}dict2 = dict(name="Charlie", age=25) # 关键字参数dict3 = dict([("fruit", "apple"), ("color", "red")]) # 键值对列表
# 2. 安全访问和修改person = {"name": "Alice", "age": 20}print(person.get("age")) # 20print(person.get("notExists", "默认")) # 默认(键不存在返回默认值)person.setdefault("email", "a@b.com") # 键不存在则插入默认值
# 3. 字典合并person.update({"age": 21, "job": "Engineer"}) # 原地合并,修改原字典new_info = {"country": "USA", "job": "Developer"}merged = person | new_info # Python 3.9+,返回新字典,不修改原字典
# 4. 字典推导式squares = {x: x ** 2 for x in range(1, 6)}print(squares)
# 5. 视图对象(动态反映字典最新状态)keys = person.keys()values = person.values()items = person.items() # 最常用于遍历person["new_key"] = "new_value"print(keys) # 视图会自动包含新键元组 (tuple) - ()
元组是用圆括号括起来的一组数据,不可变.一般用于固定数据,或在一个函数中同时返回多个值.
tuple_var = (1, 2, 3)
# tuple_var[0] = 2 # TypeError: 元组不支持 item 赋值
def func(): return 1, 2 # 实际返回的是一个元组
x, y = func() # 自动解包创建空元组:
empty_tuple = ()empty_tuple = tuple()数组 (array)
Python 有真正的数组(array 模块),但日常基本不用.
日常用 list;搞科学计算 / 数据分析用 numpy;内置 array 模块基本可以忽略.
# 内置 array 模块from array import array
arr = array('i', [1, 2, 3, 4]) # 'i' 表示有符号整数print(arr[1])
# 科学计算推荐使用 numpy(需额外安装)import numpy as np
a = np.array([1, 2, 3])print(a[1])类型强制转换
通用语法: <类型>(<值>).
movie = "叶问"number = [1, 2]print(movie + str(number)) # 叶问[1, 2]安全提示:
eval()可以把字符串当作代码执行,存在严重安全漏洞,不要用于解析不可信输入.若只需把字面量字符串转为对应对象,使用ast.literal_eval()(只允许解析字面量表达式,不执行代码).
import ast
print(ast.literal_eval("True")) # Trueprint(ast.literal_eval("[1, 2, 3]")) # [1, 2, 3]列表与元组转换时的拷贝陷阱
tuple_var = (1, 2, 3, [4, 5, 6])list_var = list(tuple_var) # 元组转列表(浅拷贝)
list_var[2] = 120 # 修改顶层元素,不影响原元组list_var[3][1] = 10 # 修改嵌套列表,会影响原元组(共享同一嵌套对象)
print(tuple_var) # (1, 2, 3, [4, 10, 6])print(list_var)2.2 运算符
算术运算符
a = 10, b = 20
| 运算符 | 描述 | 实例 |
|---|---|---|
| + | 加 - 两个对象相加 | a + b 输出 30;也可用于列表 / 字符串拼接 |
| - | 减 - 得到负数或一个数减去另一个数 | a - b 输出 -10 |
| * | 乘 - 两个数相乘或返回重复若干次的字符串 | a * b 输出 200;'-' * 3 输出 '---' |
| / | 除 - x 除以 y | b / a 输出 2.0(结果永远为 float) |
| % | 取模 - 返回除法的余数 | b % a 输出 0;公式 a % b = a - b * floor(a / b);print(7 % 3) 输出 1;print(-7 % 3) 输出 2 |
| ** | 幂 - 返回 x 的 y 次幂 | a ** b 为 10 的 20 次方 |
| // | 取整除 - 返回商的整数部分(向下取整) | 9 // 2 输出 4;-9 // 2 输出 -5(向下取整,非截断) |
比较运算符
a = 10, b = 20
| 运算符 | 描述 | 实例 |
|---|---|---|
| == | 等于 - 比较对象是否相等 | (a == b) 返回 False |
| != | 不等于 - 比较两个对象是否不相等 | (a != b) 返回 True |
| <> | 不等于 - Python 3 已废弃(仅作了解) | 类似 !=,不推荐使用 |
| > | 大于 - 返回 x 是否大于 y | (a > b) 返回 False |
| < | 小于 - 返回 x 是否小于 y | (a < b) 返回 True |
| >= | 大于等于 - 返回 x 是否大于等于 y | (a >= b) 返回 False |
| <= | 小于等于 - 返回 x 是否小于等于 y | (a <= b) 返回 True |
修订说明: 原笔记称”比较运算符返回 1 表示真,0 表示假,等价于 True/False”,这在现代 Python 中不准确——比较运算直接返回
bool类型的True/False,并非整数 1 / 0(尽管True == 1,False == 0在值上成立).
链式比较
# 其它语言(左结合): True == False == False# -> (True == False) == False -> False == False -> True## Python(链式): 中间值被使用两次,拆成 and 连接的两个等式# True == False == False# -> True == False and False == False# -> Falseprint(True == False == False) # False赋值运算符
a = 10, b = 20
| 运算符 | 描述 | 实例 |
|---|---|---|
| = | 简单赋值 | c = a + b |
| += | 加法赋值 | c += a 等效于 c = c + a |
| -= | 减法赋值 | c -= a 等效于 c = c - a |
| *= | 乘法赋值 | c *= a 等效于 c = c * a |
| /= | 除法赋值 | c /= a 等效于 c = c / a |
| %= | 取模赋值 | c %= a 等效于 c = c % a |
| **= | 幂赋值 | c **= a 等效于 c = c ** a |
| //= | 取整除赋值 | c //= a 等效于 c = c // a |
位运算符
a = 60 (0011 1100), b = 13 (0000 1101)
| 运算符 | 描述 | 实例 |
|---|---|---|
| & | 按位与 - 两位都为 1 则结果为 1 | a & b 输出 12 (0000 1100) |
| | | 按位或 - 任一位为 1 则结果为 1 | a | b 输出 61 (0011 1101) |
| ^ | 按位异或 - 两位相异则结果为 1 | a ^ b 输出 49 (0011 0001) |
| ~ | 按位取反 - 0 变 1,1 变 0;~x 等价于 -x - 1 | ~a 输出 -61 |
| << | 左移 - 各二进位全部左移指定位数,高位丢弃,低位补 0 | a << 2 输出 240 (1111 0000) |
| >> | 右移 - 各二进位全部右移指定位数 | a >> 2 输出 15 (0000 1111) |
a = 60 # 0011 1100b = 13 # 0000 1101
print("a & b =", a & b) # 12print("a | b =", a | b) # 61print("a ^ b =", a ^ b) # 49print("~a =", ~a) # -61print("a << 2 =", a << 2) # 240print("a >> 2 =", a >> 2) # 15逻辑运算符
a = 10, b = 20
| 运算符 | 逻辑表达式 | 描述 | 实例 |
|---|---|---|---|
| and | x and y | 布尔”与”;返回第一个为假的值,若都为真则返回最后一个值 | a and b 返回 20 |
| or | x or y | 布尔”或”;返回第一个为真的值,若都为假则返回最后一个值 | a or b 返回 10 |
| not | not x | 布尔”非”;x 为 True 返回 False,x 为 False 返回 True | not (a and b) 返回 False |
修订说明: 原笔记中逻辑运算符的”简短描述”与”详细描述”两张表重复,此处合并为一张.
成员运算符
用于判断元素是否在序列(字符串,列表,元组,集合,字典的键)中.
| 运算符 | 描述 | 实例 |
|---|---|---|
| in | 在指定序列中找到值返回 True,否则 False | x in y |
| not in | 在指定序列中没找到值返回 True,否则 False | x not in y |
list_var = [1, 2, 3, 4, 5]print(1 in list_var) # Trueprint(20 not in list_var) # True(注意类型也要匹配)
words = ["apple", "good", "pear"]print("good" in words) # True
# in 同样适用于字符串src = "Good morning, Night City!"print("Kulve" in src) # Falseis 身份运算符
| 运算符 | 描述 | 实例 |
|---|---|---|
| is | 判断两个标识符是否引用同一个对象 | x is y 类似 id(x) == id(y) |
| is not | 判断两个标识符是否引用不同对象 | x is not y 类似 id(x) != id(y) |
is 用于判断两个变量是否引用同一块内存,== 用于判断值是否相等.
a = 20b = 20print(a is b) # 小整数池复用,通常为 Trueb = 30print(a is b) # False关于 is 在列表中的应用:
a = [1, 2, 3, 4, 5]b = a # b 直接指向 aprint(a is b) # True
b = [1, 2, 3, 4, 5] # 新列表,不复用print(a is b) # False
b = a[:] # 切片复制,得到新列表print(a is b) # False运算符优先级
| 运算符 | 描述 |
|---|---|
| ** | 指数(最高优先级) |
| ~ + - | 按位翻转,一元加号和减号 |
| * / % // | 乘,除,取模和取整除 |
| + - | 加法,减法 |
| >> << | 右移,左移 |
| & | 位 AND |
| ^ | | 位异或,位或 |
| <= < > >= | 比较运算符 |
| == != | 等于运算符 |
| = %= /= //= -= += *= **= | 赋值运算符 |
| is is not | 身份运算符 |
| in not in | 成员运算符 |
| not and or | 逻辑运算符 |
2.3 函数
作用: 简化代码,提升复用效率. 函数是一段具有固定功能的代码片段.特点: 先定义后使用;不调用不执行;调用一次执行一次. 组成: 函数名,参数,返回值.
命名规范: 函数名使用全小写字母 + 下划线的方式命名.
标准函数调用
def describe_person(name, age): """描述一个人的基本信息""" return f"{name} 今年 {age} 岁"
print(describe_person("张三", 25)) # 张三 今年 25 岁带有默认形参的函数
默认参数只能放在参数列表最后(默认形参之后不能再跟非默认形参).
def describe_person(name, age=0): """描述一个人的基本信息""" return f"{name} 今年 {age} 岁"
# 未提供 age,使用默认值 0print(describe_person("张三")) # 张三 今年 0 岁修订说明: 原笔记示例代码
describe_person("张三", )的注释称输出”25 岁”,与age=0默认值不符,此处修正为输出 “0 岁”.
可变对象作为默认参数的陷阱
def holypc(xc1=[1, 2, 3]): xc1.append("now") print(xc1)
holypc() # [1, 2, 3, 'now']holypc() # [1, 2, 3, 'now', 'now'] 累加!
# 原因: 默认参数在函数定义时只创建一次,后续调用共享同一个对象.多返回值的函数
def describe_person(name, age=0): return f"{name} 今年 {age} 岁", f"今年 {age} 岁的 {name}"
form1, form2 = describe_person("张三")print(form1, form2)
# 单个变量接收时,得到的是元组result = describe_person("张三")print(result) # ('张三 今年 0 岁', '今年 0 岁的 张三')参数传递
Python 的官方说法是”传对象引用”(pass by object reference).可以理解为传入的是对象的引用,函数内对形参重新赋值不会影响外部变量.
- 不可变对象(
int,float,str,tuple,bool,None): 重新赋值会创建新对象,外部不受影响,看起来像值传递.
def change_number(num): num = 999 print(f"函数内: {num}")
n = 10change_number(n)print(f"调用后: {n}") # 10- 可变对象(
list,dict,set): 在函数内修改原对象会影响外部,看起来像引用传递.
def add_student(students, name): students.append(name)
student_list = ["小明", "小红"]add_student(student_list, "小刚")print(student_list) # ['小明', '小红', '小刚']可变参数(打包为元组 / 字典)
# *args: 接收任意数量的位置参数,打包成元组# **kwargs: 接收任意数量的关键字参数,打包成字典def school_report(school_name, year=2024, *students, **scores): print(f"学校: {school_name}, 年份: {year}") print(f"学生: {students}") print(f"成绩: {scores}")
school_report("第一中学", 2024, "小明", "小红", 数学=95, 英语=88)# 学生: ('小明', '小红')# 成绩: {'数学': 95, '英语': 88}拆箱(解包)
def add(a, b, c): return a + b + c
numbers = [1, 2, 3]print(add(*numbers)) # 6
info = {'name': '李四', 'age': 28, 'city': '上海'}
def introduce(name, age, city): print(f"我是{name}, {age}岁, 来自{city}")
introduce(**info) # 我是李四, 28岁, 来自上海命名参数(关键字参数)
可以通过指定参数名调换顺序,但位置参数必须在最前面.
def school_report(school_name, year=2024): print(f"学校: {school_name}, 年份: {year}")
school_report("第一中学", year=2024)匿名函数 (lambda)
语法: lambda [参数列表]: 表达式
sum_lambda = lambda x, y: x + yprint(sum_lambda(10, 20)) # 30常用于排序的 key:
students = [ {'name': 'Alice', 'age': 20}, {'name': 'Bob', 'age': 18}, {'name': 'Charlie', 'age': 22},]students.sort(key=lambda x: x['age']) # 按 age 升序print(students)2.4 结构
选择
if 分支语法:
if <表达式>: <then 块>[elif <表达式>: <elif 块>][else: <else 块>]修订说明: 原笔记中的伪代码使用了中文全角冒号
:及未对齐的缩进,此处改为标准语法示意.
match 模式匹配
match 在 Python 3.10 中引入(原笔记误写为 3.1.0).
def process_data(data): match data: case 0: return "数字 0" case "hello": return "问候语" case None: return "空值" case (x, y, z): return f"三维坐标 -> x:{x}, y:{y}, z:{z}" case [c, m, y, k]: return f"cmyk -> 青:{c}, 品红:{m}, 黄:{y}, 黑:{k}" case {"name": name, "role": "admin"}: return f"管理员: {name}" case "yes" | "y" | True: return "确认操作" case (x, y) as point: return f"点 {point} 解构为 x={x}, y={y}" case n if isinstance(n, int) and n > 100: return f"大整数: {n}" case _: return "未知类型"
print(process_data(0))print(process_data((10, 20, 30)))print(process_data({"name": "Bob", "role": "admin"}))print(process_data("yes"))case 中可放置: 字面量,序列,字典;也支持 or(多选一),as(整体别名),守卫条件 if.
循环
for <变量> in <可迭代对象>: <for 块>else: <else 块> # for 未被 break 时执行可迭代对象: 字符串,元组,列表,集合,字典.
while <条件>: <while 块>else: <else 块> # while 未被 break 时执行break: 退出当前层循环.continue: 跳过本次循环,进入下一轮.
for i in range(10): if i == 5: break print(i, end=" ")print() # 0 1 2 3 4
for i in range(10): if i % 2 == 0: continue print(i, end=" ") # 1 3 5 7 9print()
# for-else: 未被 break 时执行 elsefor i in range(3): print(i)else: print("循环正常结束")注意:
for循环结束后,循环变量不会被清空,会保留最后一次的值.
异常处理
支持自定义异常.基本语法:
try: < try 块 >except < 异常类型 > as < 变量名 >: < 异常处理块 > # 从上到下匹配,匹配一个后退出 except 链else: < else 块 > # 未发生异常时执行finally: < finally 块 > # 无论是否异常都会执行class MegaTreeException(Exception): pass
def divide(a, b): try: result = a / b print(f"{a} / {b} = {result}") if a == 10: raise ValueError("a == 10") if b == 10: raise MegaTreeException("Woo, you got a mega seed.") except ZeroDivisionError as e: print(f"除数为0: {e}") except MegaTreeException as e: print(f"MegaTree: {e}") except ValueError as e: print(f"ValueError: {e}") except Exception as e: print(f"其它异常: {e}") else: print("计算成功,无异常") finally: print("finally 块执行")
divide(10, 2)divide(10, 0)divide(9, 10)2.5 类
类是创建实例的模板,定义了一组属性(数据)和方法(操作数据的行为).
命名规范: 类名使用大驼峰(PascalCase);对应类文件使用全小写字母 + 下划线命名.
基本组成
一个 Python 类通常包含:
- 类属性
- 构造方法
__init__ - 实例方法
- 类方法(
@classmethod) - 静态方法(
@staticmethod) - 特殊(魔术)方法
class Person: # 类属性(属于类本身,所有实例共享) species = "Human"
def __init__(self, name: str, age: int): self.name = name # 实例属性 self.age = age
def introduce(self): # 实例方法 return f"我叫{self.name},今年{self.age}岁"
@classmethod def change_species(cls, new_species: str): # 类方法 cls.species = new_species
@staticmethod def is_adult(age: int) -> bool: # 静态方法 return age >= 18
def __str__(self): # 魔术方法 return f"{self.name}({self.age})"
def __eq__(self, other): return self.name == other.name and self.age == other.age基本用法
class Person: def __init__(self, name): self.name = name
def get_name(self): return self.name
p = Person("alice")print(p.get_name()) # alice说明: 类中的方法第一个参数必须是
self(约定名,不可省略),代表实例本身.普通方法调用时解释器自动传入.
类属性与实例属性
- 类属性: 所有对象共享,通过
<类名>.<属性>或<对象>.<属性>访问. - 实例属性: 各对象相互隔离,通过
self.<属性>或<对象>.<属性>访问.
类方法 @classmethod
使用 @classmethod 装饰,第一个参数为 cls(代表类本身,子类调用时自动传入子类).
class Person: school = "第一中学"
def __init__(self, name: str): self.name = name
@classmethod def change_school(cls, new_name: str): cls.school = new_name
print(Person.school) # 第一中学Person.change_school("实验中学")print(Person.school) # 实验中学p = Person("小明")p.change_school("外国语中学")print(p.school) # 外国语中学工厂方法示例(类方法创建实例)
class Person: def __init__(self, name): self.name = name
def get_name(self): return self.name
@classmethod def create_anonymous(cls): return cls("匿名")
p = Person.create_anonymous()print(p.get_name()) # 匿名静态方法 @staticmethod
使用 @staticmethod 装饰,没有 self / cls 参数,不能访问类或实例属性,一般作为工具函数使用.
class Person: @staticmethod def is_adult(age: int) -> bool: return age >= 18
print(Person.is_adult(20)) # Trueprint(Person.is_adult(15)) # False类方法 vs 静态方法
- 类方法: 适合需要访问 / 修改类状态的操作(如工厂模式创建不同形式的实例).
- 静态方法: 适合与类相关但不依赖类状态的独立工具函数(如数据校验,格式转换).
类方法在继承中的多态
子类继承父类的类方法时,cls 会指向子类而非父类.
class People: def __init__(self, name): self.name = name
@classmethod def create(cls, name): print(f"cls 的类型为 {cls.__name__}") return cls(name)
class Student(People): def study(self): print(f"{self.name} 正在学习")
stu = Student.create("小明") # cls = Studentstu.study()封装 @property
封装: 将属性与方法放到类内部,对外只暴露必要接口,隐藏实现细节并保护数据.
- 保护属性: 单下划线
_name,约定外部不直接访问. - 私有属性: 双下划线
__id,触发名称重整,类外部可通过_类名__id访问(但应避免).
@property: 将方法变为”只读属性”,调用时无需加括号.
@<属性名>.setter: 声明可写属性的 setter,用于带校验地修改值.
class Person: def __init__(self, name, age): self._name = name # 保护属性 self.__age = age # 私有属性(重整为 _Person__age) self.__id = None
@property def name(self): return self._name
@name.setter def name(self, value): if not value or not isinstance(value, str): raise ValueError("姓名必须是非空字符串") self._name = value.strip()
@property def age(self): return self.__age
@age.setter def age(self, value): if not isinstance(value, int) or value < 0: raise ValueError("年龄必须是正整数") self.__age = value
@property def id(self): return self.__id
def set_id(self, id_value): if self.__id is not None: raise ValueError("ID 已设置,不能修改") self.__id = id_value
p = Person("张三", 25)p.name = "李四"p.age = 30p.set_id("P001")print(p.name, p.age, p.id)修订说明: 原笔记中私有属性示例混用了
_name,____age(四个下划线),__id三种写法,且名称重整说明与实际代码不符.此处统一为_name(保护)与__age(私有,重整为_Person__age).
继承 - 单继承
语法: class <子类名>(<父类名>):
Python 支持多继承,单继承只是父类列表中只有一个类.
继承 = 复用 + 扩展 + 改写:
- 复用: 子类直接拥有父类属性和方法.
- 扩展: 子类在父类基础上增加新功能.
- 改写: 子类重写父类方法.
调用父类方法有两种方式:
<父类名>.<方法名>(self, <参数>): 需手动传self,适用于多继承.super().<方法名>(<参数>): 自动传self,适用于单继承.
class Person: def __init__(self, name): self.name = name
def introduce(self): return f"我是 {self.name}"
class Student(Person): def __init__(self, name, grade): super().__init__(name) self.grade = grade
def introduce(self): # 重写父类方法 return f"我是 {self.name},是一名学生,今年 {self.grade} 年级"
def study(self): return f"{self.name} 正在学习中..."
s = Student("小明", 3)print(s.introduce())print(s.study())继承 - 多继承与 MRO
Python 支持一个子类继承多个父类.若父类有同名方法,按 MRO(方法解析顺序)就近原则调用.
class Person: def __init__(self, name): self.name = name
def introduce(self): return f"我是 {self.name},是人"
class Student(Person): def __init__(self, name, grade=1): Person.__init__(self, name) self.grade = grade
class Teacher(Person): def __init__(self, name, subject="未知"): Person.__init__(self, name) self.subject = subject
class Assistant(Student, Teacher): def __init__(self, name, grade, subject): super().__init__(name) # 按 MRO: Assistant -> Student -> Teacher -> Person -> object self.grade = grade self.subject = subject
a = Assistant("小张", 4, "化学")print(a.introduce())print([c.__name__ for c in Assistant.__mro__])# ['Assistant', 'Student', 'Teacher', 'Person', 'object']补充: MRO 由 C3 线性化算法计算.本例结果为
Assistant -> Student -> Teacher -> Person -> object.多继承结构复杂,易产生命名冲突,使用super()可避免硬编码父类名.
多态
作用: 同一接口在不同对象上有不同实现(同一函数,不同功能).
实现多态三条件: 继承,方法重写,父类引用指向子类对象.
class Animal: def speak(self): print("叫了一声")
class Dog(Animal): def speak(self): print("汪汪")
class Cat(Animal): def speak(self): print("喵喵")
def let_speak(animal: Animal): animal.speak()
let_speak(Dog())let_speak(Cat())鸭子类型 (Duck Typing)
不关心对象类型,只关心它有没有对应的方法.
class Person: def speak(self): return "大家好"
class Parrot: def speak(self): return "呱呱!"
def morning_greeting(obj): print(obj.speak())
morning_greeting(Person()) # 大家好morning_greeting(Parrot()) # 呱呱!抽象基类 @abstractmethod
抽象: 忽略无关细节,只关注关键特征.抽象基类(ABC)强制子类必须实现指定的抽象方法.
from abc import ABC, abstractmethod
class AC(ABC): @abstractmethod def cold(self): pass
@abstractmethod def hot(self): pass
class Mi(AC): def cold(self): print("小米: 冷风")
def hot(self): print("小米: 热风")
mi = Mi()mi.cold()抽象基类不能被直接实例化;若子类未实现全部抽象方法,实例化时会抛出
TypeError.
魔术方法
以双下划线开头和结尾的方法,由解释器在特定场景自动调用(如实例化,打印,比较,运算).不推荐自行声明新的魔术方法,以免与未来 Python 版本冲突.
| 魔术方法 | 触发场景 | 典型用途 |
|---|---|---|
__init__ | obj = Class() | 初始化对象属性(构造函数) |
__str__ | print(obj) | 定义面向用户的显示文本,必须返回字符串 |
__len__ | len(obj) | 定义对象长度 |
__del__ | 对象被删除 / 重新绑定 / 程序结束 | 释放资源,关闭连接等 |
class Bird: def __init__(self, name): self.name = name
def __del__(self): print(f"Bird is dead@{self.name}")
bird = Bird("麻雀")bird = Bird("鹦鹉") # "麻雀" 实例立即被回收,__del__ 执行bird = Bird("鸽子")print("End")类型判断
isinstance(obj, cls): 考虑继承关系,子类实例也视为父类的实例.type(obj) == cls: 精确比较,不考虑继承.
class People: pass
class Student(People): pass
s = Student()print(isinstance(s, People)) # Trueprint(type(s) == People) # Falseprint(type(s) == Student) # True属性遮蔽
访问对象属性时遵循顺序: 实例属性 -> 类属性 -> 父类属性 -> … -> 找不到则抛 AttributeError.
class Demo: k = 100
d = Demo()print(d.k) # 100(类属性)d.k = 999 # 添加实例属性,遮蔽类属性print(d.k) # 999print(Demo.k) # 100(类属性不受影响)del d.kprint(d.k) # 100(恢复访问类属性)self 关键字
self 指向实例本身,存储的是实例的地址.类内部调用本类方法需通过 self.
class Person: def get_self(self): print(f"id(self): {id(self)}") self.say_hello()
def say_hello(self): print("hello")
p = Person()print(f"id(p): {id(p)}")p.get_self()面向对象程序设计 (OOP)
概念: 以对象为核心组织代码,把数据与操作数据的行为封装在一起,通过对象交互解决问题.组成主要是类与对象.
与面向过程的区别:
- 面向过程: 关注步骤,以函数 / 步骤为中心.适合简单逻辑,一次性脚本,代码简洁.
- 面向对象: 关注对象,把职责与关系划分清楚,适合复杂系统,多人协作,易于扩展.
2.6 文件操作
open 基本语法
相对路径的基准是当前工作目录(pwd).
f = open( < 路径 >, < 读写模式 >, encoding = < 编码 >)content = f.read()f.write( < 内容 >)f.close()注意: 以
w模式打开文件时,会立即清空原内容(而非写入前清空).
with 基本语法
with 会在代码块结束后自动关闭文件.
with open( < 路径 >, < 读写模式 >, encoding= < 编码 > ) as f: f.read()# 离开 with 块后,文件自动关闭seek() 与 tell()
seek 移动文件指针,tell 获取当前指针位置(以字节计).
with open("./test.txt", "rb") as f: content = f.read(3) # 读取 3 个字节 print(content.decode("utf-8")) print(f"当前位置 {f.tell()}") f.seek(1) # 移动指针 print(f.read(30).decode("utf-8"))注意: 文本模式下
seek只能移动到由tell()返回的位置或文件开头,移动到字符中间会破坏 UTF-8 边界导致解码失败.
read()
- 二进制模式: 读取
bytes,需用decode()解码;给定长度为字节数. - 文本模式: 按字符读取;给定长度为字符数.
- 不传参数: 一次性读取整个文件.
with open("./test.txt", "r", encoding="utf-8") as f: print(f.read(2)) # 读取 2 个字符readline() 与 readlines()
readline(): 读取一行;给定n时最多读该行前n个字符 / 字节.readlines(): 读取所有行,返回列表,每行为一个元素.
write()
with open("./out.txt", "w", encoding="utf-8") as f: f.write("Hello, world!\n") f.write("第二行")2.7 生成器
生成器是一种惰性迭代器,按需生成值,而非一次性返回所有值,从而节省内存.
关键特征:
- 函数中一定有
yield <表达式>. - 只能遍历一次.
- 惰性求值,用到时才生成下一个值.
执行到 yield 时函数暂停并返回值;下次 next() 时从 yield 处继续(不是重新执行).
def count(): for i in range(10): yield i # 暂停并返回 i
gen = count()print(next(gen)) # 0print(next(gen)) # 1for i in gen: print(i) # 2..9三. 常用内置函数与方法
3.1 print 方法
基本语法
print( < 要打印的内容 >)扩展用法
def demo_print(name, age): a, b = 10, 20 print(a, b, "标准打印") # 多个对象用空格分隔 print(a, b, sep=" | ") # 指定分隔符 print(a, b, end="") # 不换行
# 格式化方式一: f-string(推荐) print(f"Name:{name}, Age:{age}") print(f"pi:{3.1415926:.2f}") # 保留两位小数 print(f"{123:05d}") # 00123(0 填充至长度 5) print(f"{123:<15}") # 左侧对齐,右侧补空格至长度 15
# 格式化方式二: str.format() print("Name:{}, Age:{}".format(name, age)) print("Age:{1}, Name:{0}".format(name, age)) # 按位置
# 格式化方式三: % 格式化(类 C) print("Name:%s, Age:%d" % (name, age))
demo_print("张三", 17)注意: 严禁用 f-string 拼接 SQL,应使用参数化查询.输出
{}时需转义为{{}}.
3.2 range() 方法
for i in range(5): # 0 1 2 3 4 print(i, end=" ")
for i in range(10, 15): # 10 11 12 13 14 print(i, end=" ")
for i in range(0, 10, 2): # 0 2 4 6 8 print(i, end=" ")
for i in range(5, 0, -1): # 5 4 3 2 1 print(i, end=" ")3.3 json 方法
需导入 json 模块.
json.dump(<对象>, <文件对象>, ensure_ascii=False): 序列化到文件.json.load(<文件对象>): 从文件反序列化为 Python 对象.
import json
class User: def __init__(self, username, password, age, confirmed): self.username = username self.password = password self.age = age self.confirmed = confirmed
user1 = User("用户1", "pwd1", 10, True)with open("user_info.txt", "w", encoding="utf-8") as f: # ensure_ascii=False 保留中文,不转义为 \uXXXX json.dump(user1.__dict__, f, ensure_ascii=False)
with open("user_info.txt", "r", encoding="utf-8") as f: data_dict = json.load(f)
user2 = User(**data_dict) # 用 ** 解包字典为构造参数print(user2.__dict__)3.4 input() 方法
x = input("输入一个数字:") # 返回值永远是 strprint(type(x)) # <class 'str'>3.5 copy() 与 deepcopy() 方法
copy.copy(): 浅拷贝,只拷贝第一层;修改嵌套可变对象会影响原对象.copy.deepcopy(): 深拷贝,嵌套对象一并复制;修改嵌套对象不影响原对象.
import copy
new_obj = copy.copy(src_obj) # 浅拷贝new_obj = copy.deepcopy(src_obj) # 深拷贝四. 模块
本质是一个 .py 文件.使用前需先导入.
导入语法
import < 模块名 > # 调用: 模块名.函数名import < 模块名 > as < 别名 > # 调用: 别名.函数名from < 模块名 >import < 功能名1 >, < 功能名2 > # 直接调用功能名from < 模块名 >import * # 导入所有公开功能模块分类
- 自定义模块: 开发者自己编写的
.py文件. - 内置模块: 如
math,random,os,直接导入即用. - 第三方模块: 需安装,如
pip install <模块名>,可加-i <镜像源>使用国内镜像(清华,阿里云).
内置变量 __name__
模块被直接运行时 __name__ 为 "__main__";被导入时 __name__ 为模块名.常用 if __name__ == '__main__': 保护仅在直接运行时的代码.
import main2 as lib
print(f"main.py: __name__ -> {__name__}")print(lib.get_add(1, 2))def get_add(a, b): return a + b
def get_sub(a, b): return a - b
print(f"main2.py: __name__ -> {__name__}") # 导入时也会执行五. 闭包与装饰器
闭包
闭包 = 函数 + 函数定义时的外部环境状态.作用: 保存函数状态,保护私有变量.
def make_counter(): count = 0
def counter(): nonlocal count # 声明修改外部变量,避免被销毁 count += 1 return count
return counter
c = make_counter()print(c()) # 1print(c()) # 2装饰器
装饰器本质上是一个闭包函数,可在不修改原函数代码的前提下增加功能.常用语法糖 @decorator.
def check_username(func): def inner(): print("校验用户名1") func() print("校验用户名2")
return inner
def check_password(func): def inner(): print("校验密码1") func() print("校验密码2")
return inner
@check_username@check_passworddef login(): print("登录")
login()# 输出:# 校验用户名1# 校验密码1# 登录# 校验密码2# 校验用户名2调用顺序: 装饰器从下往上包裹,执行时类似栈——先进入外层,再进入内层,最后回到外层.等价于
check_username(check_password(login))().
六. 网络编程
socket
包名: socket.注意: 发送 / 接收的数据必须是 bytes 类型(用 .encode() / .decode() 转换).
服务端
步骤: 创建 socket -> 绑定 IP 与端口 -> 监听 -> 接受连接 -> 收发数据 -> 关闭.
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.bind(('127.0.0.1', 8080))server.listen(5)client, addr = server.accept()client.send("Hello, World!".encode("utf-8"))print(client.recv(1024).decode("utf-8"))client.close()客户端
步骤: 创建 socket -> 连接服务器 -> 收发数据 -> 关闭.
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)client.connect(('127.0.0.1', 8080))print(client.recv(1024).decode("utf-8"))client.send("Hello, World!".encode("utf-8"))client.close()七. 并发编程(多进程,多线程,协程)
7.1 多进程
包名: multiprocessing.进程间数据不共享,子进程启动时重新初始化全局变量.
基本用法
import timeimport multiprocessing
def task(): time.sleep(1) print(f"子进程: {multiprocessing.current_process().name}")
if __name__ == '__main__': p = multiprocessing.Process(target=task) p.start() p.join() # 等待子进程结束主进程与子进程协同
- 守护进程:
p.daemon = True(启动前设置),主进程结束时子进程立即结束. p.terminate(): 强制终止子进程(启动后,不推荐,可能内存泄漏).p.join(): 主进程阻塞,等待子进程结束.
获取进程号 PID
import osimport multiprocessing
def task(): print(f"PID={os.getpid()}, PPID={os.getppid()}")
if __name__ == '__main__': p = multiprocessing.Process(target=task) p.start() p.join()补充: 多进程间共享状态需使用
multiprocessing提供的队列,管道或Value/Array等机制(原笔记”加锁” 小节内容空缺,锁主要用于多线程;跨进程同步可用multiprocessing.Lock).
7.2 多线程
包名: threading.线程间共享全局变量,存在资源竞争,需用互斥锁.
基本用法
import threadingimport time
def coding(): for i in range(10): print(f"写第{i + 1}行代码") time.sleep(0.1)
if __name__ == '__main__': t1 = threading.Thread(target=coding) t2 = threading.Thread(target=coding) t1.start() t2.start() t1.join() t2.join()互斥锁解决资源竞争
import threading
g_num = 0mutex = threading.Lock()
def work(): mutex.acquire() global g_num for _ in range(1000000): g_num += 1 print(g_num) mutex.release()
t1 = threading.Thread(target=work)t2 = threading.Thread(target=work)t1.start()t2.start()7.3 协程
包名: asyncio.用户态轻量级并发单元,适合 IO 密集型任务;单线程运行,无线程竞争,无需加锁.
import asyncioimport time
async def rap(username): print(f"{username} 开始") await asyncio.sleep(1.5) # 模拟 IO 等待 print(f"{username} 结束")
async def app(): # gather 并发运行多个协程并收集结果 results = await asyncio.gather(rap("李四"), rap("王五")) print(results)
if __name__ == '__main__': start = time.time() asyncio.run(app()) print(f"耗时: {time.time() - start:.2f}s") # 约 1.5s7.4 多进程事件 (multiprocessing.Event)
Event 用于多进程间状态通知,降低 CPU 占用并支持状态广播.状态值只有 True / False.
import multiprocessingimport timeimport os
def worker(stop_event, worker_id): while not stop_event.wait(timeout=1): # 等待停止信号,超时返回 False print(f"进程 {worker_id} 正在干活") print(f"进程 {worker_id} 收到停止信号")
if __name__ == '__main__': stop_event = multiprocessing.Event() processes = [multiprocessing.Process(target=worker, args=(stop_event, i)) for i in range(3)] for p in processes: p.start() time.sleep(3) stop_event.set() # 广播停止信号 for p in processes: p.join()关键方法
event.wait(timeout=None): 阻塞等待,被set()返回True;超时未被 set 返回False;无超时则一直阻塞.event.is_set(): 非阻塞,已被 set 返回True,否则False.event.set(): 将事件设为True.event.clear(): 将事件设为False.
7.5 多进程队列 (multiprocessing.Queue)
Queue用于多进程间安全地传递消息.
因为多个进程之间是不共享全局变量的,需要使用该组件进行通信
队列是先进先出的,同时默认也是阻塞式存取的,进程安全.
import multiprocessingimport time
def producer(q): for i in range(10): print(q.empty()) time.sleep(0.1) q.put(f"产品-{i}") print(f"生产了: 产品-{i}") print(q.full()) print(q.qsize())
def consumer(q): while True: item = q.get() if item is None: # 用 None 作为结束信号 break print(f"消费了: {item}") time.sleep(1)
if __name__ == "__main__": q = multiprocessing.Queue() # 最多放3个,满了会阻塞 # Queue的常用方法 # q.put(<object>): 存入一个对象 # q.get(): 取出并消费一个对象 # q.empty(): 队列为空 # q.full(): 队列已满 # q.qsize(): 获取当前队列中元素数量 # q.close(): 关闭队列,无法存入只能消费. 必须在所有生产者进程都完成后调用,否则报错: 队列已关闭 p1 = multiprocessing.Process(target=producer, args=(q,)) p2 = multiprocessing.Process(target=consumer, args=(q,))
p1.start() p2.start()
p1.join() q.put(None) # 发送结束信号 p2.join()关键方法
q.put(<object>): 存入一个对象.q.get(): 取出并消费一个对象.q.empty(): 队列大致为空,不安全方法.q.full(): 队列大致已满,不安全方法.q.qsize(): 获取当前队列中大致元素数量,不安全方法q.close(): 关闭队列,无法存入只能消费. 必须在所有生产者进程都完成后调用,否则报错: 队列已关闭.
方法详细说明
Queue.put(<object>[,block=True[,timeout=<超时时间>]]): 阻塞式存入
- block: True-默认,阻塞式存入. False-非阻塞式存入,若队列已满且在超时时间内未能存入,则报错.
- timeout: 超时时间,秒.
Queue.put_nowait(<object>): 非阻塞式存入,若队列已满直接报错.
Queue.get(<object>[,block=True[,timeout=<超时时间>]]):
- block: True-默认,阻塞式取出. False-非阻塞式取出,若队列为空且在超时时间内未能取出,则报错.
- timeout: 超时时间,秒.
Queue.get_nowait(): 非阻塞式获取,若队列为空则报错.
7.6 进程池
进程池是一个基于消息传递的,带有任务队列和结果队列的进程管理器.
为什么需要它?
- 节省运算资源,减少上下文切换开销.
- 自动管理生命周期.
- 安全取回结果.
- 负载均衡.
基本语法
import multiprocessingimport time
def square(x): time.sleep(0.5) # 模拟耗时计算 print(x) return x * x
if __name__ == "__main__": # 使用with创建一个进程池,processes=4,表示最多同时执行4个进程 # 使用with语句可以自动调用close()和join()方法,避免忘记调用close()和join()方法导致程序阻塞 with multiprocessing.Pool(processes=4) as pool: results = pool.map(square, range(10)) print(results) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# with 语句结束自动调用 close() + join()几个重要的方法介绍,分为三组介绍
apply与apply_async
一次性提交一个任务.
import multiprocessingimport time
def task(x): time.sleep(0.3) return x
if __name__ == "__main__": with multiprocessing.Pool(2) as pool: # apply一次只提交一个任务. # apply 提交任务: 阻塞式提交,与单进程没有区别 i = 0 for i in range(5): res = pool.apply(task, (i,)) print(res)
# apply_async 异步提交任务: 非阻塞式提交,可以利用多进程 # 但是获取任务结果时,会阻塞. task_list = [] i = 0 for i in range(10): res = pool.apply_async(task, (i,)) task_list.append(res) i = 0 for i in task_list: # 阻塞式获取结果 print(i.get())map与map_async
“块”式提交,参数列表是一组参数,每个线程将会得到其中的一个元素作为参数传入.
结果为一个列表,每个进程运算的结果将会是该列表中的一个元素
pool.map_async(<任务对象>,<参数列表>[, callback=<回调对象>])<支持回调>支持回调>,这是最大的特点,当任务全部执行完成后,任务对象的结果集将会被当作参数传递进回调对象中
import multiprocessingimport time
def task(x): time.sleep(1) return x
def callback_func(x): print(f"回调函数执行:{x}")
if __name__ == "__main__": with multiprocessing.Pool(5) as pool: # map可以理解为是一个块式提交 # 提交任务为一个块,那么取回的结果也是一个块 # 块在多进程池中被拆分为多个进程,同时运算,有点类似于GPU
# map 提交任务: 阻塞式提交,可以利用多进程 # 结果为一个列表,包含了所有进程的结果 res = pool.map(task, range(10)) print(res)
# map_async 异步提交任务: 非阻塞式提交,可以利用多进程 # 支持回调,任务完成后,会将task的运行结果整体一次性传递到callback_func的参数中,在主进程中处理 # 获取任务结果时,会阻塞.
res = pool.map_async(task, range(20), callback=callback_func) print("等待结果中.") print(res.get())imap与imap_unordered
用于一次性存入多个任务,与map差不多,但取回结果时是返回一个迭代器.对迭代器进行遍历可以获取所有进程的执行结果.
pool.imap_unordered(<任务对象>,<参数列表>): 不保证顺序.
import multiprocessingimport time
def task(x): time.sleep(1) return x
if __name__ == "__main__": with multiprocessing.Pool(5) as pool: # imap与imap_unordered都会返回一个迭代器 # 对迭代器遍历会导致阻塞,直到迭代器为空 # 两者区别正如名字所见: # imap 是有序的,会按照任务的提交顺序返回结果 # imap_unordered 是无序的,会按照任务的完成顺序返回结果 result_iter = pool.imap(task, range(50)) for value in result_iter: # 逐个产出结果,算完一个拿一个 print(value)
result_iter = pool.imap_unordered(task, range(50)) for value in result_iter: # 逐个产出结果,算完一个拿一个 print(value)支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!






















