酸洗集子类提出不可散列的类型:'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))