Подтвердить что ты не робот

Солить все атрибуты, кроме одного

Каков наилучший способ написать метод __getstate__, который солит почти все атрибуты объекта, но исключает несколько?

У меня есть объект со многими свойствами, включая тот, который ссылается на метод instance. instancemethod не разборчивы, поэтому я получаю сообщение об ошибке при попытке рассортировать этот объект:

class Foo(object):
    def __init__(self):
        self.a = 'spam'
        self.b = 'eggs'
        self.c = 42
        self.fn = self.my_func
    def my_func(self):
        print 'My hovercraft is full of eels'

import pickle
pickle.dumps(Foo())              # throws a "can't pickle instancemethod objects" TypeError

Этот метод __getstate__ исправляет это, но затем мне нужно вручную включить все свойства, которые я хочу сериализовать:

def __getstate__(self):
    return { 'a': self.a, 'b': self.b, 'c': self.c }

Это не очень масштабируемое или поддерживаемое, если у меня есть объект со многими атрибутами или который изменяется часто.

Единственная альтернатива, о которой я могу думать, это некоторая вспомогательная функция, которая выполняет итерации через свойства объекта и добавляет их (или нет) в словарь, основываясь на типе.

4b9b3361

Ответ 1

Единственная альтернатива, о которой я могу думать, это некоторая вспомогательная функция, которая выполняет итерации через свойства объекта и добавляет их (или нет) в словарь, основываясь на типе.

Да, я думаю, что в значительной степени то, что вам осталось, если вы хотите достаточно "магии", чтобы позволить себе быть ленивым (и/или допускать динамически добавленные атрибуты). Имейте в виду, что "pickle не может справиться с этим" - не единственная причина, по которой вы не хотите включать что-то в маринованное состояние.

Но это не так сложно, как вы, кажется, думаете, предполагая, что у вас есть код для "должен ли я это делать?" логика:

def __getstate__(self):
  return dict((k, v) for (k, v) in self.__dict__.iteritems() if should_pickle(v))

Ответ 2

Используя is_instance_method из более раннего ответа:

def __getstate__(self):
    return dict((k, v) for k, v in self.__dict__.iteritems()
                       if not is_instance_method(getattr(self, k)))

Хотя операция is_instance_method также может выполняться менее "магически", используя метод известного экземпляра, скажем my_func и принимая его тип.

def __getstate__(self):
    instancemethod = type(self.my_func)
    return dict((k, v) for k, v in self.__dict__.iteritems()
                       if not isinstance(getattr(self, k), instancemethod))

Ответ 3

Вы всегда можете просто удалить плохие элементы:

def __getstate__(self):
    state = self.__dict__
    del state[...]
    return state

Ответ 4

Я разрезал бы корень вашей проблемы и попытался сначала сериализовать так называемые "не-pickleable" элементы. Для этого я бы использовал dill, который может сериализовать почти что угодно в python. У Dill также есть несколько хороших инструментов, которые помогут вам понять, что заставляет ваш травление терпеть неудачу, когда ваш код не работает.

>>> import dill
>>> dill.loads(dill.dumps(your_bad_object))
>>> ...
>>> # if you get a pickling error, use dill tools to figure out a workaround
>>> dill.detect.badobjects(your_bad_object, depth=0)
>>> dill.detect.badobjects(your_bad_object, depth=1)
>>> ...

Если вы абсолютно этого хотели, вы могли бы использовать dill badobjects (или одну из других функций обнаружения) для рекурсивного погружения в цепочку ссылок объектов и выталкивать нераспадаемые объекты вместо того, чтобы называть их на каждой глубине, как указано выше.

Ответ 5

__slots__ решение

Если вы используете слоты, вы можете избежать повторения элементов для исключения:

class C(object):
    _pickle_slots = ['i']
    __slots__ = _pickle_slots + ['j']
    def __init__(self, i, j):
        self.i = i
        self.j = j
    def __getstate__(self):
        return (None, {k:getattr(self, k) for k in C._pickle_slots })

o = pickle.loads(pickle.dumps(C(1, 2), -1))

# i is there
assert o.i == 1

# j was excluded
try:
    o.j
except:
    pass
else:
    raise

Протестировано в Python 2.7.6.