2017-02-16 4 views
1

Я протянул dict в простом способе прямого доступа это значение с d.key нотации вместо d['key']:Как создать класс, который __getattr__ правильно подбирается?

class ddict(dict): 

    def __getattr__(self, item): 
     return self[item] 

    def __setattr__(self, key, value): 
     self[key] = value 

Теперь, когда я пытаюсь замариновать его, он будет вызывать __getattr__ найти __getstate__, который не является ни настоящее, ни необходимо. То же самое будет происходить на unpickling с __setstate__:

>>> import pickle 
>>> class ddict(dict): 
...  def __getattr__(self, item): 
...   return self[item] 
...  def __setattr__(self, key, value): 
...   self[key] = value 
... 
>>> pickle.dumps(ddict()) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 3, in __getattr__ 
KeyError: '__getstate__' 

Как я должен изменить класс ddict для того, чтобы быть правильно pickable?

ответ

3

Проблема не pickle но ваш метод __getattr__ нарушает ожидаемый контракт поднятия KeyError исключения. Вам необходимо исправить свой метод __getattr__ поднять AttributeError исключения вместо:

def __getattr__(self, item): 
    try: 
     return self[item] 
    except KeyError: 
     raise AttributeError(item) 

Теперь pickle дается ожидаемый сигнал для отсутствующего __getstate__ настройки крючка.

От object.__getattr__ documentation:

Этот метод должен возвращать (вычисленное) значение атрибута или поднять AttributeError исключение.

(смелый акцент мой).

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

def __getattr__(self, item): 
    if isinstance(item, str) and item[:2] == item[-2:] == '__': 
     # skip non-existing dunder method lookups 
     raise AttributeError(item) 
    return self[item] 

Обратите внимание, что вы, вероятно, хотите укажите ddict() подкласс an empty __slots__ tuple; вам не нужно дополнительное сопоставление атрибутов __dict__ для ваших экземпляров, поскольку вместо этого вы переадресовываете атрибуты парам ключ-значение. Это экономит вам кусок памяти на один экземпляр.

Демо:

>>> import pickle 
>>> class ddict(dict): 
...  __slots__ =() 
...  def __getattr__(self, item): 
...   try: 
...    return self[item] 
...   except KeyError: 
...    raise AttributeError(item) 
...  def __setattr__(self, key, value): 
...   self[key] = value 
... 
>>> pickle.dumps(ddict()) 
b'\x80\x03c__main__\nddict\nq\x00)\x81q\x01.' 
>>> type(pickle.loads(pickle.dumps(ddict()))) 
<class '__main__.ddict'> 
>>> d = ddict() 
>>> d.foo = 'bar' 
>>> d.foo 
'bar' 
>>> pickle.loads(pickle.dumps(d)) 
{'foo': 'bar'} 

Это pickle тесты для __getstate__ метода на экземпляре, а не на классе, как is the norm for special methods, это обсуждение на другой день.

-3

Прежде всего, я думаю, вам может понадобиться различать атрибут экземпляра и атрибут класса. В Python официального документа глава 11.1.4 о травлении, он говорит:

экземпляров таких классов, ДИКТ или результат вызова GetState() является пригодно для консервирования (смотрите раздел Травильной протокол для более подробной информации).

Таким образом, сообщение об ошибке, которое вы получаете, - это когда вы пытаетесь рассортировать экземпляр класса, но не сам класс - на самом деле, определение вашего класса будет просто разборчиво.

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

In [1]: import pickle 

In [2]: class ddict(dict): 
    ...: 
    ...:  def __getattr__(self, item): 
    ...:   super.__getattr__(self, item) 
    ...:   return self[item] 
    ...: 
    ...:  def __setattr__(self, key, value): 
    ...:   super.__setattr__(self, key, value) 
    ...:   self[key] = value 
    ...: 

In [3]: d = ddict() 

In [4]: d.name = "Sam" 

In [5]: d 
Out[5]: {'name': 'Sam'} 
In [6]: pickle.dumps(d) 
Out[6]: b'\x80\x03c__main__\nddict\nq\x00)\x81q\x01X\x04\x00\x00\x00nameq\x02X\x03\x00\x00\x00Samq\x03s}q\x04h\x02h\x03sb.' 
+2

Извините, но все в этом ответе неверно. Pickle пытается получить доступ к методу '__getstate__' в экземпляре, после чего проконсультируется с методом' __getattr__'. Это пытается получить доступ к ключу с помощью этого имени в сопоставлении и не выполняется с помощью KeyError; это не является нормальным исключением для разборки доступа к атрибуту. –

+0

Далее, на 'dict' нет метода' __getattr__', поэтому вызов 'super()' не будет выполнен. Этот метод является необязательным, он используется только как резервный крючок для отсутствия атрибутов. –

+0

Далее, вся точка перехвата '__setattr__' должна переопределить процесс назначения нормального атрибута и вместо этого перенаправить на назначение словаря. Вызов версии 'super()' поражает эту цель. –

Смежные вопросы