酸洗集子类提出不可散列的类型:'list'
Pickling set subclass raises unhashable type: 'list'
此代码(使用自定义 list
subclass)对我来说效果很好:
import pickle
class Numbers(list):
def __init__(self, *numbers: int) -> None:
super().__init__()
self.extend(numbers)
numbers = Numbers(12, 34, 56)
numbers.append(78)
numbers = pickle.loads(pickle.dumps(numbers, protocol=3))
但是当我将父 class 更改为 set
时:
import pickle
class Numbers(set):
def __init__(self, *numbers: int) -> None:
super().__init__()
self.update(numbers)
numbers = Numbers(12, 34, 56)
numbers.add(78)
numbers = pickle.loads(pickle.dumps(numbers, protocol=3))
代码引发 TypeError
与此回溯:
Traceback (most recent call last):
File "test.py", line 11, in <module>
numbers = pickle.loads(pickle.dumps(numbers, protocol=3))
File "test.py", line 6, in __init__
self.update(numbers)
TypeError: unhashable type: 'list'
set
subclass 已成功初始化并运行良好,但尝试 pickle 它会引发一个非常令人困惑的异常,因为 实际上没有使用 list
我的代码.
总结
修改内置类型的构造函数是困难且容易出错的,因为其他方法可能依赖于它。尽可能避免。
错误检查
首先,通过强制 pickle
模块的 Python implementation over the compiled C implementation 我们在回溯中得到更多信息:
Traceback (most recent call last):
File "test.py", line 14, in <module>
numbers = pickle.loads(pickle.dumps(numbers, protocol=3))
File "/usr/local/lib/python3.7/pickle.py", line 1604, in _loads
encoding=encoding, errors=errors).load()
File "/usr/local/lib/python3.7/pickle.py", line 1086, in load
dispatch[key[0]](self)
File "/usr/local/lib/python3.7/pickle.py", line 1437, in load_reduce
stack[-1] = func(*args)
File "test.py", line 6, in __init__
self.update(numbers)
TypeError: unhashable type: 'list'
尽管 documentation 指出:
When a class instance is unpickled, its __init__
method is usually not invoked.
我们可以从上面的回溯中看到,如果 class 的 pickled 表示包含 REDUCE
,则 __init__
方法 被调用 操作码(通常当 class 实现自定义 __reduce__
method) and, as the inspection of the pickled representation 显示时,REDUCE
操作码确实存在:
0: \x80 PROTO 3
2: c GLOBAL '__main__ Numbers'
20: q BINPUT 0
22: ] EMPTY_LIST
23: q BINPUT 1
25: ( MARK
26: K BININT1 56
28: K BININT1 34
30: K BININT1 12
32: K BININT1 78
34: e APPENDS (MARK at 25)
35: \x85 TUPLE1
36: q BINPUT 2
38: R REDUCE
39: q BINPUT 3
41: } EMPTY_DICT
42: q BINPUT 4
44: b BUILD
45: . STOP
解决方案
避免修改构造函数:
import pickle
class Numbers(set):
pass
numbers = Numbers([12, 34, 56])
numbers.add(78)
numbers = pickle.loads(pickle.dumps(numbers, protocol=3))
或者,如果确实需要,至少确保将所有参数传递给父构造函数:
import pickle
class Numbers(set):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
numbers = Numbers([12, 34, 56])
numbers.add(78)
numbers = pickle.loads(pickle.dumps(numbers, protocol=3))
此代码(使用自定义 list
subclass)对我来说效果很好:
import pickle
class Numbers(list):
def __init__(self, *numbers: int) -> None:
super().__init__()
self.extend(numbers)
numbers = Numbers(12, 34, 56)
numbers.append(78)
numbers = pickle.loads(pickle.dumps(numbers, protocol=3))
但是当我将父 class 更改为 set
时:
import pickle
class Numbers(set):
def __init__(self, *numbers: int) -> None:
super().__init__()
self.update(numbers)
numbers = Numbers(12, 34, 56)
numbers.add(78)
numbers = pickle.loads(pickle.dumps(numbers, protocol=3))
代码引发 TypeError
与此回溯:
Traceback (most recent call last):
File "test.py", line 11, in <module>
numbers = pickle.loads(pickle.dumps(numbers, protocol=3))
File "test.py", line 6, in __init__
self.update(numbers)
TypeError: unhashable type: 'list'
set
subclass 已成功初始化并运行良好,但尝试 pickle 它会引发一个非常令人困惑的异常,因为 实际上没有使用 list
我的代码.
总结
修改内置类型的构造函数是困难且容易出错的,因为其他方法可能依赖于它。尽可能避免。
错误检查
首先,通过强制 pickle
模块的 Python implementation over the compiled C implementation 我们在回溯中得到更多信息:
Traceback (most recent call last):
File "test.py", line 14, in <module>
numbers = pickle.loads(pickle.dumps(numbers, protocol=3))
File "/usr/local/lib/python3.7/pickle.py", line 1604, in _loads
encoding=encoding, errors=errors).load()
File "/usr/local/lib/python3.7/pickle.py", line 1086, in load
dispatch[key[0]](self)
File "/usr/local/lib/python3.7/pickle.py", line 1437, in load_reduce
stack[-1] = func(*args)
File "test.py", line 6, in __init__
self.update(numbers)
TypeError: unhashable type: 'list'
尽管 documentation 指出:
When a class instance is unpickled, its
__init__
method is usually not invoked.
我们可以从上面的回溯中看到,如果 class 的 pickled 表示包含 REDUCE
,则 __init__
方法 被调用 操作码(通常当 class 实现自定义 __reduce__
method) and, as the inspection of the pickled representation 显示时,REDUCE
操作码确实存在:
0: \x80 PROTO 3
2: c GLOBAL '__main__ Numbers'
20: q BINPUT 0
22: ] EMPTY_LIST
23: q BINPUT 1
25: ( MARK
26: K BININT1 56
28: K BININT1 34
30: K BININT1 12
32: K BININT1 78
34: e APPENDS (MARK at 25)
35: \x85 TUPLE1
36: q BINPUT 2
38: R REDUCE
39: q BINPUT 3
41: } EMPTY_DICT
42: q BINPUT 4
44: b BUILD
45: . STOP
解决方案
避免修改构造函数:
import pickle
class Numbers(set):
pass
numbers = Numbers([12, 34, 56])
numbers.add(78)
numbers = pickle.loads(pickle.dumps(numbers, protocol=3))
或者,如果确实需要,至少确保将所有参数传递给父构造函数:
import pickle
class Numbers(set):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
numbers = Numbers([12, 34, 56])
numbers.add(78)
numbers = pickle.loads(pickle.dumps(numbers, protocol=3))