Как я могу сделать как можно более «совершенным» подклассом dict?
Конечная цель состоит в том, чтобы иметь простой диктант, в котором ключи строчные.
Если я переопределить __getitem__/ __setitem__, то получить / установить не работает. Как мне заставить их работать? Конечно, мне не нужно реализовывать их индивидуально?
Предотвращаю ли я травление от работы, и нужно ли его внедрять и
__setstate__т. Д.?
Нужно ли repr, update и __init__?
Должен ли я просто использовать mutablemapping(кажется, не следует использовать UserDict
или DictMixin)? Если да, то как? Документы не совсем поучительны.
Принятым ответом будет мой первый подход, но поскольку у него есть некоторые проблемы, и поскольку никто не рассматривал альтернативу, фактически подклассифицируя a dict, я собираюсь сделать это здесь.
Что не так с принятым ответом?
Это кажется довольно простой просьбой:
Как я могу сделать как можно более «совершенным» подклассом dict? Конечная цель состоит в том, чтобы иметь простой диктант, в котором ключи строчные.
Принятый ответ на самом деле не подкласс dict, и проверка для этого не проходит:
>>> isinstance(MyTransformedDict([('Test', 'test')]), dict)
False
В идеале любой код проверки типа должен проверять интерфейс, который мы ожидаем, или абстрактный базовый класс, но если наши объекты данных передаются в функции, которые проверяются, dict- и мы не можем «исправить» эти функции, этот код не удастся.
Другие придирки можно сделать:
- Принятый ответ также отсутствует Метод класса:
fromkeys.
Принятый ответ также имеет избыточность __dict__- поэтому занимает больше места в памяти:
>>> s.foo = 'bar'
>>> s.__dict__
{'foo': 'bar', 'store': {'test': 'test'}}
На самом деле подклассы dict
Мы можем повторно использовать методы dict через наследование. Все, что нам нужно сделать, это создать интерфейсный слой, который обеспечивает передачу ключей в dict в нижнем регистре, если они являются строками.
Если я переопределить __getitem__/ __setitem__, то получить / установить не работает. Как мне заставить их работать? Конечно, мне не нужно реализовывать их индивидуально?
Что ж, их реализация по отдельности является недостатком этого подхода и преимуществом использования MutableMapping(см. Принятый ответ), но на самом деле это не так уж много работы.
Во-первых, давайте выясним разницу между Python 2 и 3, создадим singleton ( _RaiseKeyError), чтобы убедиться, что мы знаем, действительно ли мы получаем аргумент dict.pop, и создадим функцию, обеспечивающую строчные ключи наших строковых ключей:
from itertools import chain
try: # Python 2
str_base = basestring
items = 'iteritems'
except NameError: # Python 3
str_base = str, bytes, bytearray
items = 'items'
_RaiseKeyError = object() # singleton for no-default behavior
def ensure_lower(maybe_str):
"""dict keys can be any hashable object - only call lower if str"""
return maybe_str.lower() if isinstance(maybe_str, str_base) else maybe_str
Теперь мы реализуем - я использую superс полными аргументами, чтобы этот код работал для Python 2 и 3:
class LowerDict(dict): # dicts take a mapping or iterable as their optional first argument
__slots__ = () # no __dict__ - that would be redundant
@staticmethod # because this doesn't make sense as a global function.
def _process_args(mapping=(), **kwargs):
if hasattr(mapping, items):
mapping = getattr(mapping, items)()
return ((ensure_lower(k), v) for k, v in chain(mapping, getattr(kwargs, items)()))
def __init__(self, mapping=(), **kwargs):
super(LowerDict, self).__init__(self._process_args(mapping, **kwargs))
def __getitem__(self, k):
return super(LowerDict, self).__getitem__(ensure_lower(k))
def __setitem__(self, k, v):
return super(LowerDict, self).__setitem__(ensure_lower(k), v)
def __delitem__(self, k):
return super(LowerDict, self).__delitem__(ensure_lower(k))
def get(self, k, default=None):
return super(LowerDict, self).get(ensure_lower(k), default)
def setdefault(self, k, default=None):
return super(LowerDict, self).setdefault(ensure_lower(k), default)
def pop(self, k, v=_RaiseKeyError):
if v is _RaiseKeyError:
return super(LowerDict, self).pop(ensure_lower(k))
return super(LowerDict, self).pop(ensure_lower(k), v)
def update(self, mapping=(), **kwargs):
super(LowerDict, self).update(self._process_args(mapping, **kwargs))
def __contains__(self, k):
return super(LowerDict, self).__contains__(ensure_lower(k))
def copy(self): # don't delegate w/ super - dict.copy() -> dict :(
return type(self)(self)
@classmethod
def fromkeys(cls, keys, v=None):
return super(LowerDict, cls).fromkeys((ensure_lower(k) for k in keys), v)
def __repr__(self):
return '{0}({1})'.format(type(self).__name__, super(LowerDict, self).__repr__())
Мы используем почти шаблонный подход для любого метода или специального метода , который ссылается на ключ, но в остальном, по наследству, мы получаем методы: len, clear, items, keys, popitem, и valuesбесплатно. В то время как это потребовало некоторой осторожной мысли, чтобы получить право, тривиально видеть, что это работает.
(Обратите внимание, что haskeyэто устарело в Python 2, удалено в Python 3.)
Вот немного использования:
>>> ld = LowerDict(dict(foo='bar'))
>>> ld['FOO']
'bar'
>>> ld['foo']
'bar'
>>> ld.pop('FoO')
'bar'
>>> ld.setdefault('Foo')
>>> ld
{'foo': None}
>>> ld.get('Bar')
>>> ld.setdefault('Bar')
>>> ld
{'bar': None, 'foo': None}
>>> ld.popitem()
('bar', None)
Предотвращаю ли я травление от работы, и нужно ли его внедрять и
__setstate__т. Д.?
маринование
А соленый подкласс dict просто отлично
>>> import pickle
>>> pickle.dumps(ld)
b'\x80\x03c__main__\nLowerDict\nq\x00)\x81q\x01X\x03\x00\x00\x00fooq\x02Ns.'
>>> pickle.loads(pickle.dumps(ld))
{'foo': None}
>>> type(pickle.loads(pickle.dumps(ld)))
<class '__main__.LowerDict'>
__repr__
Нужно ли repr, update и __init__?
Мы определили updateи __init__, но у вас есть красивые __repr__по умолчанию:
>>> ld # without __repr__ defined for the class, we get this
{'foo': None}
Тем не менее, полезно написать a __repr__для улучшения отладки вашего кода. Идеальный тест есть eval(repr(obj)) == obj. Если это легко сделать для вашего кода, я настоятельно рекомендую это:
>>> ld = LowerDict({})
>>> eval(repr(ld)) == ld
True
>>> ld = LowerDict(dict(a=1, b=2, c=3))
>>> eval(repr(ld)) == ld
True
Видите ли, это именно то, что нам нужно для воссоздания эквивалентного объекта - это то, что может отображаться в наших журналах или в следах:
>>> ld
LowerDict({'a': 1, 'c': 3, 'b': 2})
Вывод
Должен ли я просто использовать mutablemapping(кажется, не следует использовать UserDict
или DictMixin)? Если да, то как? Документы не совсем поучительны.
Да, это еще несколько строк кода, но они должны быть всеобъемлющими. Первым делом я хотел бы использовать принятый ответ, и если с ним возникнут проблемы, я бы посмотрел на свой ответ - так как он немного сложнее, и не было ABC, чтобы помочь мне правильно настроить интерфейс.
Преждевременная оптимизация усложняет поиск производительности.
MutableMappingпроще - так что он получает немедленное преимущество, при прочих равных условиях. Тем не менее, чтобы выложить все различия, давайте сравним и сопоставим.
Я должен добавить, что была попытка вставить подобный словарь в collectionsмодуль, но он был отклонен . Вы, вероятно, должны просто сделать это вместо этого:
my_dict[transform(key)]
Это должно быть гораздо проще отлаживать.
Сравнивать и противопоставлять
Есть 6 интерфейсных функций, реализованных с MutableMapping(что отсутствует fromkeys) и 11 с dictподклассом. Мне не нужно , чтобы реализовать __iter__или __len__, но вместо этого я должен реализовать get, setdefault, pop, update, copy, __contains__, и fromkeys- но это довольно тривиально, так как я могу использовать наследование для большинства из этих реализаций.
Он MutableMappingреализует некоторые вещи в Python, которые dictреализуются в C - поэтому я ожидал бы, что dictподкласс будет более производительным в некоторых случаях.
Мы получаем бесплатное __eq__в обоих подходах - оба из которых предполагают равенство только в том случае, если другой изречений является строчными - но опять же, я думаю, dictподкласс будет сравниваться быстрее.
Резюме:
- создание подклассов
MutableMappingпроще с меньшим количеством возможностей для ошибок, но медленнее, занимает больше памяти (см. избыточный dict) и терпит неудачуisinstance(x, dict)
- создание подклассов
dictбыстрее, использует меньше памяти и проходит isinstance(x, dict), но имеет большую сложность для реализации.
Что является более совершенным? Это зависит от вашего определения идеального.