#python #serialization #pickle
#python #сериализация #удаление
Вопрос:
Допустим, у меня есть класс:
class Character():
def __init__(self):
self.race = "Ork"
Я создаю экземпляр и обрабатываю его.
c = Character()
import pickle
with open(r'C:tmpstate.bin', 'w b') as f:
pickle.dump(c, f)
Когда я пытаюсь отменить выбор, все работает нормально.
Но что, если я захочу добавить другой атрибут к Character?
Я перехожу и к этому:
class Character():
def __init__(self):
self.race = "Ork"
self.health = 100
Допустим, я хочу отменить выбор старой версии, где у нас нет health
атрибута. Если я просто распаковываю данные из файла, у объекта не будет health
атрибута. Чтобы сделать это надлежащим образом, следуя тому, что написано в книге «Эффективный Python», мне нужно ввести аргументы со значениями по умолчанию и использовать copyreg
.
Итак, я делаю это:
class Character
def __init__(self, race = "Ork", health = 100):
self.race = race
self.health = health
import copyreg
def pickle_character(state):
kwargs = state.__dict__
return unpickle_character, (kwargs, )
def unpickle_character(kwargs):
return Character(**kwargs)
copyreg.pickle(Character, pickle_character)
Теперь отмена выбора должна работать нормально:
with open(r'C:tmpstate.bin', 'rb') as f:
c = pickle.load(f)
Этот код работает нормально, однако я все еще не вижу в c
объекте нашего нового health
атрибута.
Вопрос прост, почему это происходит? Все должно работать нормально в соответствии с «Эффективным Python».
Ответ №1:
Стандартное поведение для отмены выбора напрямую присваивает атрибуты — оно не использует __init__
or __new__
. Следовательно, ваши аргументы по умолчанию не применяются.
Когда экземпляр класса не выбран, его
__init__()
метод обычно не вызывается. 1
Вызов __init__
может иметь побочные эффекты и может принимать дополнительные, меньшие или иные параметры, чем атрибуты. Это делает его небезопасным по умолчанию. По сути, pickle использует object.__new__(cls)
для создания экземпляра, а затем обновляет его __dict__
.
Вы должны явно указать, pickle
использовать __init__
, если хотите.
При использовании copyreg
вы должны передать ему constructor
параметр. Обратите внимание, что у этого есть подпись, отличная от вашей unpickle_character
.
В противном случае ваша функция травления ( pickle_character
) статически определяет функцию, используемую для распаковки. Поскольку для Character
класса не зарегистрирован конструктор, а в старом pickle он не включен, загрузка старого pickle не вызывает ваш конструктор.
def pickle_character(state):
kwargs = state.__dict__
return unpickle_character, (kwargs, )
# ^ unpickler stored for *newly pickled instance*!
# no constuctor stored for *Character class* v
copyreg.pickle(Character, pickle_character)
Это проще определить __setstate__
в вашем классе. Это напрямую получает состояние, даже из старых рассолов.
class Character:
def __init__(self, race, health):
self.race = race
self.health = health
# load state with defaults for missing attributes
def __setstate__(self, state):
self.race = state.get('race', 'Ork')
self. health = state.get('health', 100)
Если вы знаете, что это __init__
безопасно и обратно совместимо, вы также можете использовать его для инициализации из обработанного состояния.
class Character:
# defaults for every initialisation
def __init__(self, race='Ork', health=100):
self.race = race
self.health = health
def __setstate__(self, state):
# re-use __init__ for initialisation
self.__init__(**state)
Комментарии:
1. Так
pickle
будет ли вместо этого вызываться__new__
?2. @C.Nivs Он напрямую создает объект, также обходя класс
__new__
.3. @MisterMiyagi хм… почему подписи разные? Я думал, автор в «Эффективном Python» имел в виду, что мы вводим dict в виде аргументов со значениями по умолчанию, и поэтому мы можем передавать ** kwargs.
4. @MisterMiyagi «Это недопустимо только при использовании в качестве конструктора» что вы имеете в виду под этим? «Обратите внимание, что я не могу воспроизвести вашу ошибку» — вы имеете в виду, что на вашем компьютере мой код отменяет выбор старой версии, и у вас есть объект с недавно добавленным атрибутом на месте?
5. @MisterMiyagi «вы должны явно указать pickle использовать init , если хотите». как я могу это сделать? Можете ли вы исправить мой код, чтобы он полагался на этот способ отмены выбора? Большое спасибо за вашу помощь!