ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python标准库深度解析:从pathlib到并发编程的实战指南

Python标准库深度解析:从pathlib到并发编程的实战指南 1. 从“能用”到“会用”为什么你需要重新认识Python标准库刚接触Python那会儿我和很多人一样觉得标准库就是那些print()、len()、open()之类的内置函数加上os、sys几个常用模块。直到有一次我需要处理一个复杂的日志文件分析任务自己吭哧吭哧写了上百行代码去解析时间戳、过滤特定字符串、合并数据结果被一位资深同事用datetime、re和collections模块里的几个函数不到二十行就优雅地解决了。那一刻我才真正意识到Python标准库不是一个简单的工具清单而是一个被严重低估的“瑞士军刀”套装里面装满了经过千锤百炼、高效且稳定的解决方案。很多人学了Python能熟练使用requests、pandas、numpy等第三方库却对自带的“宝藏”视而不见。这就像守着金矿去捡石头。标准库是Python语言的核心组成部分它意味着零依赖无需pip install开箱即用部署简单环境干净。高性能与稳定性由核心开发者维护经过长期测试性能和稳定性有保障。跨平台一致性在Windows、Linux、macOS上行为一致减少了环境适配的麻烦。最佳实践范例其代码本身就是学习Python高级用法和设计模式的绝佳教材。这篇内容不是一份枯燥的API文档罗列而是从一个多年Python使用者的角度带你深入那些真正高频、实用且常被误解的标准库模块。我会拆解它们的核心设计思想分享实际项目中的使用场景和避坑经验目标是让你看完后能在下次编码时第一个想到的不是去搜第三方库而是自信地说“等等标准库里是不是有现成的”2. 核心模块深度解析超越os和sys当我们谈论标准库时os和sys确实是入口但它们的深度远超os.listdir()和sys.path。2.1 文件与系统交互os与pathlib的现代之道os模块是老牌的文件系统操作工具但直接使用它拼接路径常常会写出跨平台兼容性差的代码。import os # 传统方式 - 不推荐 file_path os.path.join(‘some‘, ‘folder‘, ‘file.txt‘) # 这没问题 if os.path.exists(file_path): with open(file_path, ‘r‘) as f: ... # 问题路径分割、获取父目录等操作比较繁琐pathlib是Python 3.4引入的面向对象的路径库它彻底改变了我们处理路径的方式。from pathlib import Path # 现代方式 - 推荐 current_dir Path(‘.‘) # 当前目录对象 file_path current_dir / ‘some‘ / ‘folder‘ / ‘file.txt‘ # 使用 / 运算符拼接直观 if file_path.exists(): # 直接读取文本无需显式open/close content file_path.read_text(encoding‘utf-8‘) # 获取父目录、文件名、后缀等属性非常方便 parent_dir file_path.parent file_name file_path.stem # 不含后缀的文件名 suffix file_path.suffix # 文件后缀如 ‘.txt‘实操心得无脑用pathlib对于所有新的项目将pathlib作为处理文件路径的首选。它的对象化设计让代码更清晰且天然跨平台。os的不可替代性pathlib并非要完全取代os。os模块在进程管理os.fork,os.exec、环境变量操作os.environ、以及一些底层系统调用上仍是唯一选择。例如修改文件权限os.chmod(path, 0o755)。遍历目录使用Path(‘.‘).glob(‘**/*.py‘)来递归查找所有Python文件比os.walk更简洁。2.2 数据类型增强collections里的数据结构“特种兵”list、dict、set、tuple是常规军collections模块则提供了功能更强的“特种兵”。defaultdict处理缺失键的优雅方案在统计词频时传统写法需要先判断键是否存在。# 传统写法 word_count {} for word in words: if word in word_count: word_count[word] 1 else: word_count[word] 1使用defaultdict代码简洁且意图明确。from collections import defaultdict word_count defaultdict(int) # 默认值为 int()即0 for word in words: word_count[word] 1 # 无需判断直接加Counter专为计数而生Counter是defaultdict(int)的升级版提供了更丰富的计数API。from collections import Counter words [‘apple‘, ‘banana‘, ‘apple‘, ‘orange‘, ‘banana‘, ‘apple‘] word_counter Counter(words) print(word_counter) # 输出: Counter({‘apple‘: 3, ‘banana‘: 2, ‘orange‘: 1}) # 获取最常见的2个元素 print(word_counter.most_common(2)) # [(‘apple‘, 3), (‘banana‘, 2)] # 可以直接做集合运算 c1 Counter(‘abracadabra‘) c2 Counter(‘alacazam‘) print(c1 c2) # 交集: Counter({‘a‘: 4, ‘c‘: 1, ‘b‘: 1, ‘r‘: 1})deque高效的双端队列list在头部插入删除元素insert(0, item),pop(0)是O(n)操作性能差。deque在两端进行追加和弹出操作都是O(1)。from collections import deque # 实现一个固定长度的历史记录队列 history deque(maxlen5) for i in range(10): history.append(i) print(list(history)) # 最后history中只保留 [5, 6, 7, 8, 9]namedtuple给元组字段命名创建简单的、不可变的数据对象比普通类更轻量比字典更高效。from collections import namedtuple # 定义一个点的结构 Point namedtuple(‘Point‘, [‘x‘, ‘y‘]) p Point(10, y20) print(p.x, p.y) # 10 20 print(p[0]) # 10 依然支持索引访问注意事项defaultdict的默认工厂函数可以是list、set、dict或任何无参可调用对象用于构建复杂的嵌套结构非常方便例如defaultdict(list)用来分组。Counter对象可以直接与整数进行加减运算非常强大。2.3 时间日期处理datetime与time的分工时间处理是编程中的常见痛点标准库提供了datetime和time两个模块它们有明确的分工。datetime处理人类可读的日期和时间这是你日常使用最多的模块用于表示一个具体的时刻datetime.datetime或一个日期datetime.date、时间datetime.time。from datetime import datetime, date, timedelta # 获取当前时间 now datetime.now() print(now.strftime(‘%Y-%m-%d %H:%M:%S‘)) # 格式化输出 # 时间计算 tomorrow now timedelta(days1) last_week now - timedelta(weeks1) # 解析字符串 dt_str ‘2023-10-27 14:30:00‘ parsed_dt datetime.strptime(dt_str, ‘%Y-%m-%d %H:%M:%S‘) # 只关心日期 today date.today()time处理时间戳和性能计时time模块更底层主要用于获取时间戳time.time()返回自纪元1970-01-01 UTC以来的秒数浮点数用于精确计时和系统间时间交换。程序休眠time.sleep(seconds)。性能测量time.perf_counter()提供最高精度的计时器用于测量短时间间隔。import time start time.perf_counter() # 执行一些耗时操作 time.sleep(0.1) end time.perf_counter() print(f‘耗时{end - start:.4f}秒‘) # 时间戳 timestamp time.time() print(f‘当前时间戳: {timestamp}‘)核心区别与选择需要表示和操作一个具体的“时刻”或“日期”- 用datetime。需要测量代码执行时间、让程序等待、或者获取一个简单的数字时间戳- 用time。时区处理朴素的datetime对象是“无时区”的。对于需要时区的场景使用datetime.timezone或第三方库pytzPython 3.9 推荐使用zoneinfo。2.4 正则表达式re模块的威力与陷阱正则表达式是文本处理的终极武器re模块是其Python实现。基础模式re.match(pattern, string)从字符串开头匹配。re.search(pattern, string)扫描整个字符串返回第一个匹配。re.findall(pattern, string)返回所有非重叠匹配的列表。re.finditer(pattern, string)返回一个迭代器包含所有匹配的匹配对象。re.sub(pattern, repl, string)替换所有匹配项。一个实用的例子提取日志中的IP和时间import re log_line ‘127.0.0.1 - - [27/Oct/2023:14:30:01 0800] “GET /api/user HTTP/1.1” 200 1234‘ # 编译正则表达式推荐尤其需多次使用时 pattern re.compile( r‘(?Pip\d\.\d\.\d\.\d).*?\[(?Ptime.*?)\].*?“(?Pmethod\w) (?Purl.*?) HTTP‘ ) match pattern.search(log_line) if match: print(match.group(‘ip‘)) # 127.0.0.1 print(match.group(‘time‘)) # 27/Oct/2023:14:30:01 0800 print(match.groupdict()) # 以字典形式输出所有命名组避坑指南与性能优化预编译如果一个正则表达式要使用多次务必使用re.compile()预编译。这能避免每次匹配时都重新解析模式字符串带来显著的性能提升。使用原始字符串正则表达式模式字符串前加r如r‘\d‘避免Python字符串转义和正则转义混淆。贪婪 vs 非贪婪默认量词*,,?,{m,n}是“贪婪”的会匹配尽可能多的字符。在其后加?变为“非贪婪”懒惰匹配尽可能少的字符。例如r‘.*?‘用于匹配最短的HTML标签。慎用.匹配换行默认情况下.不匹配换行符。如果需要跨行匹配可以使用re.DOTALL标志或使用[\s\S]这样的字符集。性能陷阱回溯是正则表达式性能杀手。避免编写像(a)b这样可能导致“灾难性回溯”的复杂嵌套模式。对于简单的固定字符串查找优先考虑str.find()或str.replace()它们更快。3. 高级工具与并发编程初探掌握了基础模块后标准库中还有一些“高级货”能在特定场景下极大提升开发效率和程序性能。3.1 上下文管理器contextlib让资源管理更优雅我们熟知with open(‘file.txt‘) as f:这背后的协议就是上下文管理器。contextlib模块提供了创建和管理上下文管理器的工具而无需完整定义__enter__和__exit__方法。contextmanager装饰器这是最常用的工具可以将一个生成器函数变成上下文管理器。from contextlib import contextmanager import time contextmanager def timer(name): “”“计时上下文管理器”“” start time.perf_counter() try: yield # 在此处执行 with 块内的代码 finally: end time.perf_counter() print(f‘[{name}] 耗时: {end - start:.4f}秒‘) # 使用 with timer(‘数据处理‘): # 模拟耗时操作 time.sleep(0.5) data [i**2 for i in range(10000)] # 退出with块后自动打印: [数据处理] 耗时: 0.5012秒closing如果一个对象提供了close()方法但不是上下文管理器可以用closing来确保它被关闭。from contextlib import closing from urllib.request import urlopen with closing(urlopen(‘http://www.python.org‘)) as page: content page.read() # 处理content # 退出时 page.close() 会被自动调用suppress临时忽略指定异常。from contextlib import suppress import os # 传统方式需要try...except try: os.remove(‘somefile.tmp‘) except FileNotFoundError: pass # 使用 suppress 更简洁 with suppress(FileNotFoundError): os.remove(‘somefile.tmp‘)3.2 数据持久化json与pickle的抉择将Python对象保存到文件或进行网络传输序列化是必备技能。标准库主要提供了json和pickle。特性json模块pickle模块格式文本基于JSON标准二进制Python特有可读性好人类可读可编辑差二进制乱码跨语言支持几乎所有编程语言都能解析不支持仅限Python安全性相对安全只能序列化基本数据类型和结构不安全可执行任意Python代码切勿反序列化不受信任的数据支持类型有限dict, list, str, int/float, bool, None几乎支持所有Python对象函数、类实例等性能较慢较快使用建议与外部系统如Web API、前端、其他语言程序交换数据-无条件选择json。仅在Python程序内部临时存储或传输复杂的Python对象如机器学习模型、带有方法的类实例- 可以考虑pickle但必须确保数据来源绝对可信。长期存储配置或数据- 优先考虑json或yaml需第三方库pickle的二进制格式可能因Python版本升级而变得不兼容。json进阶用法import json from datetime import datetime data { ‘name‘: ‘Test‘, ‘time‘: datetime.now(), # datetime对象不能直接JSON序列化 ‘value‘: 42 } # 自定义序列化函数 def custom_serializer(obj): if isinstance(obj, datetime): return obj.isoformat() # 转换为ISO格式字符串 raise TypeError(f‘Type {type(obj)} not serializable‘) json_str json.dumps(data, defaultcustom_serializer, indent2) print(json_str) # 自定义反序列化钩子object_hook def custom_deserializer(dct): if ‘time‘ in dct: try: dct[‘time‘] datetime.fromisoformat(dct[‘time‘]) except (ValueError, AttributeError): pass return dct loaded_data json.loads(json_str, object_hookcustom_deserializer) print(type(loaded_data[‘time‘])) # class ‘datetime.datetime‘3.3 并发编程基石threading与multiprocessing的清晰边界Python并发主要有两种方式多线程 (threading) 和多进程 (multiprocessing)。理解它们的区别至关重要。核心区别GIL全局解释器锁Python有一个GIL它确保同一时刻只有一个线程执行Python字节码。这意味着threading(多线程)适用于I/O密集型任务如网络请求、文件读写、数据库查询。当某个线程在等待I/O时GIL会被释放其他线程可以运行从而有效利用等待时间。multiprocessing(多进程)适用于CPU密集型任务如科学计算、图像处理。每个进程有自己独立的Python解释器和内存空间因此可以绕过GIL真正利用多核CPU。threading示例并发下载网页import threading import requests import time def download_url(url): response requests.get(url) print(f‘{url}: {len(response.content)} bytes‘) urls [‘https://httpbin.org/delay/1‘, ‘https://httpbin.org/delay/2‘, ‘https://httpbin.org/delay/1‘] start time.time() # 顺序执行 for url in urls: download_url(url) print(f‘顺序执行耗时: {time.time() - start:.2f}秒‘) start time.time() # 多线程执行 threads [] for url in urls: thread threading.Thread(targetdownload_url, args(url,)) thread.start() threads.append(thread) for thread in threads: thread.join() # 等待所有线程结束 print(f‘多线程执行耗时: {time.time() - start:.2f}秒‘) # 你会发现多线程总耗时接近最长的单个任务耗时而不是任务之和。multiprocessing示例并行计算import multiprocessing import math import time def cpu_bound_task(n): “”“模拟CPU密集型任务计算平方和”“” return sum(i * i for i in range(n)) def run_sequential(numbers): start time.time() results [cpu_bound_task(n) for n in numbers] print(f‘顺序结果: {results[:5]}...‘) # 打印前5个 print(f‘顺序执行耗时: {time.time() - start:.2f}秒‘) def run_parallel(numbers): start time.time() with multiprocessing.Pool() as pool: # 使用进程池 results pool.map(cpu_bound_task, numbers) print(f‘并行结果: {results[:5]}...‘) print(f‘并行执行耗时: {time.time() - start:.2f}秒‘) if __name__ ‘__main__‘: # 多进程必须有的保护 numbers [5_000_000 i * 100 for i in range(20)] run_sequential(numbers) run_parallel(numbers) # 在多核机器上并行版本会快很多重要注意事项线程安全多线程访问共享资源如全局变量、同一个文件对象可能导致数据竞争。必须使用锁threading.Lock来同步。进程间通信多进程内存不共享通信需要通过队列multiprocessing.Queue、管道或共享内存等机制比线程间通信开销大。进程启动开销创建进程比创建线程慢资源消耗也更大。if __name__ ‘__main__‘:在多进程编程中这是必须的否则在创建子进程时可能导致无限递归。4. 实战构建一个简易的本地日志监控工具现在我们把前面提到的多个标准库模块组合起来实现一个实用的工具监控一个本地日志文件的新增内容并将匹配特定错误模式的行实时输出到控制台。这模拟了类似tail -f | grep error的功能。4.1 工具设计与模块选择目标持续读取一个不断增长的日志文件如app.log实时过滤出包含“ERROR”或“Exception”关键词的行并高亮显示。选型理由文件监控使用pathlib处理路径open()函数读取。为了持续读取新增内容我们需要追踪文件指针位置。模式匹配使用re模块进行正则表达式匹配比简单的in操作更灵活可以匹配更复杂的错误模式。时间处理使用datetime为输出的每一行添加时间戳。循环与控制使用time.sleep()进行间歇性轮询避免CPU占用过高。使用信号处理signal模块来优雅地退出程序。4.2 核心代码实现#!/usr/bin/env python3 简易日志文件监控工具 用法: python log_monitor.py /path/to/your/app.log import re import sys import time import signal from pathlib import Path from datetime import datetime class LogMonitor: def __init__(self, log_file_path, patternr‘ERROR|Exception‘): self.log_file Path(log_file_path) if not self.log_file.is_file(): raise FileNotFoundError(f“日志文件不存在: {log_file_path}“) self.pattern re.compile(pattern, re.IGNORECASE) # 编译正则忽略大小写 self._running True self._last_position 0 # 记录上次读取到的文件位置 # 设置信号处理使CtrlC可以优雅退出 signal.signal(signal.SIGINT, self.signal_handler) signal.signal(signal.SIGTERM, self.signal_handler) def signal_handler(self, signum, frame): print(f“\n[{datetime.now().isoformat()}] 收到终止信号正在退出...“) self._running False def _highlight_match(self, line): “”“高亮显示匹配到的关键词”“” # 使用 ANSI 转义序列实现终端颜色高亮 highlighted self.pattern.sub(lambda m: f‘\033[91m{m.group()}\033[0m‘, line) # 91m 为亮红色 return highlighted.rstrip(‘\n‘) # 去掉末尾换行 def monitor(self): print(f“[{datetime.now().isoformat()}] 开始监控文件: {self.log_file.absolute()}“) print(f“匹配模式: {self.pattern.pattern}“) print(“-” * 50) try: while self._running: # 以只读方式打开文件 with self.log_file.open(‘r‘, encoding‘utf-8‘, errors‘ignore‘) as f: # 移动到上次读取的位置 f.seek(self._last_position) # 读取所有新内容 new_lines f.readlines() # 更新位置指针 self._last_position f.tell() # 处理并输出匹配的行 for line in new_lines: if self.pattern.search(line): timestamp datetime.now().strftime(‘%Y-%m-%d %H:%M:%S‘) highlighted_line self._highlight_match(line) print(f‘[{timestamp}] {highlighted_line}‘) # 短暂休眠降低CPU占用 time.sleep(0.5) except KeyboardInterrupt: pass # 信号处理器已处理 except Exception as e: print(f“监控过程中发生错误: {e}“, filesys.stderr) finally: print(f“[{datetime.now().isoformat()}] 监控已停止。“) def main(): if len(sys.argv) 2: print(“请指定要监控的日志文件路径。“) print(f“用法: {sys.argv[0]} log_file_path“) sys.exit(1) log_file_path sys.argv[1] # 可以在这里修改或通过参数传入匹配模式 # 例如更复杂的模式: r‘\b(?:ERROR|FATAL|Exception|Traceback)\b‘ monitor LogMonitor(log_file_path) try: monitor.monitor() except FileNotFoundError as e: print(e, filesys.stderr) sys.exit(1) if __name__ ‘__main__‘: main()4.3 功能扩展与优化思路这个基础版本已经可用但我们可以利用更多标准库模块让它变得更强大使用argparse模块解析命令行参数import argparse def parse_args(): parser argparse.ArgumentParser(description‘监控日志文件中的错误‘) parser.add_argument(‘log_file‘, help‘要监控的日志文件路径‘) parser.add_argument(‘-p‘, ‘--pattern‘, default‘ERROR|Exception‘, help‘匹配错误信息的正则表达式模式 (默认: ERROR|Exception)‘) parser.add_argument(‘-i‘, ‘--ignore-case‘, action‘store_true‘, help‘忽略大小写‘) parser.add_argument(‘--history‘, typeint, default0, help‘同时输出匹配到的历史行数从文件末尾开始‘) return parser.parse_args()这样用户就可以通过python log_monitor.py app.log -p “FATAL|CRITICAL“ --history 10来定制化使用了。添加日志轮转支持真实的日志文件可能会被切割或轮转如app.log被重命名为app.log.1新建app.log。我们可以通过检查文件的inode或创建时间是否发生变化来检测轮转并重置_last_position为0。使用selectors模块进行高效事件等待在类Unix系统上对于文件描述符的I/O事件selectors可以提供比单纯time.sleep轮询更高效的机制但实现稍复杂。将输出重定向到文件或网络结合logging模块可以将监控到的错误行不仅打印到控制台还同时记录到另一个日志文件或发送到网络日志收集服务。5. 常见问题与排查技巧实录在实际使用标准库时总会遇到一些“坑”。这里记录了几个最常见的问题和我的解决思路。5.1 编码问题UnicodeDecodeError的幽灵这是文件操作和网络请求中最常见的问题之一。问题场景用open(‘file.txt‘, ‘r‘)读取一个包含中文或其他非ASCII字符的文件时程序崩溃报错UnicodeDecodeError: ‘gbk‘ codec can‘t decode byte ...。根因分析open()函数在文本模式‘r‘下读取文件时需要将字节解码为字符串。它默认使用系统 locale 的编码在中文Windows上是gbk如果文件实际是用utf-8保存的就会解码失败。解决方案明确指定编码这是最佳实践。with open(‘file.txt‘, ‘r‘, encoding‘utf-8‘) as f:处理编码未知的文件使用errors参数。open(‘file.txt‘, ‘r‘, encoding‘utf-8‘, errors‘ignore‘)会忽略无法解码的字符errors‘replace‘会用替换字符如?替代。探测编码对于完全未知的文件可以使用第三方库chardet来猜测编码但这并非100%准确。关联问题写入文件时也要指定相同的编码否则可能乱码。with open(‘file.txt‘, ‘w‘, encoding‘utf-8‘) as f:5.2 路径问题为什么我的脚本在别人电脑上找不到文件问题场景你在自己电脑上写脚本script.py里面用open(‘data/config.json‘)读取同级目录下的文件运行正常。但发给同事后他运行却报FileNotFoundError。根因分析你使用的是相对路径。当脚本被其他用户在其他目录下通过python /some/other/path/script.py方式运行时当前工作目录os.getcwd()不是脚本所在目录因此相对路径‘data/config.json‘就失效了。解决方案使用__file__构建绝对路径推荐import os from pathlib import Path # 方法1使用 os.path script_dir os.path.dirname(os.path.abspath(__file__)) config_path os.path.join(script_dir, ‘data‘, ‘config.json‘) # 方法2使用 pathlib (更优雅) script_path Path(__file__).resolve() # 获取脚本的绝对路径 config_path script_path.parent / ‘data‘ / ‘config.json‘这样无论从哪里运行脚本都能准确定位到与脚本位置相关的资源文件。5.3 性能问题os.listdir()与Path.glob()遍历大目录问题场景需要遍历一个包含数十万文件的目录使用for file in os.listdir(‘huge_dir‘):感觉非常慢。分析与优化os.listdir()会一次性返回目录下所有文件和文件夹名的列表。对于超大目录构建这个列表会消耗大量内存和时间。Path(‘huge_dir‘).glob(‘*‘)或Path(‘huge_dir‘).iterdir()返回的是一个生成器generator。生成器是惰性求值的它不会一次性把所有结果加载到内存而是每次迭代时产生一个结果这在处理大量文件时能显著降低内存占用并且响应更快。选择建议如果你需要所有文件名的一个列表来进行后续多次随机访问 - 用os.listdir()或list(Path(‘.‘).iterdir())。如果你只是要逐个处理文件且目录可能很大 - 用Path(‘.‘).iterdir()或Path(‘.‘).glob(‘*.txt‘)。5.4 多线程/多进程的“僵尸”与“孤儿”问题场景在multiprocessing中创建了子进程主进程结束后子进程还在运行成了“孤儿进程”。或者子进程异常结束但状态没有被回收成了“僵尸进程”。解决方案使用上下文管理器如前所述使用with multiprocessing.Pool() as pool:上下文管理器会在退出时自动调用pool.terminate()和pool.join()清理进程池。显式管理进程生命周期如果不使用Pool手动创建Process对象后务必在父进程退出前调用process.join()等待子进程结束或调用process.terminate()强制终止。设置守护进程process.daemon True。这样当父进程结束时所有守护子进程会被强制终止。但注意这可能导致子进程来不及清理资源。信号处理在主进程中设置信号处理器如我们日志监控工具中所做在收到终止信号时主动通知并等待所有子进程结束。5.5json序列化自定义对象的通用方案问题场景你想把一个包含datetime对象或自定义类实例的字典用json.dumps()保存但总是报错TypeError: Object of type datetime is not JSON serializable。通用解决方案除了前面提到的为特定类型定义default函数还可以创建一个通用的JSON编码器类。import json from datetime import datetime, date from decimal import Decimal from enum import Enum from uuid import UUID class ExtendedJSONEncoder(json.JSONEncoder): “”“扩展的JSON编码器支持更多Python类型”“” def default(self, obj): # 处理 datetime 和 date if isinstance(obj, (datetime, date)): return obj.isoformat() # 处理 Decimal (常用于金融) if isinstance(obj, Decimal): return float(obj) # 或 str(obj) 以保持精度 # 处理 Enum if isinstance(obj, Enum): return obj.value # 处理 UUID if isinstance(obj, UUID): return str(obj) # 处理有 __dict__ 的自定义对象简单类 if hasattr(obj, ‘__dict__‘): # 注意这可能会序列化一些内部属性可根据需要过滤 return obj.__dict__ # 处理有 to_json 或 to_dict 方法的对象 if hasattr(obj, ‘to_json‘): return obj.to_json() if hasattr(obj, ‘to_dict‘): return obj.to_dict() # 让基类处理其他类型或抛出 TypeError return super().default(obj) # 使用 data { ‘time‘: datetime.now(), ‘id‘: UUID(‘12345678-1234-5678-1234-567812345678‘), ‘amount‘: Decimal(‘99.99‘), } json_str json.dumps(data, clsExtendedJSONEncoder, indent2) print(json_str)这个编码器提供了更大的灵活性可以作为项目的基础工具。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进