文章目录
-
- 一、collections:数据结构工具箱
-
- [1.1 defaultdict:告别"键不存在"的手动判断](#1.1 defaultdict:告别"键不存在"的手动判断)
- [1.2 defaultdict 与 dict.setdefault 的性能差异](#1.2 defaultdict 与 dict.setdefault 的性能差异)
- [1.3 deque:两端操作的效率王者](#1.3 deque:两端操作的效率王者)
- [1.4 Counter:计数器的高级形态](#1.4 Counter:计数器的高级形态)
- [1.5 namedtuple 与 dataclass:什么时候用哪个](#1.5 namedtuple 与 dataclass:什么时候用哪个)
- 二、itertools:函数式编程的迭代器工具
-
- [2.1 product / combinations / permutations:排列组合](#2.1 product / combinations / permutations:排列组合)
- [2.2 groupby:最容易被误用的 itertools 工具](#2.2 groupby:最容易被误用的 itertools 工具)
- [2.3 islice:惰性切片](#2.3 islice:惰性切片)
- [2.4 chain.from_iterable:扁平化嵌套结构](#2.4 chain.from_iterable:扁平化嵌套结构)
- 三、functools:函数式编程的瑞士军刀
-
- [3.1 lru_cache:记忆化装饰器](#3.1 lru_cache:记忆化装饰器)
- [3.2 partial:冻结部分参数](#3.2 partial:冻结部分参数)
- [3.3 reduce:折叠操作](#3.3 reduce:折叠操作)
- 四、pathlib:现代化的路径操作
-
- [4.1 pathlib 相比 os.path 的核心优势](#4.1 pathlib 相比 os.path 的核心优势)
- [4.2 pathlib 核心操作速查](#4.2 pathlib 核心操作速查)
- [4.3 pathlib 的一个常见陷阱](#4.3 pathlib 的一个常见陷阱)
- 五、dataclass:轻量数据类的现代方案
-
- [5.1 dataclass 基础与字段默认值](#5.1 dataclass 基础与字段默认值)
- [5.2 frozen=True:不可变数据](#5.2 frozen=True:不可变数据)
- [5.3 post_init:初始化后处理](#5.3 post_init:初始化后处理)
- [5.4 dataclass 与 TypedDict 的选择](#5.4 dataclass 与 TypedDict 的选择)
- 六、综合实战:日志分析管道
- 总结
一、collections:数据结构工具箱
1.1 defaultdict:告别"键不存在"的手动判断
defaultdict 是 Python 项目中出现频率最高的 collections 工具之一,核心用途是自动为不存在的键初始化默认值 ,省去 if key not in dict 的手动判断。
典型场景:单词计数。
python
from collections import defaultdict
words = ["apple", "banana", "apple", "orange", "banana", "apple"]
# ❌ 传统写法:每次都要判断
count = {}
for word in words:
if word not in count:
count[word] = 0
count[word] += 1
# ✅ defaultdict 写法:工厂函数自动初始化
count = defaultdict(int) # int() 返回 0,list() 返回 [],set() 返回 set()
for word in words:
count[word] += 1 # 自动初始化为 0,再执行 +=1
print(dict(count)) # {'apple': 3, 'banana': 2, 'orange': 1}
defaultdict(int) 背后的原理是工厂函数:每当访问一个不存在的键时,调用传入的工厂函数生成默认值。常用工厂函数包括:
python
from collections import defaultdict
# int → 默认值 0(计数器)
dd_int: defaultdict = defaultdict(int)
# list → 默认值空列表(分组场景)
dd_list: defaultdict = defaultdict(list)
dd_list["fruits"].append("apple") # {"fruits": ["apple"]}
# set → 默认值空集合(去重分组)
dd_set: defaultdict = defaultdict(set)
dd_set["languages"].add("Python")
# 自定义工厂:返回一个 "unknown" 字符串
dd_custom: defaultdict = defaultdict(lambda: "unknown")
print(dd_custom["missing_key"]) # "unknown"
1.2 defaultdict 与 dict.setdefault 的性能差异
dict.setdefault(key, default) 的作用是在键不存在时插入默认值、键存在时返回已有值。它与 defaultdict 的区别不仅在于语法,更在于性能。
python
# dict.setdefault:每次调用都要执行完整的查找操作
word_count = {}
for word in words:
word_count.setdefault(word, 0) # 查找键,找不到则插入
word_count[word] += 1 # 再次查找键,执行加法
# defaultdict:键查找只执行一次
word_count = defaultdict(int)
for word in words:
word_count[word] += 1 # 找不到时初始化并返回 0,再执行 +=1(一次查找)
性能差异在数据量大时尤为明显:对于 n 个键的插入,defaultdict 执行约 n 次哈希查找,dict.setdefault 执行约 2n 次。实际工程中,如果代码里出现 setdefault 用于初始化默认值,应当改用 defaultdict。
1.3 deque:两端操作的效率王者
list 的头部插入和删除是 O(n) 操作------每次 pop(0) 或 insert(0, x) 都要移动所有元素。当需要频繁在两端操作 时,deque(双端队列)是更合适的选择:
python
from collections import deque
import time
# 测试:list vs deque 在左侧插入 10000 个元素
n = 100000
# list:O(n) 头部操作
start = time.perf_counter()
lst = []
for i in range(n):
lst.insert(0, i)
print(f"list.insert(0, i): {time.perf_counter() - start:.3f}s")
# deque:O(1) 头部操作
start = time.perf_counter()
d = deque()
for i in range(n):
d.appendleft(i)
print(f"deque.appendleft: {time.perf_counter() - start:.3f}s")
典型输出:
list.insert(0, i): 2.341s
deque.appendleft: 0.012s
差距可达 100~200 倍。deque 的选择决策如下:
是(队列/栈/滑动窗口)
否(随机访问为主)
是(固定窗口/缓存)
否
需要哪种操作?
频繁两端操作?
deque
O(1) 两端插入/删除
list
O(1) 索引访问
需要固定大小?
deque(maxlen=N)
自动丢弃最旧元素
滑动窗口是 deque 的经典应用场景:
python
from collections import deque
from typing import Generator
def moving_average(data: list[float], window: int = 3) -> Generator[float, None, None]:
"""计算滑动窗口平均值------deque 优雅实现,无需手动管理数组边界"""
dq: deque[float] = deque(maxlen=window) # 固定窗口大小,超出自动丢弃最旧元素
for value in data:
dq.append(value)
if len(dq) == window:
yield sum(dq) / window
# 使用
prices = [10.5, 11.2, 10.8, 12.1, 11.5, 13.0, 12.8]
averages = list(moving_average(prices, window=3))
print(averages) # [10.833..., 11.366..., 11.466..., 12.2, 12.433...]
1.4 Counter:计数器的高级形态
Counter 是 defaultdict(int) 的增强版,专门用于计数场景,提供了 most_common()、elements()、update() 等实用方法:
python
from collections import Counter
text = "python programming python code python java"
words = text.split()
# 基础计数
counter: Counter = Counter(words)
print(counter) # Counter({'python': 3, 'programming': 1, 'code': 1, 'java': 1})
# Top-N 最常见元素
print(counter.most_common(2)) # [('python', 3), ('programming', 1)]
# 统计字符频率
char_counter = Counter("abracadabra")
print(char_counter.most_common(3)) # [('a', 5), ('b', 2), ('r', 2)]
# Counter 运算:合并
counter1 = Counter(["apple", "banana", "apple"])
counter2 = Counter(["banana", "orange", "apple"])
print(counter1 + counter2) # Counter({'apple': 3, 'banana': 2, 'orange': 1})
print(counter1 - counter2) # Counter({'apple': 1}) ------ 只保留正值的差
1.5 namedtuple 与 dataclass:什么时候用哪个
namedtuple 和 dataclass 都可以用来定义带字段名的数据结构,但适用场景不同:
python
from collections import namedtuple
from dataclasses import dataclass
from typing import Tuple
# namedtuple:适合表示"轻量、不可变的数据记录"
Point = namedtuple("Point", ["x", "y"])
p: Point = Point(10, 20)
print(p.x, p.y, p[0], p[1]) # 同时支持属性访问和索引访问
# ❌ namedtuple 的限制:字段不可变
# p.x = 30 # AttributeError
# dataclass:适合需要默认值、可变字段、方法的数据类
@dataclass
class PointDC:
x: float
y: float
label: str = "" # 默认值支持
def distance_from_origin(self) -> float:
return (self.x ** 2 + self.y ** 2) ** 0.5
pdc: PointDC = PointDC(10.0, 20.0, label="A")
print(pdc.distance_from_origin()) # 22.360...
两者选择逻辑:
| 维度 | namedtuple | dataclass |
|---|---|---|
| 不可变性 | ✅ 默认不可变(需 _replace 修改) |
❌ 默认可变(frozen=True 才不可变) |
| 默认值 | ⚠️ 有限支持 | ✅ 完全支持 |
| 方法定义 | ❌ 不支持 | ✅ 支持 |
| 内存占用 | 更小 | 稍大 |
| JSON 序列化 | 需手动转换 | 配合 pydantic 更方便 |
| 适用场景 | 坐标、RGB 颜色、数据库记录等 | 需要方法、验证、默认值的数据对象 |
二、itertools:函数式编程的迭代器工具
2.1 product / combinations / permutations:排列组合
这三个函数是数据分析、算法题、测试用例生成的高频工具:
python
from itertools import product, combinations, permutations
# product:笛卡尔积------适合多重循环替代
colors = ["红", "绿", "蓝"]
sizes = ["S", "M", "L"]
for color, size in product(colors, sizes):
print(f"{color}-{size}", end=" ")
# 输出:红-S 红-M 红-L 绿-S 绿-M 绿-L 蓝-S 蓝-M 蓝-L
# 等价于:
# for color in colors:
# for size in sizes:
# ...
# combinations:组合(顺序无关)------从 n 个中选 k 个
deck = ["A", "K", "Q", "J"]
print(list(combinations(deck, 2)))
# 输出:[('A','K'), ('A','Q'), ('A','J'), ('K','Q'), ('K','J'), ('Q','J')]
# permutations:排列(顺序有关)------n 个中选 k 个的所有排列
print(list(permutations(deck, 2)))
# 输出:[('A','K'), ('A','Q'), ... ('J','Q'), ('J','K')] ------ 共 12 种
2.2 groupby:最容易被误用的 itertools 工具
itertools.groupby 是最容易出错的工具------它只合并相邻的相同键,如果数据未排序,结果会出乎意料:
python
from itertools import groupby
data = [
{"name": "Alice", "dept": "Engineering"},
{"name": "Bob", "dept": "Sales"},
{"name": "Carol", "dept": "Engineering"},
{"name": "Dave", "dept": "Sales"},
]
# ❌ 常见错误:未排序就分组------结果错误!
for dept, group in groupby(data, key=lambda x: x["dept"]):
print(f"{dept}: {list(group)}")
# Engineering 只包含 Alice,Sales 只包含 Bob------Carol 和 Dave 被"吞掉"了!
# 原因:groupby 只处理相邻元素,Carol 在 Alice 后面,但 Bob 把 Engineering 断了
# ✅ 正确做法:先排序,再分组
data_sorted = sorted(data, key=lambda x: x["dept"])
for dept, group in groupby(data_sorted, key=lambda x: x["dept"]):
print(f"{dept}: {list(group)}")
# Engineering: [{'name': 'Alice', ...}, {'name': 'Carol', ...}]
# Sales: [{'name': 'Bob', ...}, {'name': 'Dave', ...}]
这个坑在面试和实战中都非常常见------记住"groupby 之前先排序"这条规则。
2.3 islice:惰性切片
islice 实现了对迭代器的"惰性切片"------不提前加载全部数据,按需取用:
python
from itertools import islice
# 取前 N 个
numbers = range(1000000)
first_10 = list(islice(numbers, 10)) # 只取 10 个,不需要遍历全部 100 万
# 跳过前 N 个,取后面的
rest = list(islice(numbers, 5, 15)) # 跳过前 5 个,取第 6~15 个
# 步长切片(Python 3.10+ 支持 start/stop/step)
evens = list(islice(range(20), 0, 20, 2)) # [0, 2, 4, ..., 18]
2.4 chain.from_iterable:扁平化嵌套结构
python
from itertools import chain
nested = [["apple", "banana"], ["orange", "grape"], ["melon"]]
flat = list(chain.from_iterable(nested))
print(flat) # ['apple', 'banana', 'orange', 'grape', 'melon']
# 对比 list comprehension
flat2 = [item for sublist in nested for item in sublist]
# 两者效果相同,chain.from_iterable 在处理大型迭代器时更省内存
三、functools:函数式编程的瑞士军刀
3.1 lru_cache:记忆化装饰器
lru_cache(Least Recently Used Cache)是 Python 标准库中最强大的性能优化工具之一。原理很简单:将函数的计算结果缓存起来,相同参数的重复调用直接返回缓存值。
斐波那契数列是最经典的例子------普通递归是指数级时间复杂度,加了 @lru_cache 后变成线性:
python
from functools import lru_cache
import time
# ❌ 无缓存:重复计算导致指数级复杂度
def fib_slow(n: int) -> int:
if n < 2:
return n
return fib_slow(n - 1) + fib_slow(n - 2)
start = time.perf_counter()
print(fib_slow(35))
print(f"无缓存: {time.perf_counter() - start:.3f}s")
# ✅ 有缓存:时间复杂度骤降
@lru_cache(maxsize=None)
def fib_fast(n: int) -> int:
if n < 2:
return n
return fib_fast(n - 1) + fib_fast(n - 2)
start = time.perf_counter()
print(fib_fast(35))
print(f"有缓存: {time.perf_counter() - start:.3f}s")
无缓存: 2.847s
有缓存: 0.000s
差距高达数千倍。查看缓存统计:
python
print(fib_fast.cache_info())
# CacheInfo(hits=33, misses=36, maxsize=None, currsize=36)
使用 @lru_cache 时需要注意:
python
# ⚠️ 参数必须可哈希------list、dict 等不可哈希
@lru_cache
def process(data: list) -> int: # TypeError: unhashable type 'list'
return sum(data)
# ✅ 正确做法:将 list 转为 tuple 传入
@lru_cache
def process_tuple(data: tuple) -> int:
return sum(data)
process_tuple((1, 2, 3))
# ✅ 或者用 functools.cache(Python 3.9+,无大小限制的 lru_cache)
from functools import cache
@cache
def factorial(n: int) -> int:
return n * factorial(n - 1) if n else 1
3.2 partial:冻结部分参数
partial 允许"冻结"函数的部分参数,生成一个新的简化函数:
python
from functools import partial
# 基础用法
def power(base: float, exponent: float) -> float:
return base ** exponent
# 冻结 exponent=2,生成一个"平方函数"
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
print(square(5)) # 25.0
print(cube(2)) # 8.0
# 实际场景:绑定回调函数参数
import tkinter as tk
def on_click(button_name: str, event):
print(f"Button '{button_name}' clicked")
# 分别为每个按钮绑定回调
btn_a = partial(on_click, "Submit")
btn_b = partial(on_click, "Cancel")
# btn_a 和 btn_b 可以在不同按钮上使用,button_name 已固定
3.3 reduce:折叠操作
functools.reduce 将一个二元函数依次应用于序列元素,把序列归约为单个值:
python
from functools import reduce
# 累加
numbers = [1, 2, 3, 4, 5]
total: int = reduce(lambda a, b: a + b, numbers)
print(total) # 15
# 等价于:(((1+2)+3)+4)+5
# 实际场景:计算商品总价(字典列表)
cart = [
{"name": "笔记本", "price": 25.0, "qty": 2},
{"name": "铅笔", "price": 3.0, "qty": 5},
{"name": "橡皮", "price": 2.0, "qty": 3},
]
total_price: float = reduce(
lambda acc, item: acc + item["price"] * item["qty"],
cart,
0.0 # 初始值
)
print(f"总价: ¥{total_price:.2f}") # ¥71.0
# reduce 配合 operator:更高效
from operator import mul
from functools import reduce
product = reduce(mul, [1, 2, 3, 4, 5]) # 120
提示 :Python 3 起
reduce不再是内置函数,需要从functools导入。如果代码中大量使用reduce,可能需要评估是否应该用循环替代------对于简单的累加/累积操作,循环往往更清晰。
四、pathlib:现代化的路径操作
4.1 pathlib 相比 os.path 的核心优势
Python 3.4 引入的 pathlib 提供了面向对象的路径操作 API,相比传统的 os.path 字符串拼接有三大优势:
pathlib(现代)
路径是对象
链式调用
自动跨平台
os.path(传统)
路径是字符串
方法分散
跨平台需判断
对比示例:
python
import os
# os.path 方式:字符串拼接,冗长
base = "/home/user/documents"
filename = "report.txt"
filepath = os.path.join(base, filename) # 拼接
dirname = os.path.dirname(filepath) # 取目录
basename = os.path.basename(filepath) # 取文件名
extension = os.path.splitext(basename)[1] # 取扩展名
exists = os.path.exists(filepath) # 存在性检查
size = os.path.getsize(filepath) # 文件大小
if not os.path.exists(base):
os.makedirs(base, exist_ok=True) # 创建目录
files = [f for f in os.listdir(base) if os.path.isfile(os.path.join(base, f))] # 过滤文件
# pathlib 方式:链式调用,语义清晰
from pathlib import Path
base = Path("/home/user/documents")
filepath = base / "report.txt" # / 运算符拼接
dirname = filepath.parent # 取目录
basename = filepath.name # 取文件名
extension = filepath.suffix # 取扩展名
exists = filepath.exists() # 存在性检查
size = filepath.stat().st_size # 文件大小
base.mkdir(parents=True, exist_ok=True) # 创建目录
files = [f for f in base.iterdir() if f.is_file()] # 过滤文件
4.2 pathlib 核心操作速查
python
from pathlib import Path
p = Path("/home/user/documents/report.txt")
# 路径组件分解
print(p.parts) # ('/', 'home', 'user', 'documents', 'report.txt')
print(p.name) # 'report.txt'(文件名+扩展名)
print(p.stem) # 'report'(不含扩展名的文件名)
print(p.suffix) # '.txt'
print(p.parent) # PosixPath('/home/user/documents')
print(p.parents) # 可迭代父路径列表
# 路径判断
p.is_file() # 是否为文件
p.is_dir() # 是否为目录
p.is_symlink() # 是否为符号链接
p.exists() # 是否存在
# glob 模式匹配
documents = Path("/home/user")
for py_file in documents.rglob("*.py"): # 递归匹配所有 .py 文件
print(f"{py_file.name}: {py_file.stat().st_size} bytes")
# 读写文件(简洁 API)
p.write_text("Hello, pathlib!", encoding="utf-8")
content: str = p.read_text(encoding="utf-8")
# JSON 文件直接读写
import json
json_data = json.loads(p.read_text())
p.write_text(json.dumps(json_data, indent=2))
# 路径拼接的 / 运算符(跨平台安全)
subdir = Path("data") / "2024" / "logs"
subdir.mkdir(parents=True, exist_ok=True)
4.3 pathlib 的一个常见陷阱
python
from pathlib import Path
# ❌ 字符串 / Path 混合时容易出错
p1 = Path("/home") / "user" / "docs" # ✅ 返回 Path 对象
p2 = "/home" / "user" / "docs" # ❌ TypeError:str 不支持 / 运算符
p3 = Path("/home") + "user" + "docs" # ❌ TypeError:Path + str 不支持
# ✅ / 运算符要求至少一侧是 Path 对象
p4 = Path("/home") / "user" / "docs" # 正确
五、dataclass:轻量数据类的现代方案
5.1 dataclass 基础与字段默认值
dataclass(Python 3.7+)是比 namedtuple 功能更丰富的数据类定义方式,默认值处理需要注意字段顺序:
python
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class User:
name: str
email: str
age: int = 0 # 带默认值
is_active: bool = True
tags: list[str] = field(default_factory=list) # 可变默认值必须用 factory
# ⚠️ 可变默认值陷阱:不能直接写 tags: list = []
# 这会导致所有实例共享同一个 list 对象!
user = User(name="Alice", email="alice@example.com")
print(user) # User(name='Alice', email='alice@example.com', age=0, is_active=True, tags=[])
5.2 frozen=True:不可变数据
python
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: float
y: float
p = Point(10.0, 20.0)
# p.x = 30.0 # FrozenInstanceError:实例被"冻结",不可修改
frozen=True 将实例完全冻结,适合作为字典键或放入集合(普通 dataclass 因为可哈希问题不能直接作为 dict 的 key)。
5.3 post_init:初始化后处理
__post_init__ 在所有字段初始化完成后执行,常用于验证和计算字段:
python
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class Rectangle:
width: float
height: float
name: Optional[str] = None
@property
def area(self) -> float:
"""计算属性"""
return self.width * self.height
def __post_init__(self) -> None:
"""初始化后验证------width 和 height 必须为正"""
if self.width <= 0:
raise ValueError(f"width must be positive, got {self.width}")
if self.height <= 0:
raise ValueError(f"height must be positive, got {self.height}")
# 自动从 name 生成标签
if self.name is None:
self.name = f"Rect_{self.width}x{self.height}"
rect = Rectangle(5.0, 3.0)
print(f"{rect.name}: area={rect.area}") # Rect_5.0x3.0: area=15.0
# rect = Rectangle(-1.0, 3.0) # ValueError: width must be positive, got -1.0
5.4 dataclass 与 TypedDict 的选择
python
from dataclasses import dataclass, field
from typing import TypedDict, NotRequired
# TypedDict:表示 JSON/API 结构(运行时是 dict)
class APIUser(TypedDict):
id: int
name: str
email: NotRequired[str]
# dataclass:表示带行为的数据对象(运行时是类实例)
@dataclass
class UserEntity:
name: str
email: str
friends: list[str] = field(default_factory=list)
def add_friend(self, friend_name: str) -> None:
self.friends.append(friend_name)
| 维度 | TypedDict | dataclass |
|---|---|---|
| 运行时类型 | dict |
类实例 |
| JSON 序列化 | 直接兼容 | 需 asdict() |
| 默认值 | 需 NotRequired |
直接支持 |
| 方法 | ❌ 不支持 | ✅ 支持 |
| 验证逻辑 | ❌ 不支持(配合 pydantic) | ✅ 可在 __post_init__ 实现 |
| 适用场景 | API 响应、配置文件 | 业务实体、领域模型 |
六、综合实战:日志分析管道
综合运用 collections、itertools、pathlib 构建一个日志分析管道:
python
from pathlib import Path
from collections import Counter, defaultdict
from itertools import groupby
import json
import time
from dataclasses import dataclass, field
from typing import Optional
# ---------- 数据模型 ----------
@dataclass
class LogEntry:
timestamp: float
level: str
message: str
service: str
@classmethod
def from_line(cls, line: str, service: str) -> Optional["LogEntry"]:
"""从日志行解析 LogEntry,失败返回 None"""
try:
# 假设格式: timestamp level message
parts = line.strip().split(" ", 2)
if len(parts) < 3:
return None
return cls(
timestamp=float(parts[0]),
level=parts[1],
message=parts[2],
service=service
)
except (ValueError, IndexError):
return None
# ---------- 核心分析 ----------
def analyze_logs(log_dir: Path) -> dict:
"""分析日志目录,返回统计报告"""
all_entries: list[LogEntry] = []
service_counts: Counter = Counter()
level_counts: Counter = Counter()
error_messages: list[str] = []
# 1. 收集所有日志条目
for log_file in log_dir.rglob("*.log"):
service_name = log_file.stem
for line in log_file.read_text(encoding="utf-8").splitlines():
entry = LogEntry.from_line(line, service_name)
if entry:
all_entries.append(entry)
service_counts[entry.service] += 1
level_counts[entry.level] += 1
if entry.level == "ERROR":
error_messages.append(f"[{entry.service}] {entry.message}")
# 2. 按服务分组统计(使用 defaultdict)
service_errors: dict[str, list[str]] = defaultdict(list)
for entry in all_entries:
if entry.level == "ERROR":
service_errors[entry.service].append(entry.message)
# 3. 按时间窗口统计(islice 实现滑动窗口)
from itertools import islice
error_rates: list[float] = []
window_size = 100
entries_iter = iter(all_entries)
window = list(islice(entries_iter, window_size))
while window:
error_count = sum(1 for e in window if e.level == "ERROR")
error_rates.append(error_count / len(window))
window = list(islice(entries_iter, window_size))
# 4. 生成报告
report = {
"total_entries": len(all_entries),
"service_counts": dict(service_counts.most_common(5)),
"level_distribution": dict(level_counts),
"top_services": service_counts.most_common(3),
"error_rate_avg": sum(error_rates) / len(error_rates) if error_rates else 0,
"recent_errors": error_messages[:10],
}
return report
# ---------- 演示运行 ----------
if __name__ == "__main__":
# 创建测试日志文件
test_dir = Path("test_logs")
test_dir.mkdir(exist_ok=True)
(test_dir / "api.log").write_text(
"1000.0 INFO Starting service\n"
"1001.0 ERROR Connection failed\n"
"1002.0 WARNING Retry attempt 1\n"
"1003.0 ERROR Timeout after 30s\n",
encoding="utf-8"
)
(test_dir / "worker.log").write_text(
"1000.0 INFO Worker initialized\n"
"1001.0 INFO Task completed\n"
"1002.0 ERROR Task failed\n",
encoding="utf-8"
)
report = analyze_logs(test_dir)
print(json.dumps(report, indent=2, ensure_ascii=False))
# 清理测试文件
import shutil
shutil.rmtree(test_dir)
运行结果:
json
{
"total_entries": 7,
"service_counts": {
"api": 4,
"worker": 3
},
"level_distribution": {
"INFO": 3,
"ERROR": 3,
"WARNING": 1
},
"top_services": [("api", 4), ("worker", 3)],
"error_rate_avg": 0.42857142857142855,
"recent_errors": [
"[api] Connection failed",
"[api] Timeout after 30s",
"[worker] Task failed"
]
}
这个实战展示了标准库工具的组合威力:pathlib 遍历目录读写文件,dataclass 定义结构化数据,collections 的 Counter 和 defaultdict 做统计分析,itertools 的 islice 实现滑动窗口。
总结
标准库的价值在于"不需要 pip install,Python 装好就能用"。大多数项目花费大量精力寻找第三方库解决的问题,标准库往往已有高效实现------前提是知道这些工具的存在。这篇文章的价值,就是让那些一直"待学"的模块从后台走向前台。
如果觉得这篇文章有帮助,欢迎点赞、关注!
往期回顾: