Python 全栈系统学习笔记(超详细版)
说明:本文档基于系统性 Python 学习笔记整理,覆盖从基础语法到高级特性、从文件操作到网络编程、从数据结构到常用算法的全部核心知识。每个知识点均配有详细文字描述 + 完整可运行代码示例,并补充了原笔记缺失的重要模块。适合初学者系统复习,也适合进阶者查漏补缺。
目录
- 第一章 Python 概述与环境搭建
- 第二章 基础语法
- 第三章 流程控制
- 第四章 字符串详解
- 第五章 列表详解
- 第六章 元组详解
- 第七章 字典详解
- 第八章 集合详解
- 第九章 推导式
- 第十章 函数基础
- 第十一章 函数进阶
- 第十二章 文件操作
- 第十三章 异常处理
- 第十四章 模块与包
- 第十五章 面向对象基础
- 第十六章 面向对象进阶
- 第十七章 高级特性(闭包、深浅拷贝、装饰器)
- 第十八章 网络编程
- 第十九章 多进程
- 第二十章 多线程
- 第二十一章 迭代器与生成器
- 第二十二章 Property 属性
- 第二十三章 正则表达式
- 第二十四章 常用内置函数补充(map、filter、zip、enumerate)
- 第二十五章 数据结构与算法基础
- 第二十六章 排序算法大全
- 第二十七章 查找算法
- 第二十八章 链表
- 第二十九章 二叉树
- 第三十章 栈与队列
第一章 Python 概述与环境搭建
1.1 Python 的优点
Python 是一种高级、解释型、通用型编程语言,由 Guido van Rossum 于 1991 年发布。它的核心设计理念是 "优雅"、"明确"、"简单"。
应用极其广泛:在 TIOBE 编程语言排行榜中常年位居前三,是以下领域的首选语言:
- 人工智能与机器学习:TensorFlow、PyTorch、Scikit-learn 等主流框架均基于 Python。
- 大数据分析:Pandas、NumPy、Dask 等库让数据处理变得高效。
- 数据分析与可视化:Matplotlib、Seaborn、Plotly 提供强大的可视化能力。
- 自动化运维:Ansible、SaltStack 等运维工具基于 Python 开发。
- 自动化测试:Selenium、Pytest、Robot Framework 等测试框架广泛使用。
- Web 开发:Django(全栈框架)、Flask(轻量框架)、FastAPI(高性能异步框架)。
- 爬虫开发:Scrapy、Requests、BeautifulSoup 构成爬虫生态。
代码极其简洁:Python 的语法接近自然语言,同样的功能,Python 代码量通常只有 Java 的 1/5、C++ 的 1/10。这使得开发效率极高,代码可读性极强。
# Python 实现:读取文件并统计行数
with open('test.txt', 'r') as f:
lines = f.readlines()
print(f"文件共有 {len(lines)} 行")
1.2 Python 的缺点
执行速度较慢:Python 是解释型语言,代码在运行时逐行解释为机器码,不像 C/C++ 那样提前编译为二进制文件,因此执行速度相对较慢。
弥补方式:
- 使用 C 扩展(如 Cython)将性能瓶颈部分用 C 重写。
- 使用 PyPy 解释器(JIT 即时编译,速度可提升数倍)。
- 使用 NumPy、Pandas 等底层用 C 实现的库处理大量数据。
- 借助硬件性能提升(更多 CPU 核心、更大内存)。
1.3 Python 解释器
作用:Python 解释器是将 Python 代码翻译成计算机能理解的机器码并执行的程序。它是人与计算机之间的"翻译官"。
与 PyCharm 的关系:二者完全独立。PyCharm 是一个集成开发环境(IDE),本质上是一个功能强大的代码编辑器,它调用系统中安装的 Python 解释器来运行代码。没有 PyCharm,解释器照样可以运行 .py 脚本文件;没有解释器,PyCharm 无法执行任何 Python 代码。
# 在终端中直接运行 Python 脚本,不依赖任何 IDE
python hello.py
1.4 安装 Python
- 1.访问 Python 官网 下载对应操作系统的安装包。
- 2.安装时 务必勾选 "Add Python to PATH",否则命令行无法直接使用
python命令。 - 3.安装完成后打开终端验证:
python --version
# 输出类似:Python 3.11.4
# 或者
python3 --version
1.5 第一个 Python 程序
# hello.py
print("Hello, Python!") # print() 是Python内置的输出函数
print("我开始学习Python了!")
第二章 基础语法
2.1 注释
注释是写给程序员看的文字,Python 解释器会忽略注释内容。合理使用注释可以大幅提高代码可读性。
单行注释:以 # 开头,# 后面的所有内容都会被忽略。
多行注释(文档字符串):用三个连续的单引号 ''' 或三个连续的双引号 """ 包裹的多行文本。当多行注释出现在函数或类的声明下方时,它被称为 文档字符串(docstring),可以通过 函数名.__doc__ 查看。
快捷键:在 PyCharm、VS Code 等主流 IDE 中,选中代码后按 Ctrl + / 可快速添加或取消注释。
# 这是一个单行注释
print("Hello") # 这也是单行注释,# 后面的内容被忽略
"""
这是多行注释的第一行
这是多行注释的第二行
可以写很多行
"""
def add(a, b):
"""
这是文档字符串,用于说明函数的功能。
参数:
a: 第一个加数
b: 第二个加数
返回:
两个数的和
"""
return a + b
print(add.__doc__) # 查看文档字符串内容
2.2 变量
变量是内存中一块存储空间的名称,用于存储数据。Python 中的变量不需要声明类型,赋值时自动确定类型。
2.2.1 命名规则(必须遵守)
- 1.变量名由 字母、数字、下划线 组成。
- 2.不能以数字开头(
1name非法,name1合法)。 - 3.大小写敏感:
name、Name、NAME是三个不同的变量。 - 4.不能使用 Python 关键字(如
if、for、class、def等),在 IDE 中关键字通常会以特殊颜色高亮显示,不是白色的。
# 合法的变量名
name = "Alice"
age2 = 25
_private = "私有变量"
userName = "Bob" # 小驼峰
user_name = "Charlie" # 蛇形命名法(Python推荐)
UserName = "Dave" # 大驼峰(常用于类名)
# 非法的变量名
# 1name = "error" # 数字开头
# my-name = "error" # 含减号
# class = "error" # 关键字
2.2.2 命名规范(建议遵守)
- 简洁明了:
user_name优于un,max_retry_count优于mrc。 - 见名知意:看到变量名就能大致猜到它存储的是什么数据。
- Python 官方推荐蛇形命名法(
snake_case):全小写,单词间用下划线连接,用于变量名和函数名。
2.2.3 变量的定义与使用
# 定义变量:变量名 = 值
name = "Alice"
age = 25
height = 1.75
is_student = True
# 使用变量
print(name) # 输出: Alice
print(age) # 输出: 25
print(type(age)) # 输出: <class 'int'>,type() 查看变量类型
print(type(height)) # 输出: <class 'float'>
print(type(is_student)) # 输出: <class 'bool'>
# 变量可以重新赋值,类型也会改变
age = "二十五" # age 从 int 变成了 str
print(type(age)) # <class 'str'>
2.3 数据类型
Python 是动态类型语言,变量不需要提前声明类型,赋值时自动推断。
| 类型 | 关键字 | 示例 | 说明 |
|---|---|---|---|
| 整型 | int | a = 10 | 整数,无大小限制 |
| 浮点型 | float | b = 3.14 | 带小数点的数字 |
| 字符串 | str | c = "hello" | 文本数据,用引号包裹 |
| 布尔型 | bool | d = True | 只有 True 和 False 两个值 |
| 空值 | NoneType | e = None | 表示"空"或"无" |
a = 100 # int
b = 3.14159 # float
c = "Python" # str
d = True # bool
e = None # NoneType
print(type(a), type(b), type(c), type(d), type(e))
# <class 'int'> <class 'float'> <class 'str'> <class 'bool'> <class 'NoneType'>
2.4 类型转换
Python 提供了内置函数用于在不同数据类型之间进行转换。
# int() 转换为整数
print(int("123")) # 123(字符串转整数)
print(int(3.99)) # 3(浮点转整数,直接舍去小数部分,不是四舍五入)
print(int(True)) # 1(True->1, False->0)
# print(int("12.3")) # 报错!含小数点的字符串不能直接转int
# float() 转换为浮点数
print(float("3.14")) # 3.14
print(float(10)) # 10.0
# str() 转换为字符串
print(str(100)) # "100"
print(str(3.14)) # "3.14"
print(str(True)) # "True"
# bool() 转换为布尔值
# 以下值转换为 False:0, 0.0, "", None, 空容器([], {}, ())
# 其余所有值转换为 True
print(bool(0)) # False
print(bool("")) # False
print(bool([])) # False
print(bool(None)) # False
print(bool(1)) # True
print(bool("Hi")) # True
print(bool([1, 2])) # True
# eval() —— 危险但强大:将字符串当作Python表达式执行
print(eval("3 + 4")) # 7
print(eval("'Hello' * 3")) # HelloHelloHello
# 注意:绝对不要对不可信的输入使用 eval(),可能导致代码注入攻击!
2.5 输出(格式化)
Python 提供了三种主流的字符串格式化方式。
方式一:百分号 % 格式化(C 语言风格)
name = "Alice"
age = 25
height = 1.756
# %s —— 字符串
# %d —— 整数
# %f —— 浮点数
print("姓名: %s, 年龄: %d" % (name, age))
# %06d —— 用0补齐到6位
print("编号: %06d" % 123) # 编号: 000123
# %6d —— 用空格补齐到6位
print("编号: %6d" % 123) # 编号: 123
# %.2f —— 保留2位小数
print("身高: %.2f" % height) # 身高: 1.76
方式二:str.format() 方法
print("姓名: {}, 年龄: {}".format(name, age))
print("姓名: {0}, 年龄: {1}, 再说一次姓名: {0}".format(name, age))
print("姓名: {n}, 年龄: {a}".format(n=name, a=age))
方式三:f-string(推荐,Python 3.6+)
f-string 是最简洁、最直观的方式,支持在 {} 中直接写表达式。
print(f"姓名: {name}, 年龄: {age}")
print(f"明年年龄: {age + 1}")
print(f"身高: {height:.2f}") # 保留2位小数
print(f"Pi的值: {3.1415926:.4f}") # 3.1416
2.6 输入
input() 函数用于从键盘接收用户输入。程序执行到 input() 时会 暂停等待,直到用户按下回车键才继续执行。无论用户输入什么,input() 返回的值始终是字符串类型。
# 基本用法
name = input("请输入您的姓名:")
print(f"您好, {name}!")
# 需要数字时,必须手动转换类型
age_str = input("请输入您的年龄:") # 返回的是字符串
age = int(age_str) # 转换为整数
print(f"明年您 {age + 1} 岁")
2.7 运算符
2.7.1 算术运算符
| 运算符 | 名称 | 示例 | 结果 |
|---|---|---|---|
+ | 加 | 3 + 2 | 5 |
- | 减 | 3 - 2 | 1 |
* | 乘 | 3 * 2 | 6 |
/ | 除(结果为浮点) | 7 / 2 | 3.5 |
// | 整除(向下取整) | 7 // 2 | 3 |
% | 取模(取余数) | 7 % 2 | 1 |
** | 幂运算 | 2 ** 3 | 8 |
() | 改变运算优先级 | (1+2)*3 | 9 |
# 特殊用法:字符串与整数相乘 = 重复
print("Hi" * 3) # HiHiHi
# 字符串与字符串可以用 + 拼接
print("Hello" + " " + "World") # Hello World
# 字符串与其他类型不能直接做算术运算
# print("10" + 5) # 报错!TypeError
2.7.2 赋值与复合赋值运算符
a = 10
a += 5 # 等价于 a = a + 5,先计算 a+5 得15,再赋值给a
print(a) # 15
a -= 3 # a = a - 3 = 12
a *= 2 # a = a * 2 = 24
a //= 5 # a = a // 5 = 4
a **= 3 # a = a ** 3 = 64
a %= 10 # a = a % 10 = 4
2.7.3 比较运算符
比较运算符用于比较两个值,返回布尔值True 或 False。
| 运算符 | 含义 | 示例 |
|---|---|---|
== | 等于(注意:和SQL不同,Python用 == 判断相等,= 是赋值) | 3 == 3 → True |
!= | 不等于 | 3 != 4 → True |
> | 大于 | 5 > 3 → True |
< | 小于 | 5 < 3 → False |
>= | 大于等于 | 3 >= 3 → True |
<= | 小于等于 | 3 <= 2 → False |
x, y = 5, 10
print(x == y) # False
print(x != y) # True
print(x > y) # False
print(x < y) # True
2.7.4 逻辑运算符
| 运算符 | 含义 | 规则 |
|---|---|---|
and | 与 | 两个都为 True 才返回 True,有一个 False 就返回 False |
or | 或 | 有一个 True 就返回 True,全部 False 才返回 False |
not | 非 | 取反,True → False,False → True |
print(True and True) # True
print(True and False) # False
print(False or True) # True
print(False or False) # False
print(not True) # False
print(not False) # True
# 实际应用
age = 25
has_id = True
if age >= 18 and has_id:
print("允许进入") # 输出这个
第三章 流程控制
3.1 条件判断 if-elif-else
程序根据条件的真假决定执行哪段代码。Python 使用 缩进(通常4个空格)来表示代码块的层级关系,而不是大括号。
score = 85
if score >= 90:
print("优秀")
elif score >= 80:
print("良好") # 满足此条件,输出:良好
elif score >= 60:
print("及格")
else:
print("不及格")
# 嵌套 if
if score >= 60:
if score >= 90:
print("优秀")
else:
print("及格了")
else:
print("不及格")
3.2 while 循环
while 循环在条件为 True 时反复执行循环体,直到条件变为 False。适用于 循环次数不确定 的场景。
# 基本用法:打印1到5
i = 1
while i <= 5:
print(i)
i += 1 # 必须更新条件变量,否则会死循环!
# 计算1到100的和
total = 0
num = 1
while num <= 100:
total += num
num += 1
print(f"1到100的和: {total}") # 5050
3.3 for 循环
for 循环主要用于 遍历序列(字符串、列表、元组等)或 固定次数循环(配合 range() 函数)。语法简洁,优先使用。
# 遍历字符串
for char in "Python":
print(char, end=" ") # P y t h o n
print()
# range(stop): 生成 0 到 stop-1 的整数序列
for i in range(5):
print(i, end=" ") # 0 1 2 3 4
print()
# range(start, stop): 生成 start 到 stop-1 的整数序列
for i in range(2, 6):
print(i, end=" ") # 2 3 4 5
print()
# range(start, stop, step): 带步长
for i in range(0, 10, 2):
print(i, end=" ") # 0 2 4 6 8
print()
# range 前闭后开:start 默认 0,step 默认 1
for i in range(5): # 等价于 range(0, 5, 1)
print(i, end=" ") # 0 1 2 3 4
3.4 while 和 for 的选择
- 优先使用
for:语法简洁,不容易出错。- 遍历序列(字符串、列表、元组)时。
- 循环固定次数时。
- 其次使用
while:更灵活。- 循环次数不确定时(如等待用户输入正确密码)。
- 需要复杂的循环条件时。
3.5 无限循环
# 无限循环,需要配合 break 退出
while True:
password = input("请输入密码:")
if password == "123456":
print("密码正确!")
break # 跳出循环
else:
print("密码错误,请重试。")
3.6 break 和 continue
break:立即终止 当前所在的整个循环,跳出到循环外继续执行。continue:跳过本次循环中continue之后的剩余语句,直接进入 下一次循环。
# break 示例:找到第一个能被7整除的数就停止
for i in range(1, 100):
if i % 7 == 0:
print(f"找到第一个能被7整除的数: {i}") # 7
break
# continue 示例:跳过偶数,只打印奇数
for i in range(10):
if i % 2 == 0:
continue # 跳过本次,进入下一次循环
print(i, end=" ") # 1 3 5 7 9
3.7 循环中的 else 子句
for/while ... else 结构中,else 块在循环 正常结束(没有被 break 中断)时执行。它的典型用途是 验证循环是否被 break 中断。
# 示例:判断一个数是否为质数
num = 17
for i in range(2, num):
if num % i == 0:
print(f"{num} 不是质数,它能被 {i} 整除")
break
else:
# for 循环完整执行完毕(没有break),说明找不到因子
print(f"{num} 是质数") # 输出这个
第四章 字符串详解
4.1 字符串的特点
- 有序:每个字符都有固定的索引位置,从
0开始。 - 可重复:同一个字符可以出现多次。
- 不可变:字符串创建后不能修改其中某个字符。所有修改操作(
replace、strip、upper等)都会返回一个新的字符串副本,原字符串不变。
4.2 切片
切片用于从字符串中提取子串,语法为 字符串[start:end:step]。
start:起始索引(包含),默认0。end:结束索引(不包含),默认到字符串末尾。step:步长,默认1。正数从左到右,负数从右到左。
s = "0123456789"
print(s[2:5]) # "234"(索引2到4,不含5)
print(s[:5]) # "01234"(省略start,默认从0开始)
print(s[5:]) # "56789"(省略end,默认到最后)
print(s[::2]) # "02468"(步长为2,每隔一个取一个)
print(s[::-1]) # "987654321"(步长-1,逆序)
print(s[-3:]) # "789"(倒数第3个到最后)
print(s[-3:-6:-1]) # "765"(从右往左切片)
4.3 常用方法
text = " Hello, Python World! "
# ===== 查找 =====
print(text.find("Python")) # 9(返回子串首次出现的索引,找不到返回-1)
print(text.find("Java")) # -1
print(text.index("Python")) # 9(功能同find,但找不到会报 ValueError)
print(text.count("l")) # 3(统计子串出现次数)
# ===== 修改(均返回新字符串,原字符串不变) =====
print(text.strip()) # "Hello, Python World!"(去除两端空白字符)
print(text.lstrip()) # "Hello, Python World! "(去除左侧空白)
print(text.rstrip()) # " Hello, Python World!"(去除右侧空白)
print(text.replace("Python", "Java")) # " Hello, Java World! "
print(text.split()) # ['Hello,', 'Python', 'World!'](默认按空白分割)
print(text.split(",")) # [' Hello', ' Python World! ']
# ===== 大小写 =====
print("hello world".title()) # "Hello World"(每个单词首字母大写)
print("hello world".capitalize()) # "Hello world"(仅第一个字母大写)
print("Hello".upper()) # "HELLO"(全大写)
print("Hello".lower()) # "hello"(全小写)
# ===== 判断 =====
print("hello".startswith("he")) # True(是否以指定前缀开头)
print("hello".endswith("lo")) # True(是否以指定后缀结尾)
print("123".isdigit()) # True(是否全是数字)
print("abc".isalpha()) # True(是否全是字母)
print("abc123".isalnum()) # True(是否全是字母或数字)
# ===== 拼接 =====
words = ["I", "love", "Python"]
print(" ".join(words)) # "I love Python"(用指定分隔符拼接列表)
第五章 列表详解
5.1 列表的特点
- 有序:元素有固定索引,从
0开始。 - 可重复:同一个值可以出现多次。
- 可变:可以增、删、改元素。是 Python 中最常用的数据容器。
5.2 定义
lst1 = [] # 空列表
lst2 = [1, 2, 3, 4] # 直接定义
lst3 = list() # 空列表
lst4 = list("hello") # ['h', 'e', 'l', 'l', 'o']
lst5 = [1, "hello", 3.14, True, [1, 2]] # 可以包含不同类型
5.3 增
fruits = ["apple", "banana"]
# append:追加单个元素到末尾
fruits.append("cherry")
print(fruits) # ['apple', 'banana', 'cherry']
# extend:将另一个可迭代对象中的元素逐个添加到末尾
fruits.extend(["mango", "grape"])
print(fruits) # ['apple', 'banana', 'cherry', 'mango', 'grape']
# insert:在指定索引位置插入元素
fruits.insert(1, "orange")
print(fruits) # ['apple', 'orange', 'banana', 'cherry', 'mango', 'grape']
5.4 删
fruits = ["apple", "banana", "cherry", "mango", "grape"]
# del:根据索引删除
del fruits[0]
print(fruits) # ['banana', 'cherry', 'mango', 'grape']
# pop:根据索引删除并返回被删除的元素,默认删除最后一个
removed = fruits.pop() # 'grape'
removed = fruits.pop(0) # 'banana'
print(removed, fruits) # banana ['cherry', 'mango']
# remove:根据元素值删除第一个匹配项
fruits.remove("cherry")
print(fruits) # ['mango']
5.5 改
fruits = ["cherry", "apple", "banana"]
# 根据索引修改
fruits[0] = "watermelon"
print(fruits) # ['watermelon', 'apple', 'banana']
# 反转列表(原地修改)
fruits.reverse()
print(fruits) # ['banana', 'apple', 'watermelon']
# 排序(原地修改,默认升序)
fruits.sort()
print(fruits) # ['apple', 'banana', 'watermelon']
# 降序排序
fruits.sort(reverse=True)
print(fruits) # ['watermelon', 'banana', 'apple']
5.6 查
fruits = ["apple", "banana", "cherry", "banana"]
# in / not in:判断元素是否存在
print("apple" in fruits) # True
print("grape" not in fruits) # True
# index:根据值查找索引(返回第一次出现的索引)
print(fruits.index("banana")) # 1
# fruits.index("grape") # 报错 ValueError
# 根据索引查值
print(fruits[0]) # apple
print(fruits[-1]) # banana
# print(fruits[10]) # 报错 IndexError(索引越界)
# len:获取列表长度
print(len(fruits)) # 4
5.7 切片
列表的切片语法与字符串完全一致。
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(nums[2:5]) # [2, 3, 4]
print(nums[::3]) # [0, 3, 6, 9]
print(nums[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
第六章 元组详解
6.1 元组的特点
- 有序:有固定索引。
- 可重复:同一值可以出现多次。
- 不可变:创建后不能增、删、改元素。用于存储不应被修改的数据(如坐标、配置项)。
6.2 定义
t1 = () # 空元组
t2 = (1, 2, 3) # 直接定义
t3 = tuple() # 空元组
t4 = tuple("hello") # ('h', 'e', 'l', 'l', 'o')
# 注意:单元素元组必须加逗号!
t5 = (5) # 这是 int,不是元组
t6 = (5,) # 这才是元组
print(type(t5)) # <class 'int'>
print(type(t6)) # <class 'tuple'>
6.3 常用操作
t = (1, 2, 3, 2, 2, 4)
# 查
print(t[0]) # 1(根据索引查值)
print(t.count(2)) # 3(元素2出现的次数)
print(t.index(3)) # 2(元素3第一次出现的索引)
print(len(t)) # 6(元组长度)
print(2 in t) # True(判断元素是否存在)
# 切片(和列表、字符串一致)
print(t[1:4]) # (2, 3, 2)
print(t[::-1]) # (4, 2, 2, 3, 2, 1)
第七章 字典详解
7.1 字典的特点
- 无序(Python 3.7+ 按插入顺序排列,但本质上仍是无序的键值映射)。
- 键值对结构:每个元素由
key: value组成。 - 键不可变:键必须是不可变类型(
int、float、str、bool、tuple),不能是列表或字典。 - 键不可重复:重复的键后面的值会覆盖前面的。值可以重复。
- 可变:可以增、删、改键值对。
7.2 定义
d1 = {} # 空字典(注意:{}不是空集合!)
d2 = {"name": "Alice", "age": 25} # 直接定义
d3 = dict(name="Bob", age=30) # 用 dict() 构造
d4 = dict([("a", 1), ("b", 2)]) # 用键值对列表构造
7.3 增与改
person = {"name": "Alice", "age": 25}
# 键不存在 → 添加;键存在 → 修改
person["city"] = "Beijing" # 添加
person["age"] = 26 # 修改
print(person) # {'name': 'Alice', 'age': 26, 'city': 'Beijing'}
# setdefault:键不存在时才添加,存在则不做任何操作
person.setdefault("gender", "女")
person.setdefault("name", "新名字") # name已存在,不修改
print(person) # {'name': 'Alice', 'age': 26, 'city': 'Beijing', 'gender': '女'}
# update:批量更新
person.update({"age": 27, "phone": "13800138000"})
print(person)
7.4 删
person = {"name": "Alice", "age": 25, "city": "Beijing"}
# del:删除指定键
del person["city"]
print(person) # {'name': 'Alice', 'age': 25}
# pop:删除指定键并返回对应的值
age = person.pop("age")
print(age, person) # 25 {'name': 'Alice'}
# popitem:删除并返回最后插入的键值对(Python 3.7+)
item = person.popitem()
print(item) # ('name', 'Alice')
# clear:清空字典
person.clear()
print(person) # {}
7.5 查
person = {"name": "Alice", "age": 25, "city": "Beijing"}
# 方式一:直接用 [](键不存在会报 KeyError)
print(person["name"]) # Alice
# 方式二:用 get()(键不存在返回默认值,不会报错)
print(person.get("name")) # Alice
print(person.get("gender")) # None(键不存在)
print(person.get("gender", "未知")) # "未知"(指定默认值)
# 判断键是否存在
print("name" in person) # True
print("gender" not in person) # True
7.6 遍历
person = {"name": "Alice", "age": 25, "city": "Beijing"}
# 遍历键
for key in person.keys():
print(key)
# 遍历值
for value in person.values():
print(value)
# 遍历键值对(推荐,最高效)
for key, value in person.items():
print(f"{key}: {value}")
第八章 集合详解
8.1 集合的特点
- 无序:没有索引,不能通过位置访问元素。
- 不重复:自动去重,相同元素只保留一个。
- 可变:可以增、删元素。
- 用途:去重、集合运算(交集、并集、差集)。
8.2 定义
s1 = {1, 2, 3, 2, 1} # 自动去重 → {1, 2, 3}
s2 = set() # 空集合(必须用 set(),{} 是空字典!)
s3 = set([1, 2, 2, 3]) # 从列表创建 → {1, 2, 3}
print(s1, s2, s3)
8.3 常用操作
s = {1, 2, 3}
# 增
s.add(4) # {1, 2, 3, 4}
s.update([5, 6]) # {1, 2, 3, 4, 5, 6}
# 删
s.remove(1) # 删除指定元素,不存在则报 KeyError
s.discard(10) # 删除指定元素,不存在不报错
removed = s.pop() # 随机删除一个元素并返回
# 查
print(2 in s) # True
print(len(s)) # 集合大小
8.4 集合运算
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a & b) # 交集: {3, 4}
print(a | b) # 并集: {1, 2, 3, 4, 5, 6}
print(a - b) # 差集(A有B没有): {1, 2}
print(a ^ b) # 对称差集(不同时在两个集合中): {1, 2, 5, 6}
print(a.issubset(b)) # False(a是否是b的子集)
print(a.issuperset(b)) # False(a是否是b的超集)
第九章 推导式
推导式是一种简洁的语法,用于快速创建列表、字典、集合,本质是简化 for 循环代码。
9.1 列表推导式
# [表达式 for 变量 in 可迭代对象]
squares = [x**2 for x in range(10)]
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# 带条件过滤
even_squares = [x**2 for x in range(10) if x % 2 == 0]
print(even_squares) # [0, 4, 16, 36, 64]
# 带条件表达式
labels = ["偶数" if x % 2 == 0 else "奇数" for x in range(5)]
print(labels) # ['偶数', '奇数', '偶数', '奇数', '偶数']
9.2 字典推导式
# {键表达式: 值表达式 for 变量 in 可迭代对象}
squared_dict = {x: x**2 for x in range(5)}
print(squared_dict) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# 从两个列表创建字典
keys = ["name", "age", "city"]
values = ["Alice", 25, "Beijing"]
person = {k: v for k, v in zip(keys, values)}
print(person) # {'name': 'Alice', 'age': 25, 'city': 'Beijing'}
9.3 集合推导式
# {表达式 for 变量 in 可迭代对象}
unique_squares = {x**2 for x in [1, -1, 2, -2, 3]}
print(unique_squares) # {1, 4, 9}(自动去重)
第十章 函数基础
10.1 函数的作用
函数是一段 可重复使用 的代码块。使用函数可以实现 代码复用,避免重复编写相同的代码,同时使程序结构更清晰、更易维护。
10.2 定义与调用
# 定义函数
def greet(name):
"""给指定的人打招呼"""
print(f"Hello, {name}!")
# 调用函数
greet("Alice") # Hello, Alice!
greet("Bob") # Hello, Bob!
10.3 返回值
return用于将函数内部的计算结果返回给调用者。return之后的代码 不会被执行。- 可以返回多个值(实际返回一个元组)。
- 如果没有
return或return后面没有值,函数返回None。
def add(a, b):
return a + b
result = add(3, 5)
print(result) # 8
# 返回多个值
def min_max(numbers):
return min(numbers), max(numbers) # 实际返回元组
low, high = min_max([3, 1, 4, 1, 5, 9])
print(f"最小值: {low}, 最大值: {high}") # 最小值: 1, 最大值: 9
10.4 函数嵌套
def outer():
print("外部函数开始")
def inner():
print("内部函数执行")
inner() # 在外部函数内部调用内部函数
print("外部函数结束")
outer()
# 注意:inner() 不能在 outer() 外部直接调用,因为它是在 outer 内部定义的
第十一章 函数进阶
11.1 作用域
- 局部变量:在函数内部定义的变量,只能在函数内部访问。
- 全局变量:在函数外部定义的变量,在整个文件中都可以访问。
g_var = 100 # 全局变量
def read_global():
# 可以读取全局变量
print(f"读取全局变量: {g_var}") # 100
def try_modify():
# 这里会创建一个同名的局部变量,不影响全局变量
g_var = 200
print(f"局部g_var: {g_var}") # 200
def real_modify():
global g_var # 使用 global 关键字声明要修改全局变量
g_var = 300
read_global()
try_modify()
print(f"调用try_modify后,全局g_var: {g_var}") # 100(未被修改)
real_modify()
print(f"调用real_modify后,全局g_var: {g_var}") # 300(被修改了)
11.2 参数进阶
默认参数
定义函数时给参数指定默认值,调用时如果不传该参数,就使用默认值。
def power(base, exponent=2):
return base ** exponent
print(power(3)) # 9(使用默认 exponent=2)
print(power(3, 3)) # 27(传了 exponent=3)
位置参数与关键字参数
def introduce(name, age, city):
print(f"{name}, {age}岁, 来自{city}")
# 位置参数:按顺序传入
introduce("Alice", 25, "Beijing")
# 关键字参数:指定参数名传入,顺序可以不一致
introduce(city="Shanghai", name="Bob", age=30)
# 混合使用:位置参数必须在关键字参数前面
introduce("Charlie", city="Shenzhen", age=28)
不定长参数
当不确定传入多少个参数时使用。
# *args:接收任意数量的位置参数,打包为元组
def sum_all(*args):
print(f"args的类型: {type(args)}, 值: {args}")
return sum(args)
print(sum_all(1, 2, 3)) # args的类型: <class 'tuple'>, 值: (1, 2, 3) → 6
print(sum_all(10, 20, 30, 40)) # 100
# **kwargs:接收任意数量的关键字参数,打包为字典
def print_info(**kwargs):
print(f"kwargs的类型: {type(kwargs)}, 值: {kwargs}")
for key, value in kwargs.items():
print(f" {key}: {value}")
print_info(name="Alice", age=25, city="Beijing")
11.3 可变与不可变类型在函数中的表现
# 不可变类型(int, float, str, bool, tuple):函数内修改会创建新的局部变量
def modify_int(x):
x = 999 # 创建局部变量,不影响外部
print(f"函数内 x: {x}")
num = 10
modify_int(num)
print(f"函数外 num: {num}") # 10(未被影响)
# 可变类型(list, dict, set):函数内修改会影响外部
def modify_list(lst):
lst.append(999) # 修改的是同一个对象
print(f"函数内 lst: {lst}")
my_list = [1, 2, 3]
modify_list(my_list)
print(f"函数外 my_list: {my_list}") # [1, 2, 3, 999](被影响了)
11.4 Lambda 表达式
Lambda 是一种创建匿名函数(没有名字的函数)的简洁方式。适用于简单的、不需要复用的一次性函数。
# 语法:lambda 参数1, 参数2, ... : 表达式(自动返回表达式的结果)
# 普通函数
def square(x):
return x ** 2
# 等价的 lambda
square_lambda = lambda x: x ** 2
print(square(5)) # 25
print(square_lambda(5)) # 25
# 常与高阶函数配合使用
numbers = [3, 1, 4, 1, 5, 9]
numbers.sort(key=lambda x: -x) # 按降序排列
print(numbers) # [9, 5, 4, 3, 1, 1]
11.5 拆包与合包
# 拆包:将容器中的元素分别赋值给多个变量
a, b, c = (10, 20, 30)
print(a, b, c) # 10 20 30
# 用 * 收集剩余元素
first, *middle, last = [1, 2, 3, 4, 5]
print(first) # 1
print(middle) # [2, 3, 4](合包为列表)
print(last) # 5
# 字典用 ** 拆包
def connect(host, port):
print(f"连接到 {host}:{port}")
config = {"host": "localhost", "port": 8080}
connect(**config) # 等价于 connect(host="localhost", port=8080)
第十二章 文件操作
12.1 打开文件 open()
open() 函数用于打开一个文件,返回一个文件对象。
参数说明:
- 文件路径:
- 绝对路径:从根目录开始的完整路径,如
D:/data/test.txt。 - 相对路径:相对于当前脚本所在目录,
.代表当前目录,..代表上级目录。
- 绝对路径:从根目录开始的完整路径,如
- 打开模式:
'r':只读模式(默认)。文件不存在则报错。'w':写入模式。文件存在则清空,不存在则创建。'a':追加模式。文件存在则在末尾追加,不存在则创建。'b':二进制模式,可与'rb'、'wb'组合使用。
- 编码:
encoding='utf-8',处理中文时务必指定。
推荐使用 with as 语句(上下文管理器),它会自动关闭文件,无需手动调用 close()。
# 推荐写法:with 语句自动管理文件关闭
with open('test.txt', 'w', encoding='utf-8') as f:
f.write("第一行内容\n")
f.write("第二行内容\n")
# 离开 with 块后,文件自动关闭
12.2 写入文件
with open('output.txt', 'w', encoding='utf-8') as f:
f.write("Hello, World!\n") # write 写入字符串
f.write("这是第二行。\n")
lines = ["第三行\n", "第四行\n"]
f.writelines(lines) # writelines 写入字符串列表
12.3 读取文件
with open('output.txt', 'r', encoding='utf-8') as f:
# 方式一:read() 读取全部内容为一个字符串
content = f.read()
print(content)
with open('output.txt', 'r', encoding='utf-8') as f:
# 方式二:readline() 逐行读取
line = f.readline()
while line:
print(line.strip()) # strip() 去除行尾换行符
line = f.readline()
with open('output.txt', 'r', encoding='utf-8') as f:
# 方式三:readlines() 读取所有行到列表
lines = f.readlines()
print(lines) # ['Hello, World!\n', '这是第二行。\n', ...]
with open('output.txt', 'r', encoding='utf-8') as f:
# 方式四(推荐):直接 for 循环遍历文件对象
for line in f:
print(line.strip())
12.4 文件指针操作
with open('output.txt', 'r', encoding='utf-8') as f:
print(f.tell()) # 0,当前光标位置
content = f.read(5) # 读取5个字符
print(content)
print(f.tell()) # 5,光标移动到了第5个位置
f.seek(0) # 将光标移回文件开头
print(f.read(5)) # 重新从头读取5个字符
第十三章 异常处理
13.1 什么是异常
异常是程序运行过程中发生的错误。如果不处理异常,程序会中断并显示错误信息。使用异常处理可以让程序在出错时 优雅地处理错误,而不是直接崩溃。
13.2 try-except 基本结构
try:
# 可能发生异常的代码
num = int(input("请输入一个数字: "))
result = 100 / num
print(f"结果: {result}")
except ValueError:
# 当发生 ValueError 时执行(如输入了非数字字符)
print("输入的不是有效数字!")
except ZeroDivisionError:
# 当发生 ZeroDivisionError 时执行(如输入了0)
print("除数不能为0!")
except Exception as e:
# 捕获所有其他异常(兜底),e 是异常对象
print(f"发生未知错误: {e}")
13.3 完整结构 try-except-else-finally
try:
num = int("123")
except ValueError as e:
print(f"异常: {e}")
else:
# try 块中没有发生异常时执行
print("没有发生异常,转换成功!")
finally:
# 无论是否发生异常,都会执行(常用于资源清理)
print("finally 块始终执行")
13.4 主动抛出异常
def set_age(age):
if age < 0 or age > 150:
raise ValueError(f"年龄不合法: {age}")
print(f"年龄设置为: {age}")
try:
set_age(-5)
except ValueError as e:
print(f"捕获到异常: {e}") # 捕获到异常: 年龄不合法: -5
13.5 异常处理的作用
- 1.提高代码健壮性:程序不会因为一个错误就完全崩溃。
- 2.提供补救措施:出错时可以执行备用方案(如重试、使用默认值、记录日志等)。
- 3.改善用户体验:给用户友好的错误提示,而不是看不懂的报错信息。
第十四章 模块与包
14.1 什么是模块
模块就是一个 .py 文件,里面定义了函数、类、变量等。通过模块可以将代码组织成独立的文件,实现代码复用。
14.2 导入模块
# 方式一:import 模块名
import math
print(math.sqrt(16)) # 4.0
print(math.pi) # 3.141592653589793
# 方式二:from 模块名 import 函数名
from random import randint, choice
print(randint(1, 10)) # 1到10的随机整数
print(choice(["A", "B", "C"])) # 随机选择一个
# 方式三:import 模块名 as 别名(模块名太长时使用)
import numpy as np
import pandas as pd
# 方式四:from 模块名 import *(导入模块中所有公开内容,不推荐)
# from math import *
14.3 自定义模块
任何 .py 文件都可以作为模块被导入。模块/文件名的命名规则与变量名一致:字母、数字、下划线组成,不能以数字开头,不能和 Python 关键字或内置模块重名。
# my_utils.py(自定义模块)
PI = 3.14159
def circle_area(radius):
"""计算圆的面积"""
return PI * radius ** 2
def circle_perimeter(radius):
"""计算圆的周长"""
return 2 * PI * radius
# main.py(导入自定义模块)
import my_utils
print(my_utils.PI) # 3.14159
print(my_utils.circle_area(5)) # 78.53975
print(my_utils.circle_perimeter(5)) # 31.4159
14.4 常用内置模块
| 模块 | 用途 | 示例 |
|---|---|---|
os | 操作系统相关(文件、目录、环境变量) | os.getcwd(), os.listdir() |
sys | Python 解释器相关 | sys.argv, sys.path |
math | 数学函数 | math.sqrt(), math.pi |
random | 随机数生成 | random.randint(), random.choice() |
datetime | 日期和时间 | datetime.datetime.now() |
json | JSON 编解码 | json.dumps(), json.loads() |
time | 时间相关 | time.sleep(), time.time() |
import os
print(os.getcwd()) # 获取当前工作目录
print(os.listdir('.')) # 列出当前目录的文件
import datetime
now = datetime.datetime.now()
print(now.strftime("%Y-%m-%d %H:%M:%S")) # 2024-01-15 14:30:00
import json
data = {"name": "Alice", "age": 25}
json_str = json.dumps(data, ensure_ascii=False) # 转为JSON字符串
print(json_str)
parsed = json.loads(json_str) # 解析JSON字符串
print(parsed)
第十五章 面向对象基础
15.1 面向对象 vs 面向过程
- 面向过程:关注"做什么事",按照事情的步骤一步步执行。以函数为核心。适合简单、线性的任务。
- 面向对象:关注"谁来做这件事",将数据和操作数据的方法封装在对象中。以类和对象为核心。适合复杂、模块化的系统。
| 语言 | 编程范式 |
|---|---|
| C | 纯面向过程 |
| C++ | 面向过程 + 面向对象 |
| Java | 纯面向对象 |
| Python | 面向过程 + 面向对象 |
15.2 类与对象
- 类(Class):抽象的模板,定义了对象应该具有的属性和行为。好比"汽车设计图纸"。
- 对象(Object):类的具体实例,拥有类定义的属性和行为。好比"根据图纸造出来的一辆具体的车"。
# 定义类
class Car:
# 类属性(所有实例共享)
wheels = 4
# 初始化方法(创建对象时自动调用)
def __init__(self, brand, color):
# 实例属性(每个对象独有)
self.brand = brand
self.color = color
self.mileage = 0
# 实例方法
def drive(self, distance):
self.mileage += distance
print(f"{self.color}的{self.brand}行驶了{distance}公里,总里程{self.mileage}公里")
def honk(self):
print(f"{self.brand}: 嘀嘀!")
# __str__:定义对象的字符串表示(print对象时自动调用)
def __str__(self):
return f"一辆{self.color}的{self.brand}车,里程{self.mileage}公里"
# 创建对象(实例化)
car1 = Car("Tesla", "白色")
car2 = Car("BMW", "黑色")
car1.drive(100)
car1.drive(50)
car2.honk()
print(car1) # 调用 __str__
print(car2)
15.3 self 的作用
self 代表对象自身,在类的方法中使用,用于访问该对象的属性和调用该对象的其他方法。调用方法时,Python 自动 将对象本身作为第一个参数传给 self,不需要手动传。
class Person:
def __init__(self, name, age):
self.name = name # self.name 表示"这个对象的name属性"
self.age = age
def introduce(self):
# self.name 就是调用此方法的那个对象的 name 属性
print(f"我叫{self.name},今年{self.age}岁")
def call_other(self, other):
# 通过 self 调用自己的方法
self.introduce()
print(f"我正在和{other.name}说话")
p1 = Person("Alice", 25)
p2 = Person("Bob", 30)
p1.introduce() # 我叫Alice,今年25岁
# 等价于:Person.introduce(p1)
p1.call_other(p2) # 我叫Alice,今年25岁 \n 我正在和Bob说话
15.4 魔法方法
魔法方法(Magic Methods)以双下划线 __ 开头和结尾,会在特定场景下 自动触发,不需要手动调用。
| 魔法方法 | 触发时机 | 用途 |
|---|---|---|
__init__ | 创建对象时 | 初始化对象属性 |
__str__ | print(对象) 或 str(对象) 时 | 定义对象的可读字符串表示 |
__del__ | 对象被删除时(del 对象 或程序结束) | 资源清理 |
__repr__ | 在交互式环境中直接输入对象名 | 定义对象的官方字符串表示 |
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
print(f"{self.name} 对象已创建")
def __str__(self):
return f"学生: {self.name}, 成绩: {self.score}"
def __del__(self):
print(f"{self.name} 对象已销毁")
s = Student("Alice", 95)
print(s) # 触发 __str__: 学生: Alice, 成绩: 95
del s # 触发 __del__: Alice 对象已销毁
第十六章 面向对象进阶
16.1 三大特征
封装
将对象的属性和行为封装在一起,对外隐藏内部实现细节,只暴露必要的接口。通过访问控制实现:
- 公有:无前缀,外部可直接访问。
- 保护:单下划线
_前缀,约定为"请不要从外部访问",但技术上仍可访问。 - 私有:双下划线
__前缀,Python 会进行名称改写(name mangling),外部无法直接访问。
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner # 公有属性
self.__balance = balance # 私有属性
def deposit(self, amount):
if amount > 0:
self.__balance += amount
print(f"存入 {amount},余额: {self.__balance}")
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
print(f"取出 {amount},余额: {self.__balance}")
else:
print("余额不足")
def get_balance(self):
return self.__balance
account = BankAccount("Alice", 1000)
account.deposit(500) # 存入 500,余额: 1500
account.withdraw(200) # 取出 200,余额: 1300
print(account.get_balance()) # 1300
# print(account.__balance) # 报错!无法直接访问私有属性
继承
子类可以继承父类的属性和方法,并可以 重写(Override) 父类的方法或添加新的属性和方法。
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name} 正在吃东西")
def speak(self):
print(f"{self.name} 发出了声音")
# 单继承
class Dog(Animal):
def speak(self): # 重写父类方法
print(f"{self.name}: 汪汪!")
def fetch(self): # 子类新增方法
print(f"{self.name} 正在捡球")
# 多继承
class Pet:
def __init__(self, owner):
self.owner = owner
class GuideDog(Dog, Pet): # 多继承,优先继承左边的父类
def __init__(self, name, owner):
Dog.__init__(self, name) # 调用父类的初始化
Pet.__init__(self, owner)
dog = Dog("旺财")
dog.eat() # 旺财 正在吃东西(继承自Animal)
dog.speak() # 旺财: 汪汪!(重写后的方法)
dog.fetch() # 旺财 正在捡球(子类新增方法)
# 查看继承顺序
print(GuideDog.mro())
调用父类方法的两种方式:
class Child(Parent):
def method(self):
# 方式一:父类名.方法(self)(推荐,清晰明确)
Parent.method(self)
# 方式二:super().方法()(适用于单继承)
super().method()
多态
不同类的对象对同一方法调用产生不同的行为。前提是 继承 + 重写 + 父类类型接收子类对象。
class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "汪汪!"
class Cat(Animal):
def speak(self):
return "喵喵~"
class Duck(Animal):
def speak(self):
return "嘎嘎!"
# 多态的核心:统一接口,不同表现
def animal_sound(animal):
print(animal.speak())
animal_sound(Dog()) # 汪汪!
animal_sound(Cat()) # 喵喵~
animal_sound(Duck()) # 嘎嘎!
多态的作用:
- 1.解耦合:调用者不需要知道具体是哪个子类,只需要知道它有
speak()方法。 - 2.可扩展性:新增子类时,不需要修改调用者的代码。
16.2 类属性、类方法、静态方法
class MyClass:
class_attr = "我是类属性" # 类属性(所有实例共享)
def __init__(self, value):
self.instance_attr = value # 实例属性(每个实例独有)
# 实例方法:第一个参数是 self,可以访问实例属性和类属性
def instance_method(self):
print(f"实例方法: {self.instance_attr}")
# 类方法:用 @classmethod 装饰,第一个参数是 cls(代表类本身)
@classmethod
def class_method(cls):
print(f"类方法: {cls.class_attr}")
# 静态方法:用 @staticmethod 装饰,没有 self 也没有 cls,类似普通函数
@staticmethod
def static_method():
print("静态方法: 我和类、实例都没关系")
# 使用
obj = MyClass("实例值")
obj.instance_method() # 实例方法: 实例值
MyClass.class_method() # 类方法: 我是类属性
MyClass.static_method() # 静态方法: 我和类、实例都没关系
# 类属性可以通过类和实例访问(但建议通过类访问)
print(MyClass.class_attr) # 我是类属性
print(obj.class_attr) # 我是类属性
第十七章 高级特性
17.1 闭包
闭包是一个函数,它记住了自己被创建时的环境(外层函数的局部变量),即使外层函数已经执行完毕。
闭包的三个条件:
- 1.函数嵌套(函数内部定义另一个函数)。
- 2.内部函数引用了外部函数的变量。
- 3.外部函数返回内部函数名(不是调用)。
def outer(msg):
# msg 是外部函数的局部变量
def inner():
print(f"闭包捕获的消息: {msg}") # 内部函数引用了外部变量
return inner # 返回内部函数对象(注意:不是 inner())
# outer 执行完毕后,msg 本应被销毁,但因为闭包的存在,它被保留了
closure = outer("Hello, 闭包!")
closure() # 闭包捕获的消息: Hello, 闭包!
closure() # 闭包捕获的消息: Hello, 闭包!(每次调用都能访问 msg)
nonlocal 关键字:在闭包内部修改外部函数的变量(类似 global)。
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(count 的值被保留并持续修改)
17.2 深浅拷贝
深浅拷贝是为 可变类型(列表、字典、集合)准备的。不可变类型本身不能修改,拷贝没有实际意义。
import copy
original = [1, 2, [3, 4]]
# ===== 赋值(引用传递) =====
assigned = original
assigned[0] = 99
assigned[2][0] = 100
print(original) # [99, 2, [100, 4]](完全被影响,因为指向同一块内存)
original = [1, 2, [3, 4]] # 重置
# ===== 浅拷贝(只复制最外层) =====
shallow = copy.copy(original)
shallow[0] = 99 # 修改外层元素,不影响原对象
shallow[2][0] = 100 # 修改内层嵌套对象,会影响原对象(因为共享引用)
print(original) # [1, 2, [100, 4]](外层没变,内层被影响)
print(shallow) # [99, 2, [100, 4]]
original = [1, 2, [3, 4]] # 重置
# ===== 深拷贝(完全独立的副本) =====
deep = copy.deepcopy(original)
deep[0] = 99
deep[2][0] = 100
print(original) # [1, 2, [3, 4]](完全不受影响)
print(deep) # [99, 2, [100, 4]]
形象比喻:
- 赋值:两个人看同一张纸,一个人改了内容,另一个人看到的也变了。
- 浅拷贝:复印了纸上的文字,但嵌套的表格只复印了引用地址,改表格内容双方都受影响。
- 深拷贝:完全克隆了一份,任何修改都互不影响。
17.3 装饰器
装饰器本质上是一个 高阶函数(接收函数作为参数,返回新的函数),用于在不修改原函数代码的前提下,为函数添加额外的功能。
闭包的三个条件 + 内部函数有额外功能增强 = 装饰器
import time
# ===== 定义装饰器 =====
def timer(func):
"""计时装饰器:统计函数执行时间"""
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs) # 调用原函数
end = time.time()
print(f"[计时] {func.__name__} 执行耗时: {end - start:.4f}秒")
return result
return wrapper
# ===== 使用装饰器 =====
# 传统方式
def say_hello():
time.sleep(1)
print("Hello!")
decorated_hello = timer(say_hello)
decorated_hello()
# 语法糖方式(推荐)
@timer # 等价于 slow_function = timer(slow_function)
def slow_function(n):
total = 0
for i in range(n):
total += i
return total
result = slow_function(1000000)
print(f"计算结果: {result}")
装饰器的四种类型:
# 1. 无参无返回值
def log(func):
def wrapper(*args, **kwargs):
print(f"调用函数: {func.__name__}")
func(*args, **kwargs)
return wrapper
# 2. 有参无返回值
def log_with_level(level):
def decorator(func):
def wrapper(*args, **kwargs):
print(f"[{level}] 调用: {func.__name__}")
func(*args, **kwargs)
return wrapper
return decorator
@log_with_level("INFO")
def greet(name):
print(f"Hello, {name}")
greet("Alice")
# [INFO] 调用: greet
# Hello, Alice
# 3. 无参有返回值
def double_result(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result * 2
return wrapper
# 4. 有参有返回值
def retry(max_attempts=3):
def decorator(func):
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"第{attempt+1}次失败: {e}")
print("所有重试均失败")
return wrapper
return decorator
第十八章 网络编程
18.1 网络基础概念
IP 地址
IP(Internet Protocol)地址是互联网上每台计算机的唯一标识。就像快递需要收件地址一样,网络通信需要知道对方的 IP 地址才能找到目标计算机。
- IPv4:由4个0-255的数字组成,用
.分隔,如192.168.1.1。 - 特殊 IP:
127.0.0.1(或localhost)代表本机。
端口号
一台计算机上可能同时运行多个网络程序(浏览器、QQ、游戏等)。端口号用于区分同一台计算机上的不同程序。范围是 0-65535,其中 0-1023 是系统保留端口。
协议
协议是通信双方约定的规则,就像两个人交流需要说同一种语言。
TCP(Transmission Control Protocol,传输控制协议):
- 可靠传输:数据不会丢失、不会乱序。
- 面向连接:通信前需要先建立连接(三次握手),通信结束后断开连接(四次挥手)。
- 适用场景:文件传输、网页浏览、邮件等对数据完整性要求高的场景。
TCP 三次握手(建立连接):
- 1.客户端发送 SYN(同步请求)给服务端:"我想连接你"。
- 2.服务端回复 SYN+ACK(同步确认):"好的,我也想连接你"。
- 3.客户端发送 ACK(确认):"确认,连接建立"。
TCP 四次挥手(断开连接):
- 1.客户端发送 FIN(结束请求):"我说完了"。
- 2.服务端回复 ACK(确认):"好的,我知道了"。
- 3.服务端发送 FIN(结束请求):"我也说完了"。
- 4.客户端回复 ACK(确认):"好的,再见"。
UDP(User Datagram Protocol,用户数据报协议):
- 不可靠传输:数据可能丢失、乱序。
- 无连接:不需要建立连接,直接发送数据。
- 速度快:没有握手挥手的开销。
- 适用场景:视频直播、语音通话、DNS查询等对实时性要求高、允许少量丢包的场景。
HTTP/HTTPS:
- HTTP 是基于 TCP 的应用层协议,用于 Web 浏览器和服务器之间的通信。
- HTTPS = HTTP + SSL/TLS 加密,安全性更高。
18.2 Socket 编程
Socket(套接字)是网络通信的端点,是应用层与 TCP/IP 协议族通信的中间软件抽象层。简单来说,Socket 就是网络通信的"电话机"——服务端安装一部"电话机"等待来电,客户端拨打这部"电话机"建立通话。
TCP 服务端构造流程与作用
TCP 服务端的工作流程是:创建Socket → 绑定地址 → 监听 → 接受连接 → 收发数据 → 关闭连接。
详细步骤说明:
- 1.创建 Server Socket 对象:创建一个 TCP 类型的 Socket 对象,相当于"买了一部电话机"。
- 2.bind(绑定):将 Socket 绑定到指定的 IP 地址和端口号,相当于"给电话机装上电话号码"。客户端通过这个 IP+端口 找到服务端。
- 3.listen(监听):将 Socket 设为监听模式,开始等待客户端的连接请求。参数指定等待队列的最大长度(最大128),相当于"电话机开机,等待来电"。
- 4.accept(接受连接):阻塞等待,直到有客户端连接。成功后返回一个新的 Socket 对象(专门用于和这个客户端通信)和客户端地址。相当于"接起电话,开始通话"。
- 5.recv/send(收发数据):通过 accept 返回的新 Socket 收发数据。
- 6.close(关闭):通信结束后关闭 Socket,释放资源。
import socket
def start_server():
# 第1步:创建TCP类型的Socket对象
# AF_INET 表示使用IPv4,SOCK_STREAM 表示使用TCP协议
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口复用(避免"Address already in use"错误)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 第2步:绑定IP和端口
host = '127.0.0.1' # 本机地址
port = 9999 # 端口号
server_socket.bind((host, port))
# 第3步:开始监听,最大等待队列长度为5
server_socket.listen(5)
print(f"服务器启动,监听 {host}:{port},等待客户端连接...")
# 第4步:接受客户端连接(阻塞,直到有客户端连接)
client_socket, client_addr = server_socket.accept()
print(f"客户端 {client_addr} 已连接")
# 第5步:收发数据
# 接收数据(最多接收1024字节)
data = client_socket.recv(1024).decode('utf-8')
print(f"收到客户端消息: {data}")
# 发送数据
response = f"服务器已收到消息: {data}"
client_socket.send(response.encode('utf-8'))
# 第6步:关闭连接
client_socket.close()
server_socket.close()
print("服务器已关闭")
if __name__ == '__main__':
start_server()
TCP 客户端构造流程与作用
TCP 客户端的工作流程是:创建Socket → 连接服务器 → 收发数据 → 关闭连接。
详细步骤说明:
- 1.创建 Client Socket 对象:创建一个 TCP 类型的 Socket 对象,相当于"买了一部电话机"。
- 2.connect(连接服务器):向指定的服务器 IP 和端口发起连接请求(会触发 TCP 三次握手)。相当于"拨打电话号码"。
- 3.send/recv(收发数据):连接建立后,通过 Socket 收发数据。
- 4.close(关闭):通信结束后关闭 Socket。
import socket
def start_client():
# 第1步:创建TCP类型的Socket对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 第2步:连接服务器(指定服务器的IP和端口)
server_host = '127.0.0.1'
server_port = 9999
client_socket.connect((server_host, server_port))
print(f"已连接到服务器 {server_host}:{server_port}")
# 第3步:发送数据
message = "你好,服务器!我是客户端。"
client_socket.send(message.encode('utf-8'))
print(f"已发送消息: {message}")
# 接收服务器的响应
response = client_socket.recv(1024).decode('utf-8')
print(f"服务器响应: {response}")
# 第4步:关闭连接
client_socket.close()
print("客户端已关闭")
if __name__ == '__main__':
start_client()
完整的多客户端服务端(循环版本)
上面的服务端只能处理一个客户端。实际应用中,服务端需要循环接受多个客户端连接:
import socket
def start_multi_server():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('127.0.0.1', 9999))
server_socket.listen(5)
print("服务器启动,等待连接...")
while True: # 循环接受新客户端
client_socket, addr = server_socket.accept()
print(f"客户端 {addr} 已连接")
while True: # 循环处理当前客户端的消息
try:
data = client_socket.recv(1024).decode('utf-8')
if not data or data.lower() == 'quit':
print(f"客户端 {addr} 断开连接")
break
print(f"[{addr}] {data}")
client_socket.send(f"收到: {data}".encode('utf-8'))
except ConnectionResetError:
print(f"客户端 {addr} 异常断开")
break
client_socket.close()
if __name__ == '__main__':
start_multi_server()
第十九章 多进程
19.1 什么是进程
进程(Process)是 操作系统进行资源分配和调度的基本单位。每个进程拥有独立的内存空间、代码段、数据段。简单理解:打开一个浏览器是一个进程,打开一个音乐播放器是另一个进程,它们之间互不干扰。
19.2 多进程编程
Python 的 multiprocessing 模块提供了创建和管理进程的 API。
import multiprocessing
import os
import time
def worker(name, seconds):
"""子进程执行的任务"""
pid = os.getpid()
ppid = os.getppid()
print(f"[子进程 {name}] PID={pid}, 父进程PID={ppid}, 开始工作")
time.sleep(seconds)
print(f"[子进程 {name}] 工作完成")
if __name__ == '__main__':
print(f"[主进程] PID={os.getpid()}")
# 创建子进程
# target: 要执行的函数名
# args: 位置参数(元组)
# kwargs: 关键字参数(字典)
p1 = multiprocessing.Process(target=worker, args=("下载", 2))
p2 = multiprocessing.Process(target=worker, args=("解析", 3))
# 启动子进程
p1.start()
p2.start()
print(f"[主进程] 等待子进程完成...")
# 等待子进程结束
p1.join()
p2.join()
print("[主进程] 所有子进程已完成")
19.3 进程间变量不共享
每个进程有独立的内存空间,因此 进程之间的变量互不影响。同一个变量名在不同进程中是完全不同的个体。
import multiprocessing
counter = 0 # 全局变量
def increment():
global counter
counter += 1
print(f"子进程 counter: {counter}")
if __name__ == '__main__':
p1 = multiprocessing.Process(target=increment)
p2 = multiprocessing.Process(target=increment)
p1.start()
p2.start()
p1.join()
p2.join()
print(f"主进程 counter: {counter}") # 仍然是0!进程间不共享变量
19.4 守护进程
守护进程会在主进程结束时自动终止,不需要手动管理。
import multiprocessing
import time
def daemon_task():
while True:
print("守护进程运行中...")
time.sleep(1)
if __name__ == '__main__':
p = multiprocessing.Process(target=daemon_task)
p.daemon = True # 设为守护进程
p.start()
time.sleep(3)
print("主进程结束,守护进程自动终止")
第二十章 多线程
20.1 什么是线程
线程(Thread)是 CPU 调度和执行的最小单位。一个进程可以包含多个线程,它们共享进程的内存空间(全局变量、文件描述符等)。线程切换的开销比进程小得多。
20.2 多线程编程
import threading
import time
def download(filename, seconds):
print(f"[线程 {threading.current_thread().name}] 开始下载 {filename}")
time.sleep(seconds)
print(f"[线程 {threading.current_thread().name}] {filename} 下载完成")
# 创建线程
t1 = threading.Thread(target=download, args=("文件A", 2), name="下载线程1")
t2 = threading.Thread(target=download, args=("文件B", 3), name="下载线程2")
# 启动线程
t1.start()
t2.start()
# 等待线程结束
t1.join()
t2.join()
print("所有下载完成")
20.3 线程间变量共享
线程共享同一进程的内存空间,因此 线程之间可以共享变量。但这同时带来了 线程安全问题。
import threading
counter = 0
def increment():
global counter
for _ in range(100000):
counter += 1
# 不加锁的情况下,结果可能不是预期的 500000
threads = []
for i in range(5):
t = threading.Thread(target=increment)
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"counter = {counter}") # 可能不是 500000!
20.4 互斥锁
互斥锁(Lock)用于保证同一时刻只有一个线程访问共享资源,解决线程安全问题。
import threading
counter = 0
lock = threading.Lock() # 创建锁对象
def safe_increment():
global counter
for _ in range(100000):
lock.acquire() # 上锁(如果锁已被其他线程持有,则阻塞等待)
try:
counter += 1
finally:
lock.release() # 释放锁(务必在 finally 中释放,防止异常导致死锁)
# 或者使用 with 语句(推荐,自动管理锁的获取和释放)
def safe_increment_v2():
global counter
for _ in range(100000):
with lock: # 自动 acquire 和 release
counter += 1
threads = []
for i in range(5):
t = threading.Thread(target=safe_increment_v2)
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"counter = {counter}") # 500000(正确结果)
20.5 进程 vs 线程
| 对比项 | 进程 | 线程 |
|---|---|---|
| 定义 | 资源分配的最小单位 | CPU调度的最小单位 |
| 内存 | 独立内存空间 | 共享进程内存 |
| 创建开销 | 大 | 小 |
| 切换速度 | 慢 | 快 |
| 通信 | 需要特殊机制(管道、队列) | 直接读写共享变量 |
| 稳定性 | 一个进程崩溃不影响其他进程 | 一个线程崩溃可能导致整个进程崩溃 |
| GIL限制 | 不受GIL限制,可利用多核 | 受GIL限制,CPU密集型无法利用多核 |
| 适用场景 | CPU密集型任务 | I/O密集型任务 |
第二十一章 迭代器与生成器
21.1 迭代器
迭代器是一个实现了 __iter__() 和 __next__() 方法的对象。它支持 惰性计算(用到时才产生数据),不会一次性将所有数据加载到内存中,适合处理海量数据。
class MyRange:
"""自定义迭代器:模拟 range()"""
def __init__(self, start, end, step=1):
self.start = start
self.end = end
self.step = step
self.current = start
def __iter__(self):
return self # 返回迭代器对象自身
def __next__(self):
if (self.step > 0 and self.current >= self.end) or \
(self.step < 0 and self.current <= self.end):
raise StopIteration # 没有更多元素时抛出此异常
value = self.current
self.current += self.step
return value
# 使用
for num in MyRange(1, 10, 2):
print(num, end=" ") # 1 3 5 7 9
print()
# 用 next() 手动获取
it = MyRange(0, 5)
print(next(it)) # 0
print(next(it)) # 1
print(next(it)) # 2
21.2 生成器
生成器是一种更简便的创建迭代器的方式。有两种创建方式:生成器推导式 和 yield 关键字。
生成器推导式
# 列表推导式:立即生成所有元素,占用内存
squares_list = [x**2 for x in range(1000000)] # 占用大量内存
# 生成器推导式:惰性生成,用到时才计算,节省内存
squares_gen = (x**2 for x in range(1000000)) # 几乎不占内存
print(type(squares_gen)) # <class 'generator'>
print(next(squares_gen)) # 0
print(next(squares_gen)) # 1
yield 关键字
yield 的作用:
- 1.返回一个值给调用者。
- 2.暂停函数执行,保存当前状态。
- 3.下次调用
next()时,从上次暂停的位置继续执行。 - 4.将普通函数变成生成器函数,返回生成器对象。
def countdown(n):
"""生成器函数:倒数"""
print("开始倒数!")
while n > 0:
yield n # 返回 n,然后暂停
n -= 1 # 下次从此处继续
print("倒数结束!")
gen = countdown(5)
print(type(gen)) # <class 'generator'>
# 用 next() 逐个获取
print(next(gen)) # 开始倒数! \n 5
print(next(gen)) # 4
print(next(gen)) # 3
# 用 for 循环遍历剩余元素
for num in gen:
print(num, end=" ") # 2 1 \n 倒数结束!
21.3 迭代器 vs 生成器
| 对比项 | 迭代器 | 生成器 |
|---|---|---|
| 实现方式 | 手动实现 __iter__ + __next__ | 生成器推导式或 yield |
| 代码复杂度 | 较复杂 | 简洁 |
| 灵活性 | 最灵活 | 足够灵活 |
| 推荐程度 | 特殊场景使用 | 推荐! |
| 共同点 | 惰性计算、节省内存、适合海量数据遍历 |
第二十二章 Property 属性
22.1 作用
property 将方法伪装成属性,使得访问私有属性时可以像访问普通属性一样简洁,同时可以在读取和修改时加入校验逻辑。
22.2 实现方式一:装饰器
class Person:
def __init__(self, name, age):
self.name = name
self.__age = age # 私有属性
# @property 装饰的方法就是 getter(读取属性)
@property
def age(self):
print("正在获取 age...")
return self.__age
# @属性名.setter 装饰的方法就是 setter(修改属性)
@age.setter
def age(self, value):
print("正在设置 age...")
if not isinstance(value, int) or value < 0 or value > 150:
raise ValueError("年龄必须是0-150之间的整数")
self.__age = value
p = Person("Alice", 25)
# 像访问普通属性一样使用(实际上调用了 getter 方法)
print(p.age) # 正在获取 age... \n 25
# 像修改普通属性一样使用(实际上调用了 setter 方法)
p.age = 30 # 正在设置 age...
print(p.age) # 正在获取 age... \n 30
# p.age = -5 # 报错 ValueError: 年龄必须是0-150之间的整数
22.3 实现方式二:类属性
class Circle:
def __init__(self, radius):
self.__radius = radius
def get_radius(self):
return self.__radius
def set_radius(self, value):
if value <= 0:
raise ValueError("半径必须为正数")
self.__radius = value
# 用 property() 函数将方法绑定为属性
radius = property(get_radius, set_radius)
c = Circle(5)
print(c.radius) # 5(调用 get_radius)
c.radius = 10 # 调用 set_radius
print(c.radius) # 10
第二十三章 正则表达式
正则表达式(Regular Expression)是一种用于 匹配字符串中字符组合的模式,广泛用于文本搜索、替换、验证等场景。Python 通过 re 模块提供正则表达式支持。
23.1 常用匹配规则
| 模式 | 含义 | 示例 |
|---|---|---|
. | 匹配任意单个字符(除换行符) | a.c 匹配 abc, a1c |
\d | 匹配数字 [0-9] | \d\d 匹配 12, 99 |
\w | 匹配字母、数字、下划线 [a-zA-Z0-9_] | \w+ 匹配 hello_123 |
\s | 匹配空白字符(空格、Tab、换行) | |
^ | 匹配字符串开头 | ^Hello 匹配以 Hello 开头的字符串 |
$ | 匹配字符串结尾 | world$ 匹配以 world 结尾的字符串 |
* | 前一个字符出现 0 次或多次 | ab*c 匹配 ac, abc, abbc |
+ | 前一个字符出现 1 次或多次 | ab+c 匹配 abc, abbc |
? | 前一个字符出现 0 次或 1 次 | ab?c 匹配 ac, abc |
{n} | 前一个字符恰好出现 n 次 | \d{3} 匹配 123 |
{n,m} | 前一个字符出现 n 到 m 次 | \d{2,4} 匹配 12, 123, 1234 |
[] | 字符集,匹配其中任意一个 | [abc] 匹配 a, b, c |
() | 分组 | (ab)+ 匹配 ab, abab |
| | 或 | cat|dog 匹配 cat 或 dog |
23.2 常用函数
import re
text = "我的电话是13812345678,邮箱是[email protected],另一个邮箱 [email protected]"
# ===== re.match:从字符串开头匹配 =====
result = re.match(r'\d+', "123abc")
print(result.group() if result else "未匹配") # 123
result = re.match(r'\d+', "abc123")
print(result) # None(开头不是数字)
# ===== re.search:从任意位置匹配(找到第一个) =====
result = re.search(r'1[3-9]\d{9}', text)
if result:
print(f"找到手机号: {result.group()}") # 13812345678
# ===== re.findall:找到所有匹配项,返回列表 =====
emails = re.findall(r'\w+@\w+\.\w+', text)
print(f"找到的邮箱: {emails}") # ['[email protected]', 'admin@python']
# ===== re.sub:替换 =====
result = re.sub(r'\d+', '*', text)
print(result) # 我的电话是*,邮箱是*@*.com,另一个邮箱 *@*.*
# ===== re.compile:预编译正则(多次使用同一正则时推荐) =====
pattern = re.compile(r'1[3-9]\d{9}')
result = pattern.search(text)
print(result.group()) # 13812345678
23.3 分组匹配
import re
text = "2024-01-15"
# 用 () 定义分组
match = re.match(r'(\d{4})-(\d{2})-(\d{2})', text)
if match:
print(match.group(0)) # 2024-01-15(整个匹配)
print(match.group(1)) # 2024(第1个分组)
print(match.group(2)) # 01(第2个分组)
print(match.group(3)) # 15(第3个分组)
第二十四章 常用内置函数补充
24.1 map(function, iterable)
将指定函数应用于可迭代对象的每一个元素,返回一个 迭代器。常用于批量数据转换。
# 将每个数字转为它的平方
numbers = [1, 2, 3, 4, 5]
squared = map(lambda x: x**2, numbers)
print(list(squared)) # [1, 4, 9, 16, 25]
# 将字符串列表转为整数列表
str_list = ['1', '2', '3', '4']
int_list = list(map(int, str_list))
print(int_list) # [1, 2, 3, 4]
# 多个可迭代对象并行处理
a = [1, 2, 3]
b = [10, 20, 30]
result = list(map(lambda x, y: x + y, a, b))
print(result) # [11, 22, 33]
24.2 filter(function, iterable)
过滤可迭代对象,只保留使函数返回 True 的元素,返回一个 迭代器。
# 筛选偶数
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4, 6, 8, 10]
# 筛选非空字符串
words = ["hello", "", "world", None, "python", ""]
non_empty = list(filter(None, words)) # None 作为函数会过滤掉布尔值为False的元素
print(non_empty) # ['hello', 'world', 'python']
# 筛选长度大于3的单词
words = ["hi", "hello", "ok", "python", "go"]
long_words = list(filter(lambda w: len(w) > 3, words))
print(long_words) # ['hello', 'python']
24.3 zip(*iterables)
将多个可迭代对象中 对应位置 的元素打包成元组,返回一个迭代器。以最短的可迭代对象为准。
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
cities = ["Beijing", "Shanghai", "Shenzhen"]
# 打包为元组列表
zipped = list(zip(names, ages, cities))
print(zipped)
# [('Alice', 25, 'Beijing'), ('Bob', 30, 'Shanghai'), ('Charlie', 35, 'Shenzhen')]
# 常用于同时遍历多个列表
for name, age in zip(names, ages):
print(f"{name} is {age} years old")
# 配合 dict() 快速创建字典
person_dict = dict(zip(names, ages))
print(person_dict) # {'Alice': 25, 'Bob': 30, 'Charlie': 35}
# 解包(zip 的逆操作)
pairs = [('a', 1), ('b', 2), ('c', 3)]
letters, numbers = zip(*pairs)
print(letters) # ('a', 'b', 'c')
print(numbers) # (1, 2, 3)
24.4 enumerate(iterable, start=0)
为可迭代对象添加索引,返回 (索引, 元素) 的迭代器。比手动维护计数器更优雅。
fruits = ["apple", "banana", "cherry"]
# 不用 enumerate(手动维护索引)
for i in range(len(fruits)):
print(f"{i}: {fruits[i]}")
# 用 enumerate(推荐)
for index, fruit in enumerate(fruits):
print(f"{index}: {fruit}")
# 指定起始索引
for index, fruit in enumerate(fruits, start=1):
print(f"{index}. {fruit}")
# 1. apple
# 2. banana
# 3. cherry
第二十五章 数据结构与算法基础
25.1 基本概念
- 算法(Algorithm):解决问题的方法和步骤。算法是独立于编程语言的,同一个算法可以用 Python、Java、C++ 等任何语言实现。
- 数据结构(Data Structure):存储和组织数据的方式。不同的数据结构适用于不同的场景。
- 关系:算法依赖于数据结构。选择合适的数据结构可以让算法更高效。
25.2 时间复杂度
时间复杂度描述的是算法执行时间随输入规模增长的变化趋势,而不是具体的执行时间。
计算规则:
- 常量操作:
O(1),如赋值、算术运算。 - 顺序代码:各步骤时间复杂度 相加,取最高阶。
- 循环代码:循环次数 × 循环体时间复杂度,相乘。
- 嵌套循环:各层循环次数相乘。
- 分支结构:取各分支中 时间复杂度最大 的。
- 大O记法:只保留 最高阶项,去掉常量系数和低阶项。
# O(1) —— 常量时间
def get_first(lst):
return lst[0] # 无论列表多长,执行时间不变
# O(n) —— 线性时间
def sum_all(lst):
total = 0
for item in lst: # 遍历 n 次
total += item
return total
# O(n²) —— 平方时间
def bubble_sort(lst):
n = len(lst)
for i in range(n): # 外层 n 次
for j in range(n-1): # 内层 n 次
if lst[j] > lst[j+1]:
lst[j], lst[j+1] = lst[j+1], lst[j]
# O(log n) —— 对数时间
def binary_search(lst, target):
low, high = 0, len(lst) - 1
while low <= high:
mid = (low + high) // 2
if lst[mid] == target:
return mid
elif lst[mid] < target:
low = mid + 1
else:
high = mid - 1
return -1
常见时间复杂度(从快到慢):
| 复算度 | 名称 | 示例 |
|---|---|---|
O(1) | 常量时间 | 数组按索引访问、哈希表查找 |
O(log n) | 对数时间 | 二分查找 |
O(n) | 线性时间 | 遍历数组、线性查找 |
O(n log n) | 线性对数时间 | 归并排序、快速排序(平均) |
O(n²) | 平方时间 | 冒泡排序、选择排序、插入排序 |
O(n³) | 立方时间 | 三层嵌套循环 |
O(2^n) | 指数时间 | 递归求斐波那契(未优化) |
O(n!) | 阶乘时间 | 全排列 |
25.3 空间复杂度
空间复杂度描述的是算法运行过程中 临时占用的存储空间 随输入规模增长的变化趋势。
| 复杂度 | 含义 |
|---|---|
O(1) | 只使用常量级额外空间 |
O(n) | 使用与输入规模成正比的额外空间 |
O(n²) | 使用二维数组等 |
25.4 数据结构分类
线性结构:元素之间存在一对一的关系。
- 顺序表(数组)、链表、栈、队列。
非线性结构:元素之间存在一对多或多对多的关系。
- 树(二叉树、二叉搜索树)、图、堆。
第二十六章 排序算法大全
26.1 冒泡排序(Bubble Sort)
原理:从左到右,相邻两个元素两两比较,如果左边大于右边就交换。每一轮将当前未排序部分的最大值"冒泡"到最右侧。
步骤:
- 1.比较相邻的两个元素,如果前一个大于后一个,交换它们。
- 2.对每一对相邻元素执行同样的操作,从开始到结尾。一轮结束后,最大的元素在最右边。
- 3.对除了最后一个元素外的所有元素重复以上步骤。
- 4.持续每次减少一轮的重复次数,直到没有需要比较的元素。
def bubble_sort(arr):
n = len(arr)
for i in range(n - 1): # 需要 n-1 轮
for j in range(0, n - i - 1): # 每轮比较次数递减
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr
# 优化版:如果某一轮没有发生交换,说明已经有序,提前结束
def bubble_sort_optimized(arr):
n = len(arr)
for i in range(n - 1):
swapped = False
for j in range(0, n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
swapped = True
if not swapped: # 没有发生交换,已有序
break
return arr
print(bubble_sort([64, 34, 25, 12, 22, 11, 90]))
# [11, 12, 22, 25, 34, 64, 90]
- 时间复杂度:最坏
O(n²),最好O(n)(优化版,已有序时)。 - 空间复杂度:
O(1)。 - 稳定性:稳定(相等元素的相对位置不变)。
26.2 选择排序(Selection Sort)
原理:每一轮从未排序部分找到最小元素,放到已排序部分的末尾(即未排序部分的开头)。
def selection_sort(arr):
n = len(arr)
for i in range(n - 1):
min_idx = i # 假设当前位置的元素最小
for j in range(i + 1, n): # 在未排序部分找真正的最小值
if arr[j] < arr[min_idx]:
min_idx = j
arr[i], arr[min_idx] = arr[min_idx], arr[i] # 将最小值放到位置i
return arr
print(selection_sort([64, 25, 12, 22, 11]))
# [11, 12, 22, 25, 64]
- 时间复杂度:
O(n²)(无论最好最坏)。 - 空间复杂度:
O(1)。 - 稳定性:不稳定。
26.3 插入排序(Insertion Sort)
原理:将数组分为已排序和未排序两部分。每次从未排序部分取出第一个元素,在已排序部分从后向前扫描,找到合适位置插入。
def insertion_sort(arr):
for i in range(1, len(arr)): # 从第2个元素开始
key = arr[i] # 待插入的元素
j = i - 1
# 在已排序部分从后向前找插入位置
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j] # 元素后移
j -= 1
arr[j + 1] = key # 插入到正确位置
return arr
print(insertion_sort([12, 11, 13, 5, 6]))
# [5, 6, 11, 12, 13]
- 时间复杂度:最坏
O(n²),最好O(n)(已有序时)。 - 空间复杂度:
O(1)。 - 稳定性:稳定。
26.4 快速排序(Quick Sort)
原理:选择一个基准元素(pivot),将数组分为两部分——小于基准的放左边,大于基准的放右边,然后递归地对左右两部分排序。
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2] # 选择中间元素作为基准
left = [x for x in arr if x < pivot] # 小于基准的
middle = [x for x in arr if x == pivot] # 等于基准的
right = [x for x in arr if x > pivot] # 大于基准的
return quick_sort(left) + middle + quick_sort(right)
print(quick_sort([3, 6, 8, 10, 1, 2, 1]))
# [1, 1, 2, 3, 6, 8, 10]
- 时间复杂度:平均
O(n log n),最坏O(n²)(基准选择不好时)。 - 空间复杂度:
O(n log n)(递归调用栈)。 - 稳定性:不稳定。
26.5 归并排序(Merge Sort)
原理:将数组递归地分成两半,分别排序,然后合并两个有序数组。
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid]) # 递归排序左半部分
right = merge_sort(arr[mid:]) # 递归排序右半部分
return merge(left, right) # 合并两个有序数组
def merge(left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:]) # 剩余元素
result.extend(right[j:])
return result
print(merge_sort([38, 27, 43, 3, 9, 82, 10]))
# [3, 9, 10, 27, 38, 43, 82]
- 时间复杂度:
O(n log n)(最好、最坏、平均都一样)。 - 空间复杂度:
O(n)。 - 稳定性:稳定。
26.6 堆排序(Heap Sort)
原理:利用堆(完全二叉树)这种数据结构。先将数组构建为大顶堆,然后将堆顶元素(最大值)与末尾交换,缩小堆的范围,重复此过程。
def heap_sort(arr):
n = len(arr)
# 构建大顶堆
for i in range(n // 2 - 1, -1, -1):
heapify(arr, n, i)
# 逐个提取最大值
for i in range(n - 1, 0, -1):
arr[0], arr[i] = arr[i], arr[0] # 将最大值放到末尾
heapify(arr, i, 0) # 对剩余部分重新构建堆
def heapify(arr, n, i):
largest = i
left = 2 * i + 1
right = 2 * i + 2
if left < n and arr[left] > arr[largest]:
largest = left
if right < n and arr[right] > arr[largest]:
largest = right
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
heapify(arr, n, largest)
data = [12, 11, 13, 5, 6, 7]
heap_sort(data)
print(data) # [5, 6, 7, 11, 12, 13]
- 时间复杂度:
O(n log n)。 - 空间复杂度:
O(1)。 - 稳定性:不稳定。
26.7 排序算法对比总结
| 算法 | 平均时间复杂度 | 最好 | 最坏 | 空间复杂度 | 稳定性 |
|---|---|---|---|---|---|
| 冒泡排序 | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
| 选择排序 | O(n²) | O(n²) | O(n²) | O(1) | 不稳定 |
| 插入排序 | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
| 快速排序 | O(n log n) | O(n log n) | O(n²) | O(log n) | 不稳定 |
| 归并排序 | O(n log n) | O(n log n) | O(n log n) | O(n) | 稳定 |
| 堆排序 | O(n log n) | O(n log n) | O(n log n) | O(1) | 不稳定 |
稳定性:排序后,相同值的元素的相对位置不变。
第二十七章 查找算法
27.1 线性查找(Linear Search)
原理:从头到尾逐个遍历,找到目标值就返回索引,遍历完没找到就返回 -1。
def linear_search(arr, target):
for i in range(len(arr)):
if arr[i] == target:
return i
return -1
print(linear_search([10, 23, 45, 70, 11, 15], 70)) # 3
print(linear_search([10, 23, 45, 70, 11, 15], 99)) # -1
- 时间复杂度:
O(n)。 - 适用场景:无序数组、小规模数据。
27.2 二分查找(Binary Search)
原理:要求数组 有序。每次取中间元素与目标值比较:
- 相等 → 找到,返回索引。
- 目标值 < 中间值 → 在左半部分继续查找。
- 目标值 > 中间值 → 在右半部分继续查找。
def binary_search(arr, target):
low, high = 0, len(arr) - 1
while low <= high:
mid = (low + high) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
low = mid + 1 # 目标在右半部分
else:
high = mid - 1 # 目标在左半部分
return -1
sorted_list = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]
print(binary_search(sorted_list, 23)) # 5
print(binary_search(sorted_list, 100)) # -1
- 时间复杂度:
O(log n)。 - 适用场景:有序数组、大规模数据。
27.3 递归实现二分查找
def binary_search_recursive(arr, target, low, high):
if low > high:
return -1
mid = (low + high) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
return binary_search_recursive(arr, target, mid + 1, high)
else:
return binary_search_recursive(arr, target, low, mid - 1)
sorted_list = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]
print(binary_search_recursive(sorted_list, 23, 0, len(sorted_list)-1)) # 5
第二十八章 链表
28.1 什么是链表
链表是一种 线性数据结构,由一系列节点组成,每个节点包含 数据域(存储数据)和 链接域(存储下一个节点的地址)。
与数组(顺序表)的对比:
| 对比项 | 数组(顺序表) | 链表 |
|---|---|---|
| 内存 | 连续内存 | 分散内存 |
| 访问 | 按索引 O(1) | 需要遍历 O(n) |
| 插入/删除 | 需要移动元素 O(n) | 只需修改指针 O(1) |
| 空间利用 | 可能浪费 | 按需分配 |
28.2 节点类
class Node:
"""链表节点"""
def __init__(self, item):
self.item = item # 数据域
self.next = None # 链接域(指向下一个节点)
28.3 链表类
class LinkedList:
def __init__(self):
self.head = None # 头节点
def is_empty(self):
"""判断链表是否为空"""
return self.head is None
def length(self):
"""获取链表长度"""
count = 0
cur = self.head
while cur:
count += 1
cur = cur.next
return count
def travel(self):
"""遍历链表"""
elements = []
cur = self.head
while cur:
elements.append(cur.item)
cur = cur.next
print(elements)
def add(self, item):
"""头部添加节点"""
node = Node(item)
node.next = self.head
self.head = node
def append(self, item):
"""尾部添加节点"""
node = Node(item)
if self.is_empty():
self.head = node
else:
cur = self.head
while cur.next:
cur = cur.next
cur.next = node
def insert(self, pos, item):
"""在指定位置插入节点"""
if pos <= 0:
self.add(item)
elif pos >= self.length():
self.append(item)
else:
node = Node(item)
cur = self.head
for _ in range(pos - 1):
cur = cur.next
node.next = cur.next
cur.next = node
def remove(self, item):
"""删除第一个匹配的节点"""
cur = self.head
pre = None
while cur:
if cur.item == item:
if pre is None: # 删除的是头节点
self.head = cur.next
else:
pre.next = cur.next
return
pre = cur
cur = cur.next
def search(self, item):
"""查找元素是否存在"""
cur = self.head
while cur:
if cur.item == item:
return True
cur = cur.next
return False
# 测试
ll = LinkedList()
ll.append(1)
ll.append(2)
ll.append(3)
ll.add(0) # 头部添加
ll.insert(2, 1.5) # 在索引2处插入
ll.travel() # [0, 1, 1.5, 2, 3]
ll.remove(1.5)
ll.travel() # [0, 1, 2, 3]
print(ll.length()) # 4
print(ll.search(2)) # True
第二十九章 二叉树
29.1 什么是二叉树
二叉树是一种 非线性数据结构,每个节点最多有两个子节点(左子节点和右子节点)。
相关术语:
- 根节点:树的最顶层节点,没有父节点。
- 叶子节点:没有子节点的节点。
- 深度:从根节点到该节点的路径长度。
- 满二叉树:每一层的节点数都达到最大值。
- 完全二叉树:除了最后一层,其他层都是满的,最后一层的节点都靠左排列。
29.2 节点类与二叉树类
class BinaryNode:
"""二叉树节点"""
def __init__(self, item):
self.item = item # 数据域
self.lchild = None # 左子节点
self.rchild = None # 右子节点
class BinaryTree:
"""二叉树"""
def __init__(self, root=None):
self.root = root
29.3 广度优先遍历(层序遍历)
原理:从上到下、从左到右,一层一层地遍历所有节点。使用 队列(先进先出)辅助实现。
from collections import deque
class BinaryTree:
def __init__(self, root=None):
self.root = root
def breadth_first_traversal(self):
"""广度优先遍历(层序遍历)"""
if not self.root:
return
queue = deque([self.root]) # 使用队列
result = []
while queue:
node = queue.popleft() # 取出队首节点
result.append(node.item)
if node.lchild:
queue.append(node.lchild) # 左子节点入队
if node.rchild:
queue.append(node.rchild) # 右子节点入队
return result
def add(self, item):
"""广度优先方式添加节点(构建完全二叉树)"""
node = BinaryNode(item)
if not self.root:
self.root = node
return
queue = deque([self.root])
while queue:
cur = queue.popleft()
if not cur.lchild:
cur.lchild = node
return
elif not cur.rchild:
cur.rchild = node
return
else:
queue.append(cur.lchild)
queue.append(cur.rchild)
# 测试
bt = BinaryTree()
for i in [1, 2, 3, 4, 5, 6, 7]:
bt.add(i)
# 构建的树:
# 1
# / \
# 2 3
# / \ / \
# 4 5 6 7
print("广度优先遍历:", bt.breadth_first_traversal())
# [1, 2, 3, 4, 5, 6, 7]
29.4 深度优先遍历
深度优先遍历有三种方式:先序遍历、中序遍历、后序遍历。区别在于访问根节点的时机。
先序遍历(根 → 左 → 右)
先访问根节点,再递归遍历左子树,最后递归遍历右子树。
def pre_order(self, node):
"""先序遍历:根 → 左 → 右"""
if node:
print(node.item, end=" ") # 先访问根
self.pre_order(node.lchild) # 再遍历左子树
self.pre_order(node.rchild) # 最后遍历右子树
# 在 BinaryTree 类中添加此方法,调用方式:
# bt.pre_order(bt.root)
# 输出: 1 2 4 5 3 6 7
中序遍历(左 → 根 → 右)
先递归遍历左子树,再访问根节点,最后递归遍历右子树。
def in_order(self, node):
"""中序遍历:左 → 根 → 右"""
if node:
self.in_order(node.lchild) # 先遍历左子树
print(node.item, end=" ") # 再访问根
self.in_order(node.rchild) # 最后遍历右子树
# 调用方式:
# bt.in_order(bt.root)
# 输出: 4 2 5 1 6 3 7
后序遍历(左 → 右 → 根)
先递归遍历左子树,再递归遍历右子树,最后访问根节点。
def post_order(self, node):
"""后序遍历:左 → 右 → 根"""
if node:
self.post_order(node.lchild) # 先遍历左子树
self.post_order(node.rchild) # 再遍历右子树
print(node.item, end=" ") # 最后访问根
# 调用方式:
# bt.post_order(bt.root)
# 输出: 4 5 2 6 7 3 1
完整测试
# 在 BinaryTree 类中添加以上三个方法后:
print("先序遍历:", end=" ")
bt.pre_order(bt.root) # 1 2 4 5 3 6 7
print()
print("中序遍历:", end=" ")
bt.in_order(bt.root) # 4 2 5 1 6 3 7
print()
print("后序遍历:", end=" ")
bt.post_order(bt.root) # 4 5 2 6 7 3 1
print()
29.5 二叉搜索树(BST)
二叉搜索树是一种特殊的二叉树:左子树上所有节点的值 < 根节点的值 < 右子树上所有节点的值。这使得查找、插入、删除的平均时间复杂度为 O(log n)。
class BSTNode:
def __init__(self, key):
self.key = key
self.lchild = None
self.rchild = None
class BST:
def __init__(self):
self.root = None
def insert(self, key):
"""插入节点"""
if not self.root:
self.root = BSTNode(key)
else:
self._insert(self.root, key)
def _insert(self, node, key):
if key < node.key:
if node.lchild:
self._insert(node.lchild, key)
else:
node.lchild = BSTNode(key)
elif key > node.key:
if node.rchild:
self._insert(node.rchild, key)
else:
node.rchild = BSTNode(key)
# key == node.key 时不插入(不允许重复)
def search(self, key):
"""查找节点"""
return self._search(self.root, key)
def _search(self, node, key):
if not node:
return False
if key == node.key:
return True
elif key < node.key:
return self._search(node.lchild, key)
else:
return self._search(node.rchild, key)
def in_order(self, node):
"""中序遍历BST,结果是有序的"""
if node:
self.in_order(node.lchild)
print(node.key, end=" ")
self.in_order(node.rchild)
# 测试
bst = BST()
for key in [5, 3, 7, 1, 4, 6, 8]:
bst.insert(key)
# 构建的BST:
# 5
# / \
# 3 7
# / \ / \
# 1 4 6 8
print("中序遍历(有序):", end=" ")
bst.in_order(bst.root) # 1 3 4 5 6 7 8
print()
print("查找 4:", bst.search(4)) # True
print("查找 9:", bst.search(9)) # False
第三十章 栈与队列
30.1 栈(Stack)
栈是一种 后进先出(LIFO, Last In First Out) 的数据结构。就像一摞盘子,最后放上去的最先被拿走。
核心操作:push(入栈)、pop(出栈)、peek(查看栈顶)。
class Stack:
def __init__(self):
self.items = []
def push(self, item):
"""入栈"""
self.items.append(item)
def pop(self):
"""出栈(移除并返回栈顶元素)"""
if self.is_empty():
raise IndexError("栈为空")
return self.items.pop()
def peek(self):
"""查看栈顶元素(不移除)"""
if self.is_empty():
raise IndexError("栈为空")
return self.items[-1]
def is_empty(self):
return len(self.items) == 0
def size(self):
return len(self.items)
# 测试
s = Stack()
s.push(1)
s.push(2)
s.push(3)
print(s.pop()) # 3(最后入栈的最先出栈)
print(s.peek()) # 2
print(s.size()) # 2
应用场景:括号匹配、函数调用栈、表达式求值、浏览器前进后退。
30.2 队列(Queue)
队列是一种 先进先出(FIFO, First In First Out) 的数据结构。就像排队买票,先来的人先买。
核心操作:enqueue(入队)、dequeue(出队)。
from collections import deque
class Queue:
def __init__(self):
self.items = deque()
def enqueue(self, item):
"""入队(在队尾添加元素)"""
self.items.append(item)
def dequeue(self):
"""出队(从队首移除并返回元素)"""
if self.is_empty():
raise IndexError("队列为空")
return self.items.popleft()
def front(self):
"""查看队首元素"""
if self.is_empty():
raise IndexError("队列为空")
return self.items[0]
def is_empty(self):
return len(self.items) == 0
def size(self):
return len(self.items)
# 测试
q = Queue()
q.enqueue("A")
q.enqueue("B")
q.enqueue("C")
print(q.dequeue()) # A(最先入队的最先出队)
print(q.front()) # B
print(q.size()) # 2
应用场景:任务调度、消息队列、二叉树层序遍历、广度优先搜索(BFS)。
总结:本文档系统梳理了 Python 从基础语法到高级特性、从数据结构到常用算法的完整知识体系。相比原始笔记,补充了 map/filter/zip/enumerate 高阶函数、文件操作、异常处理、模块与包、Socket 网络编程详细流程、多进程/多线程对比、快速排序/归并排序/堆排序、二叉搜索树、栈与队列 等重要内容。
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/weixin_69500620/article/details/159279801



