使用pickle加载Python类时出现__init__参数缺失错误
问题解决与替代方案
为什么pickle加载会报错?
pickle反序列化时默认会调用类的__init__方法,但你的Building类__init__要求必填的name和jobs参数,而pickle不会自动传递这些参数,因此抛出TypeError。给参数加默认值虽然能加载,但会跳过原有属性的恢复,导致数据丢失。
修复pickle序列化的方法
方法1:实现__getstate__和__setstate__方法
这两个方法可以自定义序列化/反序列化的属性处理逻辑,绕过__init__的调用,直接恢复实例原有属性:
import pickle class Building: def __init__(self, name, jobs): self.name = name self.jobs = jobs def __getstate__(self): # 返回需要保存的实例属性字典 return self.__dict__ def __setstate__(self, state): # 直接将保存的属性字典赋值给实例,不调用__init__ self.__dict__.update(state) class Kingdom: def __init__(self): self.buildings = [] # 测试代码 kingdom = Kingdom() kingdom.buildings.append(Building("Castle", ["knight", "archer"])) # 保存 with open("kingdom.pkl", "wb") as f: pickle.dump(kingdom, f) # 加载 with open("kingdom.pkl", "rb") as f: loaded_kingdom = pickle.load(f) print(loaded_kingdom.buildings[0].name) # 输出 Castle
方法2:使用__reduce__方法(更底层)
返回一个元组指定反序列化的构造方式,适合需要更精细控制的场景:
import pickle class Building: def __init__(self, name, jobs): self.name = name self.jobs = jobs def __reduce__(self): # 返回(类构造函数,构造参数,实例属性字典) return (self.__class__, (self.name, self.jobs), self.__dict__) # Kingdom类和测试代码同上
其他保存包含子类实例对象的方法
1. JSON序列化
JSON是可读的文本格式,适合跨语言场景,但需要自定义编码/解码逻辑:
import json class Building: def __init__(self, name, jobs): self.name = name self.jobs = jobs class Kingdom: def __init__(self): self.buildings = [] # 自定义JSON编码器 class GameEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, (Building, Kingdom)): return {"__type__": type(obj).__name__, **obj.__dict__} return super().default(obj) # 自定义JSON解码器 def game_decoder(obj): if obj.get("__type__") == "Building": b = Building(obj["name"], obj["jobs"]) return b elif obj.get("__type__") == "Kingdom": k = Kingdom() k.buildings = [game_decoder(b) for b in obj["buildings"]] return k return obj # 保存 kingdom = Kingdom() kingdom.buildings.append(Building("Farm", ["farmer", "shepherd"])) with open("kingdom.json", "w", encoding="utf-8") as f: json.dump(kingdom, f, cls=GameEncoder, indent=2) # 加载 with open("kingdom.json", "r", encoding="utf-8") as f: loaded_kingdom = json.load(f, object_hook=game_decoder)
2. 数据类+JSON/pickle
如果你的类以存储数据为主,用dataclasses可以简化序列化逻辑:
from dataclasses import dataclass, asdict import json @dataclass class Building: name: str jobs: list[str] @dataclass class Kingdom: buildings: list[Building] # 保存 kingdom = Kingdom([Building("Mill", ["miller"])]) with open("kingdom.json", "w", encoding="utf-8") as f: json.dump(asdict(kingdom), f, indent=2) # 加载 with open("kingdom.json", "r", encoding="utf-8") as f: data = json.load(f) loaded_kingdom = Kingdom([Building(**b) for b in data["buildings"]])
3. SQLite数据库存储
适合需要持久化大量数据或需要查询功能的场景,将对象属性映射到数据库表:
import sqlite3 class Building: def __init__(self, name, jobs): self.name = name self.jobs = jobs class Kingdom: def __init__(self, name="MyKingdom"): self.name = name self.buildings = [] # 初始化数据库连接 conn = sqlite3.connect("game_save.db") cursor = conn.cursor() # 创建表 cursor.execute(""" CREATE TABLE IF NOT EXISTS kingdoms ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL ) """) cursor.execute(""" CREATE TABLE IF NOT EXISTS buildings ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, jobs TEXT NOT NULL # 用逗号分隔列表 ) """) cursor.execute(""" CREATE TABLE IF NOT EXISTS kingdom_buildings ( kingdom_id INTEGER, building_id INTEGER, FOREIGN KEY(kingdom_id) REFERENCES kingdoms(id), FOREIGN KEY(building_id) REFERENCES buildings(id), PRIMARY KEY(kingdom_id, building_id) ) """) conn.commit() # 保存王国 def save_kingdom(kingdom): cursor.execute("INSERT INTO kingdoms (name) VALUES (?)", (kingdom.name,)) kingdom_id = cursor.lastrowid for b in kingdom.buildings: cursor.execute("INSERT INTO buildings (name, jobs) VALUES (?, ?)", (b.name, ",".join(b.jobs))) building_id = cursor.lastrowid cursor.execute("INSERT INTO kingdom_buildings VALUES (?, ?)", (kingdom_id, building_id)) conn.commit() # 加载王国 def load_kingdom(kingdom_id=1): cursor.execute("SELECT name FROM kingdoms WHERE id=?", (kingdom_id,)) kingdom_name = cursor.fetchone()[0] kingdom = Kingdom(kingdom_name) cursor.execute(""" SELECT b.name, b.jobs FROM buildings b JOIN kingdom_buildings kb ON b.id=kb.building_id WHERE kb.kingdom_id=? """, (kingdom_id,)) for name, jobs_str in cursor.fetchall(): jobs = jobs_str.split(",") kingdom.buildings.append(Building(name, jobs)) return kingdom # 测试 kingdom = Kingdom("NorthLand") kingdom.buildings.append(Building("Blacksmith", ["blacksmith", "apprentice"])) save_kingdom(kingdom) loaded_kingdom = load_kingdom() print(loaded_kingdom.name, loaded_kingdom.buildings[0].name)
内容的提问来源于stack exchange,提问作者igr2020
相关产品推荐
相关产品推荐

