Класс, производный от collections.Counter теряет значения при мариновании - PullRequest
1 голос
/ 25 мая 2020

Я хочу создать класс, который работает как счетчик, но имеет некоторые дополнительные функции. Вот урезанная версия:

from collections import Counter
import pickle

class DerivedCounter(Counter):
    def __init__(self, *args, capacity: int = 10):
        super().__init__(*args)
        self._capacity = capacity

dc = DerivedCounter(capacity = 200)
print("Original", dc._capacity)
print("Pickled", pickle.loads(pickle.dumps(dc))._capacity)

При использовании Dask этот объект становится маринованным и не очищенным. К сожалению, значение _capacity теряется по пути, и на выходе получается:

Original 200
Pickled 10

Кажется, используется значение по умолчанию вместо того, которое было выбрано при создании объекта! Когда я наследую от dict вместо Counter, я получаю следующее:

Original 200
Pickled 200

Так что же такого странного в Counter и как я могу это обойти?

Best, Boris

Ответы [ 3 ]

1 голос
/ 26 мая 2020

@ jasonharper, большое спасибо за объяснение в вашем комментарии! Функция Counter __reduce__ действительно кажется немного особенной. Например, функции __getstate__, __setstate__, __getnewargs__ и __getnewargs_ex__ не вызываются. Итак, переопределение __reduce__ похоже на go.

Вот переопределение, о котором вы говорите: https://github.com/python/cpython/blob/3.7/Lib/collections/ init .py # L697

Мне непонятно, почему Counter понадобится это. Фактически, восстановление его функции __reduce__ до функции dict, похоже, решает мою проблему:

from collections import Counter
import pickle

class DerivedCounter(Counter):
    def __init__(self, *args, capacity: int = 10):
        super().__init__(*args)
        self._capacity = capacity

    def __reduce__(self):
        return dict.__reduce__(self)

dc = DerivedCounter(capacity = 200)
print("Original", dc._capacity)
print("Pickled", pickle.loads(pickle.dumps(dc))._capacity)

Теперь результат такой, как я ожидал:

Original 200
Pickled 200

И восстановленный объект кажется полностью неповрежденным с точки зрения его функции счетчика. Так что, может быть, переопределение __reduce__ в Counter устарело, если не вредно?

Еще раз спасибо!

0 голосов
/ 25 мая 2020

Вы можете использовать модуль copyreg (встроенный модуль copyreg ), чтобы делать то, что вы хотите. Он скопирует все состояние в созданном вами экземпляре.

class DerivedCounter(Counter):
def __init__(self, capacity: int = 10, *args):
    print('init')
    super().__init__(*args)
    self._capacity = capacity  

def pickle_dc(d):
    print("pickling a DerivedCounter instance...")
    return DerivedCounter, (d._capacity, { key: value for key, value in 
dc.items()})

copyreg.pickle(DerivedCounter, pickle_dc)
dc = DerivedCounter(200, {'a':10})
print(dc._capacity)
a = pickle.dumps(dc)

print(pickle.loads(a)._capacity)
c = pickle.loads(a)

В pickle do c мы видим, что загрузка pickle не вызывает __init__ do c здесь

0 голосов
/ 25 мая 2020

Вы мариноваете весь класс. Когда вы распаковываете его, он называется fre sh.

from collections import Counter
import pickle

class DerivedCounter(Counter):
    def __init__(self, *args, capacity=10):
        print('dc', args, capacity)    #<---add this line

        Counter.__init__(self, *args)
        self._capacity = capacity

dc = DerivedCounter(capacity=200)
print(pickle.loads(pickle.dumps(dc))._capacity)

#>>> dc () 200
#>>> dc ({},) 10
#>>> 10

Может быть достаточно маринования dc.__dict__.

from collections import Counter
import pickle

class Pickler:
    @property       
    def pickled(self):
        return pickle.dumps(self.__dict__)

    def __init__(self, pickled=None):
        if pickled:
            self.cucumbered(pickled)

    def cucumbered(self, pickled):
        self.__dict__ = pickle.loads(pickled)
        return self


class DerivedCounter(Counter, Pickler):
    def __init__(self, *args, capacity=10, pickled=None):
        Counter.__init__(self, *args)
        Pickler.__init__(self, pickled)
        if not pickled:
            self._capacity = capacity


dc = DerivedCounter(capacity=200)
print(dc.cucumbered(dc.pickled)._capacity)    # 200

dc2 = DerivedCounter(pickled=dc.pickled)    
print(dc2._capacity)                          # 200
...