Как я могу сделать как можно более «совершенным» подклассом dict ? Конечная цель состоит в том, чтобы иметь простой диктант, в котором ключи строчные.
Казалось бы, должен быть какой-то крошечный набор примитивов, которые я могу переопределить, чтобы сделать эту работу, но, судя по всем моим исследованиям и попыткам, кажется, что это не так:
Если я переопределить
__getitem__
/__setitem__
, тоget
/set
не работает. Как я могу заставить их работать? Конечно, мне не нужно реализовывать их индивидуально?Предотвращаю ли я травление от работы, и нужно ли его внедрять и
__setstate__
т. Д.?Должен ли я нужен
repr
,update
и__init__
?Должен ли я просто использовать mutablemapping (кажется, не следует использовать
UserDict
илиDictMixin
)? Если да, то как? Документы не совсем поучительны.
Вот мой первый пример, get()
он не работает и, несомненно, есть много других мелких проблем:
class arbitrary_dict(dict):
"""A dictionary that applies an arbitrary key-altering function
before accessing the keys."""
def __keytransform__(self, key):
return key
# Overridden methods. List from
# /programming/2390827/how-to-properly-subclass-dict
def __init__(self, *args, **kwargs):
self.update(*args, **kwargs)
# Note: I'm using dict directly, since super(dict, self) doesn't work.
# I'm not sure why, perhaps dict is not a new-style class.
def __getitem__(self, key):
return dict.__getitem__(self, self.__keytransform__(key))
def __setitem__(self, key, value):
return dict.__setitem__(self, self.__keytransform__(key), value)
def __delitem__(self, key):
return dict.__delitem__(self, self.__keytransform__(key))
def __contains__(self, key):
return dict.__contains__(self, self.__keytransform__(key))
class lcdict(arbitrary_dict):
def __keytransform__(self, key):
return str(key).lower()
источник
Ответы:
Вы можете написать объект, который ведет себя
dict
очень легко с ABC (Абстрактные базовые классы) изcollections.abc
модуля. Он даже сообщает вам, если вы пропустили метод, поэтому ниже приведена минимальная версия, которая закрывает ABC.Вы получаете несколько бесплатных методов от ABC:
Я бы не подкласс
dict
(или другие встроенные) напрямую. Часто это не имеет смысла, потому что вы действительно хотите реализовать интерфейс adict
. И это именно то, для чего азбука.источник
__keytransform__()
потому что оно нарушает руководство по стилю PEP 8, которое советует «Никогда не изобретайте такие имена; используйте их только как документированные» в конце раздела « Описательные стили именования ».if isinstance(t, collections.MutableMapping): print t, "can be used like a dict"
. Не проверяйте тип объекта, проверьте интерфейс.Принятым ответом будет мой первый подход, но поскольку у него есть некоторые проблемы, и поскольку никто не рассматривал альтернативу, фактически подклассифицируя a
dict
, я собираюсь сделать это здесь.Что не так с принятым ответом?
Это кажется довольно простой просьбой:
Принятый ответ на самом деле не подкласс
dict
, и проверка для этого не проходит:В идеале любой код проверки типа должен проверять интерфейс, который мы ожидаем, или абстрактный базовый класс, но если наши объекты данных передаются в функции, которые проверяются,
dict
- и мы не можем «исправить» эти функции, этот код не удастся.Другие придирки можно сделать:
fromkeys
.Принятый ответ также имеет избыточность
__dict__
- поэтому занимает больше места в памяти:На самом деле подклассы
dict
Мы можем повторно использовать методы dict через наследование. Все, что нам нужно сделать, это создать интерфейсный слой, который обеспечивает передачу ключей в dict в нижнем регистре, если они являются строками.
Что ж, их реализация по отдельности является недостатком этого подхода и преимуществом использования
MutableMapping
(см. Принятый ответ), но на самом деле это не так уж много работы.Во-первых, давайте выясним разницу между Python 2 и 3, создадим singleton (
_RaiseKeyError
), чтобы убедиться, что мы знаем, действительно ли мы получаем аргументdict.pop
, и создадим функцию, обеспечивающую строчные ключи наших строковых ключей:Теперь мы реализуем - я использую
super
с полными аргументами, чтобы этот код работал для Python 2 и 3:Мы используем почти шаблонный подход для любого метода или специального метода , который ссылается на ключ, но в остальном, по наследству, мы получаем методы:
len
,clear
,items
,keys
,popitem
, иvalues
бесплатно. В то время как это потребовало некоторой осторожной мысли, чтобы получить право, тривиально видеть, что это работает.(Обратите внимание, что
haskey
это устарело в Python 2, удалено в Python 3.)Вот немного использования:
маринование
А соленый подкласс dict просто отлично
__repr__
Мы определили
update
и__init__
, но у вас есть красивые__repr__
по умолчанию:Тем не менее, полезно написать a
__repr__
для улучшения отладки вашего кода. Идеальный тест естьeval(repr(obj)) == obj
. Если это легко сделать для вашего кода, я настоятельно рекомендую это:Видите ли, это именно то, что нам нужно для воссоздания эквивалентного объекта - это то, что может отображаться в наших журналах или в следах:
Вывод
Да, это еще несколько строк кода, но они должны быть всеобъемлющими. Первым делом я хотел бы использовать принятый ответ, и если с ним возникнут проблемы, я бы посмотрел на свой ответ - так как он немного сложнее, и не было ABC, чтобы помочь мне правильно настроить интерфейс.
Преждевременная оптимизация усложняет поиск производительности.
MutableMapping
проще - так что он получает немедленное преимущество, при прочих равных условиях. Тем не менее, чтобы выложить все различия, давайте сравним и сопоставим.Я должен добавить, что была попытка вставить подобный словарь в
collections
модуль, но он был отклонен . Вы, вероятно, должны просто сделать это вместо этого:Это должно быть гораздо проще отлаживать.
Сравнивать и противопоставлять
Есть 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)
, но имеет большую сложность для реализации.Что является более совершенным? Это зависит от вашего определения идеального.
источник
__slots__
или, возможно, повторно использовать его__dict__
как store, но это смешивает семантику, еще одну потенциальную точку критики.ensure_lower
на первом аргументе (который всегда является ключом)? Тогда было бы то же количество переопределений, но все они были бы в форме__getitem__ = ensure_lower_decorator(super(LowerDict, self).__getitem__)
.copy
- я думаю, что должен это сделать, нет? Я думаю, что он должен проверить интерфейс - например, объект DataFrame pandas не является экземпляром Mapping (при последней проверке), но у него есть элементы / объекты.Мои требования были немного строже:
Моей первоначальной мыслью было заменить наш неуклюжий класс Path на нечувствительный к регистру подкласс юникода - но:
some_dict[CIstr(path)]
уродлив)Так что мне наконец-то пришлось записать этот нечувствительный к регистру диктат. Благодаря коду @AaronHall это было сделано в 10 раз проще.
Неявное и явное все еще остается проблемой, но как только пыль уляжется, переименование атрибутов / переменных для начала с ci (и большой жирный комментарий к документу, объясняющий, что ci означает нечувствительный к регистру), я думаю, является идеальным решением, так как читатели кода должны полностью осознавать, что мы имеем дело с нечувствительными к регистру базовыми структурами данных. Надеюсь, это исправит некоторые трудно воспроизводимые ошибки, которые, я подозреваю, сводятся к чувствительности к регистру.
Комментарии / исправления приветствуются :)
источник
__repr__
должен использовать родительский класс,__repr__
чтобы пройти тест eval (repr (obj)) == obj (я не думаю, что он делает это прямо сейчас) и не полагаться на него__str__
.total_ordering
декоратор класса - это исключит 4 метода из вашего подкласса Юникода. Но подкласс dict выглядит очень умно реализованным. : PCIstr.__repr__
, в вашем случае, вы можете пройти тест repr с очень небольшими хлопотами, и это должно сделать отладку намного приятнее. Я также добавил бы__repr__
для вашего dict. Я сделаю это в своем ответе, чтобы продемонстрировать.__slots__
в CIstr - действительно влияет на производительность (CIstr не предназначен для подкласса или действительно используется вне LowerDict, должен быть статическим вложенным конечным классом). Все еще не уверен, как элегантно решить проблему repr (жало может содержать комбинацию'
и"
кавычки)Все, что вам нужно сделать, это
ИЛИ
Пример использования для моего личного использования
Примечание : проверено только в python3
источник
После опробования обоих из двух лучших предложений, я остановился на тенистом среднем маршруте для Python 2.7. Возможно 3 более разумно, но для меня:
который я действительно ненавижу, но, кажется, соответствует моим потребностям, а именно:
**my_dict
dict
, это обходит ваш код . попробуй это.isinstance(my_dict, dict)
dict
Если вам нужно отличить себя от других, лично я использую что-то вроде этого (хотя я бы порекомендовал лучшие имена):
Пока вам нужно только узнать себя внутренне, таким образом сложнее случайно вызвать вызов
__am_i_me
из-за изменения имен в python (переименовывается_MyDict__am_i_me
из любого вызова вне этого класса). Чуть более приватно, чем_method
s, как на практике, так и в культурном отношении.Пока что у меня нет претензий, за исключением серьезно выглядящей
__class__
тенистости. Я был бы рад услышать о любых проблемах, с которыми сталкиваются другие, хотя я не до конца понимаю последствия. Но до сих пор у меня не было никаких проблем, и это позволило мне перенести большую часть кода среднего качества во многие места без каких-либо изменений.В качестве доказательства: https://repl.it/repls/TraumaticToughCockatoo
В основном: скопируйте текущую опцию # 2 , добавьте
print 'method_name'
строки в каждый метод, а затем попробуйте это и посмотрите вывод:Вы увидите похожее поведение для других сценариев. Скажем, ваша фальшивка
dict
- это обертка вокруг какого-то другого типа данных, так что нет разумного способа сохранить данные в бэк-дикте;**your_dict
будет пустым, независимо от того, что делает любой другой метод.Это работает правильно
MutableMapping
, но как только вы наследуете отdict
него становится неуправляемым.Изменить: в качестве обновления, это работает без единой проблемы в течение почти двух лет, на нескольких сотнях тысяч (а может быть, пару миллионов) линий сложного, устаревшего Python. Так что я очень доволен этим :)
Редактировать 2: очевидно, я неправильно скопировал это или что-то давно.
@classmethod __class__
не работает дляisinstance
проверок -@property __class__
работает: https://repl.it/repls/UnitedScientificSequenceисточник
**your_dict
будет пустым» (если вы подкласс отdict
)? Я не видел никаких проблем с распаковкой dict ...**your_dict
ваш код не выполняется, поэтому не могу вывести ничего «особенного». Например, вы не можете считать «чтения», потому что он не выполняет ваш код для чтения. MutableMapping действительно работает для этого (используйте его, если можете!), Но он терпит неудачу,isinstance(..., dict)
поэтому я не мог его использовать. Устаревшее программное обеспечение.**your_dict
, но я нахожу это очень интересным, чтоMutableMapping
будет делать это.**some_dict
довольно распространено. По крайней мере , это происходит очень часто в декораторах, поэтому если у вас есть какие - либо , вы сразу же риск , казалось бы, невозможным проступок , если не объяснить.def __class__()
уловка не работает ни с Python 2, ни с 3, по крайней мере для примера кода в вопросе Как зарегистрировать реализацию abc.MutableMapping в качестве подкласса dict? (изменено, чтобы иначе работать в двух версиях). Я хочуisinstance(SpreadSheet(), dict)
вернутьсяTrue
.