关注

Python 全栈系统学习笔记(超详细版)

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. 1.访问 Python 官网 下载对应操作系统的安装包。
  2. 2.安装时 务必勾选 "Add Python to PATH",否则命令行无法直接使用 python 命令。
  3. 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. 1.变量名由 字母、数字、下划线 组成。
  2. 2.不能以数字开头1name 非法,name1 合法)。
  3. 3.大小写敏感nameNameNAME 是三个不同的变量。
  4. 4.不能使用 Python 关键字(如 ifforclassdef 等),在 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 优于 unmax_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 是动态类型语言,变量不需要提前声明类型,赋值时自动推断。

类型关键字示例说明
整型inta = 10整数,无大小限制
浮点型floatb = 3.14带小数点的数字
字符串strc = "hello"文本数据,用引号包裹
布尔型boold = True只有 TrueFalse 两个值
空值NoneTypee = 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 + 25
-3 - 21
*3 * 26
/除(结果为浮点)7 / 23.5
//整除(向下取整)7 // 23
%取模(取余数)7 % 21
**幂运算2 ** 38
()改变运算优先级(1+2)*39

# 特殊用法:字符串与整数相乘 = 重复
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 比较运算符

比较运算符用于比较两个值,返回布尔值TrueFalse

运算符含义示例
==等于(注意:和SQL不同,Python用 == 判断相等,= 是赋值)3 == 3True
!=不等于3 != 4True
>大于5 > 3True
<小于5 < 3False
>=大于等于3 >= 3True
<=小于等于3 <= 2False
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取反,TrueFalseFalseTrue
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 whilefor 的选择

  • 优先使用 for:语法简洁,不容易出错。
    • 遍历序列(字符串、列表、元组)时。
    • 循环固定次数时。
  • 其次使用 while:更灵活。
    • 循环次数不确定时(如等待用户输入正确密码)。
    • 需要复杂的循环条件时。

3.5 无限循环

# 无限循环,需要配合 break 退出
while True:
    password = input("请输入密码:")
    if password == "123456":
        print("密码正确!")
        break  # 跳出循环
    else:
        print("密码错误,请重试。")

3.6 breakcontinue

  • 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 开始。
  • 可重复:同一个字符可以出现多次。
  • 不可变:字符串创建后不能修改其中某个字符。所有修改操作(replacestripupper 等)都会返回一个新的字符串副本,原字符串不变。

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 组成。
  • 键不可变:键必须是不可变类型(intfloatstrbooltuple),不能是列表或字典。
  • 键不可重复:重复的键后面的值会覆盖前面的。值可以重复。
  • 可变:可以增、删、改键值对。

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 之后的代码 不会被执行
  • 可以返回多个值(实际返回一个元组)。
  • 如果没有 returnreturn 后面没有值,函数返回 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. 1.提高代码健壮性:程序不会因为一个错误就完全崩溃。
  2. 2.提供补救措施:出错时可以执行备用方案(如重试、使用默认值、记录日志等)。
  3. 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()
sysPython 解释器相关sys.argv, sys.path
math数学函数math.sqrt(), math.pi
random随机数生成random.randint(), random.choice()
datetime日期和时间datetime.datetime.now()
jsonJSON 编解码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. 1.解耦合:调用者不需要知道具体是哪个子类,只需要知道它有 speak() 方法。
  2. 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. 1.函数嵌套(函数内部定义另一个函数)。
  2. 2.内部函数引用了外部函数的变量。
  3. 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
  • 特殊 IP127.0.0.1(或 localhost)代表本机。
端口号

一台计算机上可能同时运行多个网络程序(浏览器、QQ、游戏等)。端口号用于区分同一台计算机上的不同程序。范围是 0-65535,其中 0-1023 是系统保留端口。

协议

协议是通信双方约定的规则,就像两个人交流需要说同一种语言。

TCP(Transmission Control Protocol,传输控制协议)

  • 可靠传输:数据不会丢失、不会乱序。
  • 面向连接:通信前需要先建立连接(三次握手),通信结束后断开连接(四次挥手)。
  • 适用场景:文件传输、网页浏览、邮件等对数据完整性要求高的场景。

TCP 三次握手(建立连接):

  1. 1.客户端发送 SYN(同步请求)给服务端:"我想连接你"。
  2. 2.服务端回复 SYN+ACK(同步确认):"好的,我也想连接你"。
  3. 3.客户端发送 ACK(确认):"确认,连接建立"。

TCP 四次挥手(断开连接):

  1. 1.客户端发送 FIN(结束请求):"我说完了"。
  2. 2.服务端回复 ACK(确认):"好的,我知道了"。
  3. 3.服务端发送 FIN(结束请求):"我也说完了"。
  4. 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. 1.创建 Server Socket 对象:创建一个 TCP 类型的 Socket 对象,相当于"买了一部电话机"。
  2. 2.bind(绑定):将 Socket 绑定到指定的 IP 地址和端口号,相当于"给电话机装上电话号码"。客户端通过这个 IP+端口 找到服务端。
  3. 3.listen(监听):将 Socket 设为监听模式,开始等待客户端的连接请求。参数指定等待队列的最大长度(最大128),相当于"电话机开机,等待来电"。
  4. 4.accept(接受连接):阻塞等待,直到有客户端连接。成功后返回一个新的 Socket 对象(专门用于和这个客户端通信)和客户端地址。相当于"接起电话,开始通话"。
  5. 5.recv/send(收发数据):通过 accept 返回的新 Socket 收发数据。
  6. 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. 1.创建 Client Socket 对象:创建一个 TCP 类型的 Socket 对象,相当于"买了一部电话机"。
  2. 2.connect(连接服务器):向指定的服务器 IP 和端口发起连接请求(会触发 TCP 三次握手)。相当于"拨打电话号码"。
  3. 3.send/recv(收发数据):连接建立后,通过 Socket 收发数据。
  4. 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. 1.返回一个值给调用者。
  2. 2.暂停函数执行,保存当前状态。
  3. 3.下次调用 next() 时,从上次暂停的位置继续执行。
  4. 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 匹配 catdog

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. 1.比较相邻的两个元素,如果前一个大于后一个,交换它们。
  2. 2.对每一对相邻元素执行同样的操作,从开始到结尾。一轮结束后,最大的元素在最右边。
  3. 3.对除了最后一个元素外的所有元素重复以上步骤。
  4. 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

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--