Python JSON文件读写全攻略:从基础操作到进阶实战
1. 项目概述为什么JSON是Python开发者的“瑞士军刀”如果你刚开始用Python处理数据或者从其他语言转过来可能会觉得文件操作有点琐碎。但当你真正上手后会发现在Python的世界里处理JSON格式的数据几乎就像喝水一样自然。JSONJavaScript Object Notation早已超越了它名字的起源成为了现代软件开发中数据交换的“普通话”。它结构清晰、人类可读、机器易解析从Web API的响应、配置文件到简单的数据持久化处处都有它的身影。这个项目要解决的就是如何用Python这把“瑞士军刀”优雅、高效且不出错地完成JSON文件的“存”与“取”。这听起来基础但魔鬼藏在细节里。比如你保存的中文数据再次打开时变成了乱码一个复杂的嵌套字典在写入文件时报错了或者你想把JSON数据保存得更美观以便于人工查阅。这些看似简单的问题恰恰是区分“能用”和“用好”的关键。无论是数据分析师需要缓存API拉取的结果还是后端开发者要读写配置文件亦或是自动化脚本需要记录运行状态掌握JSON文件的保存与读取都是必备的核心技能。接下来我会结合我踩过的坑和总结的经验带你从“会操作”到“懂原理”彻底玩转Python中的JSON。2. 核心工具解析标准库json模块的里里外外Python处理JSON的核心武器是其标准库中的json模块。它开箱即用无需安装任何第三方包这为我们的数据持久化任务提供了极大的便利。但直接import json然后调用两个函数就够了吗远不止如此。理解这个模块的设计哲学和内部机制能让你避免很多潜在的麻烦。2.1json模块的双向转换哲学json模块的核心功能其实是序列化Serialization与反序列化Deserialization在Python语境下我们更常称之为编码Encoding和解码Decoding。这个过程不是在直接操作文件而是在内存中的Python对象和JSON格式字符串之间进行转换。序列化 (dump/dumps):将Python对象如dict,list,str,int,float,bool,None转换编码成JSON格式字符串。dump()函数直接将编码后的字符串写入文件对象而dumps()则返回这个字符串本身。反序列化 (load/loads):将JSON格式字符串转换解码回Python对象。load()函数从文件对象中读取并解码loads()则直接从给定的字符串解码。这里有一个非常重要的概念JSON是一种数据格式规范而Python对象是内存中的数据结构。json模块扮演了翻译官的角色。文件读写open()函数只是提供了字符串输入输出的通道。所以完整的流程是写入文件Python对象 - (json.dump) - JSON字符串 - (文件对象.write) - 硬盘文件。读取文件硬盘文件 - (文件对象.read) - JSON字符串 - (json.load) - Python对象。2.2 数据类型映射关系并非无缝对接很多人以为Python字典和JSON对象可以完全划等号这是一个常见的误解。它们大部分相似但存在关键区别忽略这些区别会导致解码错误。Python 对象JSON 对象说明与注意事项dictObject最常用的映射。JSON对象的键必须是字符串。Python字典的键可以是任何可哈希类型如整数、元组但在序列化时非字符串键会被强制转换为字符串。例如{1: “a”}会变成{“1”: “a”}反序列化回来键就是字符串”1″而不是整数1。list, tupleArraytuple在序列化后会被当作list处理反序列化回来默认也是list。如果需要保持tuple类型需要自定义编解码器。strStringJSON字符串必须使用双引号(””)。Python字符串的单引号(”)在序列化后会被转换为双引号。这也是为什么有时候手动编辑JSON文件用错了引号会导致解析失败。int, floatNumberPython的int和float对应JSON的数值。但Python特有的float(‘inf’)无穷大、float(‘nan’)非数字在JSON标准中没有定义默认序列化会报错。True / Falsetrue / false注意大小写。JSON是小写的true和falsePython是大写的True和False。NonenullJSON是小写的null。其他set,datetime, 自定义类无直接对应默认无法序列化尝试序列化这些类型会引发TypeError: Object of type … is not JSON serializable。这是新手最常遇到的错误之一。注意这个映射表是理解一切JSON操作的基础。特别是“非字符串键转换”和“自定义对象无法序列化”这两点是实际编码中最主要的错误来源。2.3 关键参数详解控制序列化的细节json.dump()和json.dumps()提供了一系列参数来控制输出格式善用它们能让你的JSON文件更专业。indent: 这是让JSON文件变得“美观”的关键参数。传入一个整数如2或4表示缩进的空格数。设置后输出会带有换行和缩进极大提升了可读性。但请注意这也会增加文件体积多了很多空格和换行符如果文件纯粹由机器读取可以不设置以节省空间。ensure_ascii: 这是中文开发者必须关注的参数默认值为True。当它为True时所有非ASCII字符如中文、日文、表情符号都会被转义为\uXXXX形式的Unicode码点。例如“中国”会变成”\u4e2d\u56fd”。虽然解析不受影响但人眼完全无法阅读。将其设置为False这些字符就会以原样保存。separators: 这是一个高级参数用于控制JSON中各项之间的分隔符。默认是(‘, ‘, ‘: ‘)即逗号后有一个空格冒号后有一个空格。你可以将其改为(‘,’, ‘:’)来移除所有不必要的空格实现最紧凑的存储。这在追求极致文件体积时有用。sort_keys: 设置为True时输出的字典JSON对象按键的字母顺序排序。这能确保每次生成的JSON字符串一致对于需要比较或版本控制的场景很有用。一个综合使用的例子import json data { “name”: “张三”, “age”: 30, “skills”: [“Python”, “数据分析”], “is_employed”: True } # 美观且包含中文的JSON字符串 pretty_json_str json.dumps(data, ensure_asciiFalse, indent2, sort_keysTrue) print(pretty_json_str)输出将是格式清晰、包含中文的字符串非常适合作为配置文件。3. 基础操作实战从文件读写到异常处理理解了核心工具后我们进入实战环节。基础的读写操作虽然简单但健壮的代码必须考虑文件不存在、格式错误等各种边界情况。3.1 写入JSON文件dump() 的典型用法将数据写入文件我们使用json.dump(obj, fp, …)。这里的关键是理解fp是一个文件对象通常由open()函数在写入模式’w’或’wt’下创建。标准写入流程import json # 要保存的数据一个嵌套的字典结构 user_data { “user_id”: 1001, “profile”: { “name”: “李四”, “preferences”: {“theme”: “dark”, “language”: “zh-CN”} }, “login_history”: [“2023-10-01”, “2023-10-05”] } # 1. 打开文件。使用 ‘w’ 模式写入如果文件已存在则覆盖。 # 2. 指定编码为 ‘utf-8’这是处理中文等文本的黄金标准避免乱码。 # 3. 使用 with 语句确保文件操作完成后自动正确关闭即使发生异常。 with open(‘user_data.json’, ‘w’, encoding‘utf-8’) as f: # 将数据序列化并写入文件 # 设置 ensure_asciiFalse 让中文正常显示 # 设置 indent4 让文件结构清晰便于后续手动查看或编辑 json.dump(user_data, f, ensure_asciiFalse, indent4) print(“数据已保存至 user_data.json”)执行后user_data.json文件的内容将是格式优美、包含中文的JSON。实操心得with语句和编码务必使用with语句它管理文件的上下文自动处理文件的打开和关闭能有效避免因忘记关闭文件导致的数据丢失或资源泄露。这是Pythonic的写法。显式指定encoding’utf-8’在Windows和macOS上open()的默认编码可能是系统本地编码如GBK、CP1252。如果不指定当数据包含非ASCII字符时在不同系统间迁移文件就可能出现乱码。utf-8是跨平台、跨语言的通用编码永远将其作为文本文件的默认编码。3.2 读取JSON文件load() 的典型用法从文件读取数据我们使用json.load(fp)。标准读取流程import json # 1. 打开文件。使用 ‘r’ 模式读取。 # 2. 同样指定编码为 ‘utf-8’与写入时保持一致。 with open(‘user_data.json’, ‘r’, encoding‘utf-8’) as f: # 从文件对象反序列化数据 loaded_data json.load(f) print(f”读取的数据类型{type(loaded_data)}”) # class ‘dict’ print(f”用户姓名{loaded_data[‘profile’][‘name’]}”) # 李四 print(“完整数据“, loaded_data)现在loaded_data变量就包含了之前保存的完整字典结构你可以像操作普通Python字典一样访问和修改它。3.3 字符串与对象的互转dumps() 和 loads()当你的数据不是来自文件或者你不需要立即保存到文件时dumps()和loads()就派上用场了。它们在网络通信、进程间数据传输、或者对数据进行预处理时非常有用。使用场景示例import json # 场景1将数据转换为字符串用于网络传输或日志记录 data_dict {“status”: “success”, “code”: 200} json_str json.dumps(data_dict) # 序列化为字符串 print(f”JSON字符串{json_str}, 类型{type(json_str)}“) # 类型是 str # 场景2接收到的网络响应是JSON字符串需要解析 response_str ‘{“status”: “error”, “message”: “File not found”}’ response_dict json.loads(response_str) # 反序列化为字典 print(f”错误信息{response_dict[‘message’]}“) # File not found # 场景3美观地打印复杂数据结构调试技巧 complex_data {“users”: [{“id”: i, “name”: f”User{i}”} for i in range(5)]} print(json.dumps(complex_data, indent2)) # 用缩进格式化输出比直接print(complex_data)更清晰3.4 异常处理让代码更健壮文件操作和数据处理天生就容易出错。一个健壮的程序必须处理这些异常。常见的异常类型FileNotFoundError尝试读取一个不存在的文件。json.JSONDecodeError文件内容不是有效的JSON格式如格式错误、尾随逗号、错误的引号。PermissionError没有文件读写权限。UnicodeDecodeError文件编码与指定的encoding参数不匹配。带异常处理的读取示例import json file_path ‘some_data.json’ try: with open(file_path, ‘r’, encoding‘utf-8’) as f: data json.load(f) print(“数据读取成功”) except FileNotFoundError: print(f”错误文件 ‘{file_path}’ 未找到。请检查路径。“) except json.JSONDecodeError as e: print(f”错误文件 ‘{file_path}’ 不是有效的JSON格式。“) print(f”解析错误详情{e.msg}位于第{e.lineno}行第{e.colno}列附近。“) except UnicodeDecodeError: print(f”错误文件 ‘{file_path}’ 的编码可能不是 ‘utf-8’请检查文件来源。“) except Exception as e: print(f”发生了未预期的错误{type(e).__name__}: {e}“) else: # 只有在try块成功执行时才运行的代码 print(f”成功加载了 {len(data)} 条记录。“) finally: # 无论是否发生异常都会执行的代码通常用于清理资源 # 由于使用了with语句文件关闭已自动处理这里可以放其他清理逻辑 print(“文件读取操作结束。”)提示在生产环境中JSONDecodeError异常非常有用。它的异常对象e包含了msg错误信息、doc出错的文档内容、pos错误位置、lineno行号和colno列号能帮你快速定位JSON文件中的语法错误。4. 进阶技巧与性能优化当你掌握了基础操作后可能会遇到更复杂的需求比如要保存日期时间、自定义类的对象或者处理巨大的JSON文件。这时就需要一些进阶技巧。4.1 处理非标准类型自定义编解码器如前所述json模块默认无法处理datetime,set或自定义类的对象。解决方案是自定义编解码器主要有两种方式自定义函数和继承JSONEncoder/JSONDecoder。方法一使用default和object_hook参数这是最灵活、最常用的方法。default: 在dump/dumps时使用。当遇到无法序列化的对象时会调用default指定的函数。该函数应将该对象转换为可序列化的类型如字典。object_hook: 在load/loads时使用。当解析出一个JSON对象字典后会调用object_hook指定的函数。你可以检查字典内容并将其转换回自定义类型的实例。示例序列化和反序列化datetime对象import json from datetime import datetime, date # 待序列化的数据包含datetime对象 data_with_date { “event”: “项目会议”, “time”: datetime.now(), # 这是一个datetime对象 “participants”: {“Alice”, “Bob”, “Charlie”} # 这是一个set对象 } # 自定义序列化函数 def custom_serializer(obj): “”“将非标准类型转换为可序列化的类型。”“” if isinstance(obj, datetime): # 将datetime转换为ISO格式的字符串 return obj.isoformat() elif isinstance(obj, date): return obj.isoformat() elif isinstance(obj, set): # 将set转换为list return list(obj) else: # 对于其他无法处理的类型抛出TypeError raise TypeError(f”Object of type {obj.__class__.__name__} is not JSON serializable”) # 序列化 json_str json.dumps(data_with_date, defaultcustom_serializer, indent2) print(“序列化后的JSON“) print(json_str) # 输出内容中time字段是字符串如 “2023-10-27T10:30:00.123456″participants是数组。 # 自定义反序列化函数 def custom_deserializer(dct): “”“尝试将特定的字典结构还原为对象。”“” # 检查是否有我们自定义的标记字段这里用字段名和格式判断 if “event” in dct and “time” in dct and ‘T’ in dct[“time”]: try: # 尝试将time字段的字符串解析回datetime对象 dct[“time”] datetime.fromisoformat(dct[“time”]) # 将participants列表转回set如果存在 if “participants” in dct and isinstance(dct[“participants”], list): dct[“participants”] set(dct[“participants”]) except (ValueError, AttributeError): pass # 如果解析失败保持原样 return dct # 反序列化 loaded_data json.loads(json_str, object_hookcustom_deserializer) print(f”\n反序列化后的time类型{type(loaded_data[‘time’])}“) # class ‘datetime.datetime’ print(f”反序列化后的participants类型{type(loaded_data[‘participants’])}“) # class ‘set’方法二继承json.JSONEncoder和json.JSONDecoder当你的自定义序列化逻辑非常复杂或者需要在多个地方复用时可以创建自定义的编码器/解码器类。import json from datetime import datetime class CustomEncoder(json.JSONEncoder): “”“自定义JSON编码器。”“” def default(self, obj): if isinstance(obj, datetime): return {“__datetime__”: obj.isoformat()} # 添加一个特殊键以标识类型 # 可以继续添加其他类型的处理逻辑 return super().default(obj) # 对于其他类型调用父类方法会抛出TypeError class CustomDecoder(json.JSONDecoder): “”“自定义JSON解码器。”“” def __init__(self, *args, **kwargs): # 指定object_hook并调用父类初始化 super().__init__(object_hookself.object_hook, *args, **kwargs) def object_hook(self, dct): if “__datetime__” in dct: # 根据特殊键还原对象 return datetime.fromisoformat(dct[“__datetime__”]) return dct # 使用自定义编码器 data {“now”: datetime.now()} json_str json.dumps(data, clsCustomEncoder) print(json_str) # {“now”: {“__datetime__”: “2023-10-27T10:30:00.123456″}} # 使用自定义解码器 loaded_data json.loads(json_str, clsCustomDecoder) print(type(loaded_data[‘now’])) # class ‘datetime.datetime’4.2 处理大型JSON文件流式读取与写入当JSON文件非常大几百MB甚至GB级别时一次性加载到内存json.load()可能导致内存溢出。此时需要流式处理。场景处理一个每行是一个独立JSON对象的大文件JSON Lines格式.jsonlimport json # 假设有一个巨大的日志文件 logs.jsonl每行是一个JSON对象 input_file ‘logs.jsonl’ output_file ‘filtered_logs.jsonl’ # 流式读取逐行处理 with open(input_file, ‘r’, encoding‘utf-8’) as infile, \ open(output_file, ‘w’, encoding‘utf-8’) as outfile: for line_num, line in enumerate(infile, 1): line line.strip() # 去除首尾空白字符 if not line: continue # 跳过空行 try: record json.loads(line) # 解析单行JSON # 进行过滤操作例如只保留错误级别的日志 if record.get(“level”) “ERROR”: # 将过滤后的记录写入新文件同样每行一个JSON json.dump(record, outfile, ensure_asciiFalse) outfile.write(‘\n’) # 写入换行符 except json.JSONDecodeError as e: print(f”警告第{line_num}行JSON格式错误已跳过。错误{e}“) continue print(“流式过滤完成。”)这种方式内存占用极小只与单行数据的大小有关非常适合处理海量数据。4.3 性能考量第三方库 ujson 和 orjsonPython标准库的json模块在大多数情况下性能足够好。但在对性能有极致要求的场景如高频微服务、实时数据处理可以考虑使用用C语言实现的第三方库它们通常快一个数量级。ujson (UltraJSON): 安装pip install ujson。API与标准库json模块基本兼容但不完全一致例如参数名可能不同对某些数据类型的处理有差异。速度极快尤其是在序列化方面。import ujson data {“key”: “value” * 1000} # 用法类似 json_str ujson.dumps(data) data_loaded ujson.loads(json_str)orjson: 安装pip install orjson。这是目前性能最强的Python JSON库之一且通常能产生更小的输出。它的API与标准库差异较大例如orjson.dumps()返回的是bytes而不是str但功能也更强大默认支持datetime,UUID,numpy数组等类型的序列化。import orjson data {“key”: “value”} # orjson.dumps() 返回 bytes json_bytes orjson.dumps(data) # orjson.loads() 可以接受 bytes 或 str data_loaded orjson.loads(json_bytes)选择建议除非你确实遇到了性能瓶颈否则优先使用标准库json。它的兼容性最好文档最全所有Python环境都可用。如果决定使用第三方库务必仔细阅读其文档注意API差异和数据类型支持范围并在关键代码路径上进行充分的测试和基准测试。5. 常见问题排查与实战心得即使理解了所有原理在实际操作中依然会遇到各种“坑”。下面是我总结的一些高频问题和解决技巧。5.1 编码问题中文乱码的根源与解决问题现象保存的中文在文件里显示为\u4e2d\u6587这样的Unicode转义序列或者用其他文本编辑器打开时是乱码。根本原因与解决方案序列化时未关闭ASCII转义这是最常见的原因。在json.dump()或json.dumps()时没有设置ensure_asciiFalse。解决方案始终在需要保存非ASCII字符时加上ensure_asciiFalse。文件打开编码不一致写入时用了utf-8但读取时用了其他编码如gbk或者没有指定编码依赖系统默认。解决方案在所有文件操作中显式、统一地指定encoding’utf-8’。终端或编辑器不支持UTF-8数据本身是正确的但显示它的终端或文本编辑器没有使用UTF-8编码查看。解决方案确保你的开发环境如VSCode、PyCharm和终端如设置chcp 65001在Windows CMD中使用UTF-8编码。5.2 类型错误无法序列化对象的终极排查问题现象执行json.dump()时抛出TypeError: Object of type … is not JSON serializable。排查步骤定位问题对象错误信息会告诉你哪个类型无法序列化如Object of type datetime is not JSON serializable。检查数据结构你的数据中很可能包含了datetime,set, 自定义类的实例或者numpy数组等。使用default参数如4.1节所述提供一个default处理函数将这些特殊类型转换为基本类型字符串、数字、列表、字典。递归检查对于复杂的嵌套结构问题可能藏在很深的层级。可以写一个辅助函数来遍历和检查数据结构import json from datetime import datetime def debug_serialize(obj): try: # 尝试序列化一小部分 json.dumps(obj) return True except TypeError as e: print(f”无法序列化的对象: {obj}, 类型: {type(obj)}“) # 如果是字典或列表递归检查其元素 if isinstance(obj, dict): for k, v in obj.items(): if not debug_serialize(v): print(f” 问题出现在键 ‘{k}’ 对应的值上。”) elif isinstance(obj, (list, tuple)): for i, item in enumerate(obj): if not debug_serialize(item): print(f” 问题出现在索引 {i} 的元素上。”) return False complex_data {“id”: 1, “time”: datetime.now(), “set”: {1, 2, 3}} debug_serialize(complex_data)5.3 格式错误JSON解码失败的原因问题现象执行json.load()时抛出json.JSONDecodeError。常见原因与解决文件内容根本不是JSON可能误读了文本文件、CSV文件等。用文本编辑器打开文件确认内容。语法错误尾随逗号JSON标准不允许在对象或数组的最后一个元素后加逗号。{“a”: 1,}是错误的。一些JavaScript引擎允许但Python的解析器严格遵守标准。引号错误JSON要求字符串必须使用双引号(””)。使用单引号(”)会导致解析失败。注释JSON标准不支持注释。在JSON文件中添加//或/* */会导致错误。如果你需要带注释的配置文件可以考虑使用YAML或TOML格式或者使用json5这样的第三方库支持JSON超集。文件编码问题文件可能包含BOM字节顺序标记或其他不可见字符。确保以正确的编码如utf-8-sig可以处理带BOM的UTF-8打开。使用在线校验工具将出错的JSON内容复制到 JSONLint 这类在线校验工具中可以快速定位语法错误的具体位置。5.4 路径问题文件找不到的几种情况问题现象FileNotFoundError: [Errno 2] No such file or directory: ‘data.json’。排查与解决相对路径 vs 绝对路径open(‘data.json’)使用的是相对路径相对于当前Python脚本的工作目录。工作目录不一定是脚本所在目录。使用os.getcwd()打印当前工作目录检查data.json是否在这个目录下。使用绝对路径最可靠的方式是使用绝对路径。可以通过os.path模块来构建。import os script_dir os.path.dirname(os.path.abspath(__file__)) # 获取脚本所在目录的绝对路径 file_path os.path.join(script_dir, ‘data’, ‘data.json’) # 构建目标文件的绝对路径 with open(file_path, ‘r’, encoding‘utf-8’) as f: …检查文件名和扩展名确认文件名拼写正确注意大小写在Linux/Mac系统下是大小写敏感的。确认文件扩展名是.json而不是.txt或其他。5.5 性能问题处理大文件时内存不足问题现象读取一个几百MB的JSON文件时程序内存占用飙升甚至崩溃。解决方案流式处理如果文件格式允许如每行一个JSON对象的JSON Lines格式使用4.2节介绍的方法逐行处理。使用ijson库如果文件是一个巨大的单一JSON数组或对象可以使用ijson库pip install ijson。它允许你以流的方式迭代解析JSON元素而不需要一次性将整个文件加载到内存。import ijson # 迭代解析一个大型JSON数组中的每个对象 with open(‘huge_array.json’, ‘rb’) as f: # ijson 需要二进制模式打开 for item in ijson.items(f, ‘item’): # ‘item’ 是数组元素的路径 process(item) # 处理每一个元素重新评估数据格式对于超大的、需要频繁读写的数据集JSON可能不是最高效的格式。可以考虑使用二进制格式如pickle但注意Python版本兼容性和安全性、专门的列式存储如Parquet配合pandas或数据库。6. 综合应用案例一个简单的配置管理系统为了将上述所有知识点串联起来我们来实现一个简单的应用配置管理系统。这个系统需要能读取JSON格式的配置文件在程序运行时修改配置并能将修改后的配置写回文件。同时要能处理一些自定义类型如日志级别枚举。import json import os from enum import Enum from datetime import datetime from typing import Any, Dict class LogLevel(Enum): “”“自定义枚举类型表示日志级别。”“” DEBUG “DEBUG” INFO “INFO” WARNING “WARNING” ERROR “ERROR” class AppConfig: “”“应用程序配置类。”“” def __init__(self, config_path: str): self.config_path config_path self.data: Dict[str, Any] self._load_config() def _load_config(self) - Dict[str, Any]: “”“从文件加载配置。如果文件不存在则使用默认配置并保存。”“” default_config { “app_name”: “MyApp”, “version”: “1.0.0”, “debug”: False, “log_level”: LogLevel.INFO.value, # 存储枚举的值 “database”: { “host”: “localhost”, “port”: 5432 }, “last_updated”: None # 将被datetime对象替换 } if not os.path.exists(self.config_path): print(f”配置文件 ‘{self.config_path}’ 不存在创建默认配置。”) self._save_config(default_config) return default_config try: with open(self.config_path, ‘r’, encoding‘utf-8’) as f: loaded_data json.load(f) # 合并默认配置和加载的配置确保新版本有新增的配置项 merged_config {**default_config, **loaded_data} # 尝试将log_level的字符串值转换回LogLevel枚举可选 # 这里为了简单我们只存储值不转换回枚举对象 return merged_config except (FileNotFoundError, json.JSONDecodeError) as e: print(f”加载配置文件失败 ({e})使用默认配置。”) return default_config def _save_config(self, config_data: Dict[str, Any]) - None: “”“将配置字典保存到文件。”“” # 在保存前更新‘最后更新时间’ config_data[“last_updated”] datetime.now().isoformat() # 自定义序列化函数 def _custom_encoder(obj: Any) - Any: if isinstance(obj, datetime): return obj.isoformat() elif isinstance(obj, Enum): return obj.value raise TypeError(f”Type {type(obj)} not serializable”) try: # 先写入临时文件避免写入过程中出错导致原配置文件损坏 temp_path self.config_path ‘.tmp’ with open(temp_path, ‘w’, encoding‘utf-8’) as f: json.dump(config_data, f, default_custom_encoder, ensure_asciiFalse, indent4) # 原子操作用临时文件替换原文件在Unix和Windows上均有效 os.replace(temp_path, self.config_path) print(f”配置已保存至 {self.config_path}”) except IOError as e: print(f”保存配置文件失败: {e}”) def get(self, key: str, default: Any None) - Any: “”“安全地获取配置项。”“” keys key.split(‘.’) # 支持点号分隔的嵌套键如 ‘database.host’ value self.data try: for k in keys: value value[k] return value except (KeyError, TypeError): return default def set(self, key: str, value: Any) - None: “”“设置配置项并自动保存。”“” keys key.split(‘.’) data self.data # 遍历到倒数第二个键确保路径存在 for k in keys[:-1]: data data.setdefault(k, {}) # 设置最终的值 data[keys[-1]] value # 保存到文件 self._save_config(self.data) def __str__(self) - str: “”“以美观的格式打印当前配置。”“” return json.dumps(self.data, ensure_asciiFalse, indent2) # 使用示例 if __name__ “__main__”: config AppConfig(‘my_app_config.json’) print(“当前配置“) print(config) # 修改配置 config.set(‘debug’, True) config.set(‘database.port’, 3306) config.set(‘new_setting’, {‘timeout’: 30}) print(“\n修改后的配置“) print(config) # 获取配置 db_host config.get(‘database.host’) log_level config.get(‘log_level’, ‘INFO’) non_existent config.get(‘some.deep.nonexistent.key’, ‘default_value’) print(f”\n数据库主机: {db_host}“) print(f”日志级别: {log_level}“) print(f”不存在的键: {non_existent}“)这个案例综合运用了安全的文件读写使用with语句和异常处理。编码与格式化使用ensure_asciiFalse和indent。处理非标准类型通过default参数处理datetime和Enum。原子性写入先写临时文件再替换防止写入中途出错导致配置文件损坏。配置合并策略加载时与默认配置合并保证兼容性。嵌套键的支持通过split(‘.’)实现了类似config.get(‘a.b.c’)的访问方式。通过这样一个完整的项目你应该能深刻体会到JSON文件的读写不仅仅是两个函数调用它涉及到编码、类型系统、文件IO、异常处理、数据结构和软件设计等多个方面。把这些细节都处理好你的程序才会健壮、可靠。在实际开发中对于更复杂的配置你可能会考虑使用configparserINI格式、pyyamlYAML格式或专门的配置管理库但理解JSON这一基础是掌握所有其他格式的基石。