Как «идеально» переопределить диктовку?

218

Как я могу сделать как можно более «совершенным» подклассом 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()
Пол Биггар
источник
Я думаю, что __keytransform __ () должен быть статическим. Хороший подход, хотя. (предваряющий @staticmethod)
Aiyion.Prime

Ответы:

229

Вы можете написать объект, который ведет себя dictочень легко с ABC (Абстрактные базовые классы) из collections.abcмодуля. Он даже сообщает вам, если вы пропустили метод, поэтому ниже приведена минимальная версия, которая закрывает ABC.

from collections.abc import MutableMapping


class TransformedDict(MutableMapping):
    """A dictionary that applies an arbitrary key-altering
       function before accessing the keys"""

    def __init__(self, *args, **kwargs):
        self.store = dict()
        self.update(dict(*args, **kwargs))  # use the free update to set keys

    def __getitem__(self, key):
        return self.store[self.__keytransform__(key)]

    def __setitem__(self, key, value):
        self.store[self.__keytransform__(key)] = value

    def __delitem__(self, key):
        del self.store[self.__keytransform__(key)]

    def __iter__(self):
        return iter(self.store)

    def __len__(self):
        return len(self.store)

    def __keytransform__(self, key):
        return key

Вы получаете несколько бесплатных методов от ABC:

class MyTransformedDict(TransformedDict):

    def __keytransform__(self, key):
        return key.lower()


s = MyTransformedDict([('Test', 'test')])

assert s.get('TEST') is s['test']   # free get
assert 'TeSt' in s                  # free __contains__
                                    # free setdefault, __eq__, and so on

import pickle
# works too since we just use a normal dict
assert pickle.loads(pickle.dumps(s)) == s

Я бы не подкласс dict(или другие встроенные) напрямую. Часто это не имеет смысла, потому что вы действительно хотите реализовать интерфейс adict . И это именно то, для чего азбука.

Йохен Ритцель
источник
46
Я бы предложил переименовать, __keytransform__()потому что оно нарушает руководство по стилю PEP 8, которое советует «Никогда не изобретайте такие имена; используйте их только как документированные» в конце раздела « Описательные стили именования ».
Мартино
1
Вопрос, однако, - не приведет ли реализация этого интерфейса к пользовательскому типу, как правило, к более медленным, похожим на диктовку операциям, которые используют встроенный тип?
twneale
2
Есть ли способ сделать это так, чтобы isinstance (_, dict) == True? Или вы просто используете Mutable Mapping для создания подкласса?
Энди Хейден
5
@AndyHayden: Вы должны написать if isinstance(t, collections.MutableMapping): print t, "can be used like a dict". Не проверяйте тип объекта, проверьте интерфейс.
Йохен Ритцель
2
@NeilG К сожалению, это включает JSONEncoder в стандартную библиотеку Python - github.com/python-git/python/blob/…
Энди Смит,
97

Как я могу сделать как можно более «совершенным» подклассом 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), но имеет большую сложность для реализации.

Что является более совершенным? Это зависит от вашего определения идеального.

Аарон Холл
источник
Как принятый ответ удалит лишний диктат?
Seanny123
1
Два способа, которые сразу приходят на ум, - это объявить атрибут store __slots__или, возможно, повторно использовать его __dict__как store, но это смешивает семантику, еще одну потенциальную точку критики.
Аарон Холл
1
Разве не было бы проще написать декоратор, который принимает метод и использует ваш метод ensure_lowerна первом аргументе (который всегда является ключом)? Тогда было бы то же количество переопределений, но все они были бы в форме __getitem__ = ensure_lower_decorator(super(LowerDict, self).__getitem__).
Graipher
1
Спасибо за это - получение предупреждений для pop и fromkeys о том, что они не соответствуют сигнатуре метода базового класса.
Mr_and_Mrs_D
1
@Mr_and_Mrs_D Я добавил реализацию copy- я думаю, что должен это сделать, нет? Я думаю, что он должен проверить интерфейс - например, объект DataFrame pandas не является экземпляром Mapping (при последней проверке), но у него есть элементы / объекты.
Аарон Холл
4

Мои требования были немного строже:

  • Мне пришлось сохранить информацию о регистре (строки - это пути к файлам, отображаемым пользователю, но это приложение для Windows, поэтому внутренне все операции должны быть без учета регистра)
  • Мне нужно было ключи , чтобы быть как можно меньше (это действительно сделать разницу в производительности памяти, отрубленную 110 мб из 370). Это означало, что кэширование строчной версии ключей не вариант.
  • Мне нужно, чтобы создание структур данных было как можно более быстрым (опять же, на этот раз разница в производительности, скорости). Я должен был пойти со встроенным

Моей первоначальной мыслью было заменить наш неуклюжий класс Path на нечувствительный к регистру подкласс юникода - но:

  • оказалось трудно получить это право - см .: регистр строк без учета регистра в python
  • Оказывается, что явная обработка ключей dict делает код многословным и грязным - и подвержен ошибкам (структуры передаются туда-сюда, и неясно, имеют ли они экземпляры CIStr в качестве ключей / элементов, легко забыть, плюс some_dict[CIstr(path)]уродлив)

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

class CIstr(unicode):
    """See https://stackoverflow.com/a/43122305/281545, especially for inlines"""
    __slots__ = () # does make a difference in memory performance

    #--Hash/Compare
    def __hash__(self):
        return hash(self.lower())
    def __eq__(self, other):
        if isinstance(other, CIstr):
            return self.lower() == other.lower()
        return NotImplemented
    def __ne__(self, other):
        if isinstance(other, CIstr):
            return self.lower() != other.lower()
        return NotImplemented
    def __lt__(self, other):
        if isinstance(other, CIstr):
            return self.lower() < other.lower()
        return NotImplemented
    def __ge__(self, other):
        if isinstance(other, CIstr):
            return self.lower() >= other.lower()
        return NotImplemented
    def __gt__(self, other):
        if isinstance(other, CIstr):
            return self.lower() > other.lower()
        return NotImplemented
    def __le__(self, other):
        if isinstance(other, CIstr):
            return self.lower() <= other.lower()
        return NotImplemented
    #--repr
    def __repr__(self):
        return '{0}({1})'.format(type(self).__name__,
                                 super(CIstr, self).__repr__())

def _ci_str(maybe_str):
    """dict keys can be any hashable object - only call CIstr if str"""
    return CIstr(maybe_str) if isinstance(maybe_str, basestring) else maybe_str

class LowerDict(dict):
    """Dictionary that transforms its keys to CIstr instances.
    Adapted from: https://stackoverflow.com/a/39375731/281545
    """
    __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, 'iteritems'):
            mapping = getattr(mapping, 'iteritems')()
        return ((_ci_str(k), v) for k, v in
                chain(mapping, getattr(kwargs, 'iteritems')()))
    def __init__(self, mapping=(), **kwargs):
        # dicts take a mapping or iterable as their optional first argument
        super(LowerDict, self).__init__(self._process_args(mapping, **kwargs))
    def __getitem__(self, k):
        return super(LowerDict, self).__getitem__(_ci_str(k))
    def __setitem__(self, k, v):
        return super(LowerDict, self).__setitem__(_ci_str(k), v)
    def __delitem__(self, k):
        return super(LowerDict, self).__delitem__(_ci_str(k))
    def copy(self): # don't delegate w/ super - dict.copy() -> dict :(
        return type(self)(self)
    def get(self, k, default=None):
        return super(LowerDict, self).get(_ci_str(k), default)
    def setdefault(self, k, default=None):
        return super(LowerDict, self).setdefault(_ci_str(k), default)
    __no_default = object()
    def pop(self, k, v=__no_default):
        if v is LowerDict.__no_default:
            # super will raise KeyError if no default and key does not exist
            return super(LowerDict, self).pop(_ci_str(k))
        return super(LowerDict, self).pop(_ci_str(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__(_ci_str(k))
    @classmethod
    def fromkeys(cls, keys, v=None):
        return super(LowerDict, cls).fromkeys((_ci_str(k) for k in keys), v)
    def __repr__(self):
        return '{0}({1})'.format(type(self).__name__,
                                 super(LowerDict, self).__repr__())

Неявное и явное все еще остается проблемой, но как только пыль уляжется, переименование атрибутов / переменных для начала с ci (и большой жирный комментарий к документу, объясняющий, что ci означает нечувствительный к регистру), я думаю, является идеальным решением, так как читатели кода должны полностью осознавать, что мы имеем дело с нечувствительными к регистру базовыми структурами данных. Надеюсь, это исправит некоторые трудно воспроизводимые ошибки, которые, я подозреваю, сводятся к чувствительности к регистру.

Комментарии / исправления приветствуются :)

Mr_and_Mrs_D
источник
CIstr __repr__должен использовать родительский класс, __repr__чтобы пройти тест eval (repr (obj)) == obj (я не думаю, что он делает это прямо сейчас) и не полагаться на него __str__.
Аарон Холл
Также проверьте total_orderingдекоратор класса - это исключит 4 метода из вашего подкласса Юникода. Но подкласс dict выглядит очень умно реализованным. : P
Аарон Холл
Спасибо @AaronHall - это вы реализовали это: P Re: полное упорядочение - я намеренно написал методы, встроенные в соответствии с рекомендациями Рэймонда Хеттингера, здесь: stackoverflow.com/a/43122305/281545 . Re: repr: Я помню, как читал комментарий (какой-то основной разработчик IIRC) о том, что не стоит и пытаться заставить repr пройти этот тест (это хлопотно) - лучше сосредоточиться на том, чтобы он был максимально информативным ( но не более)
Mr_and_Mrs_D
Я позволю вам ваши избыточные методы сравнения (вы должны отметить это в своем ответе), но CIstr.__repr__, в вашем случае, вы можете пройти тест repr с очень небольшими хлопотами, и это должно сделать отладку намного приятнее. Я также добавил бы __repr__для вашего dict. Я сделаю это в своем ответе, чтобы продемонстрировать.
Аарон Холл
@AaronHall: я добавил __slots__в CIstr - действительно влияет на производительность (CIstr не предназначен для подкласса или действительно используется вне LowerDict, должен быть статическим вложенным конечным классом). Все еще не уверен, как элегантно решить проблему repr (жало может содержать комбинацию 'и "кавычки)
Mr_and_Mrs_D
4

Все, что вам нужно сделать, это

class BatchCollection(dict):
    def __init__(self, *args, **kwargs):
        dict.__init__(*args, **kwargs)

ИЛИ

class BatchCollection(dict):
    def __init__(self, inpt={}):
        super(BatchCollection, self).__init__(inpt)

Пример использования для моего личного использования

### EXAMPLE
class BatchCollection(dict):
    def __init__(self, inpt={}):
        dict.__init__(*args, **kwargs)

    def __setitem__(self, key, item):
        if (isinstance(key, tuple) and len(key) == 2
                and isinstance(item, collections.Iterable)):
            # self.__dict__[key] = item
            super(BatchCollection, self).__setitem__(key, item)
        else:
            raise Exception(
                "Valid key should be a tuple (database_name, table_name) "
                "and value should be iterable")

Примечание : проверено только в python3

ravi404
источник
3

После опробования обоих из двух лучших предложений, я остановился на тенистом среднем маршруте для Python 2.7. Возможно 3 более разумно, но для меня:

class MyDict(MutableMapping):
   # ... the few __methods__ that mutablemapping requires
   # and then this monstrosity
   @property
   def __class__(self):
       return dict

который я действительно ненавижу, но, кажется, соответствует моим потребностям, а именно:

  • может переопределить **my_dict
    • если вы наследуете от dict, это обходит ваш код . попробуй это.
    • это делает № 2 неприемлемым для меня все время , так как это довольно часто встречается в коде Python
  • маскируется как isinstance(my_dict, dict)
    • исключает только MutableMapping, поэтому # 1 недостаточно
    • Я от всей души рекомендую № 1, если вам это не нужно, это просто и предсказуемо
  • полностью контролируемое поведение
    • поэтому я не могу наследовать от dict

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

def __am_i_me(self):
  return True

@classmethod
def __is_it_me(cls, other):
  try:
    return other.__am_i_me()
  except Exception:
    return False

Пока вам нужно только узнать себя внутренне, таким образом сложнее случайно вызвать вызов __am_i_meиз-за изменения имен в python (переименовывается _MyDict__am_i_meиз любого вызова вне этого класса). Чуть более приватно, чем _methods, как на практике, так и в культурном отношении.

Пока что у меня нет претензий, за исключением серьезно выглядящей __class__тенистости. Я был бы рад услышать о любых проблемах, с которыми сталкиваются другие, хотя я не до конца понимаю последствия. Но до сих пор у меня не было никаких проблем, и это позволило мне перенести большую часть кода среднего качества во многие места без каких-либо изменений.


В качестве доказательства: https://repl.it/repls/TraumaticToughCockatoo

В основном: скопируйте текущую опцию # 2 , добавьте print 'method_name'строки в каждый метод, а затем попробуйте это и посмотрите вывод:

d = LowerDict()  # prints "init", or whatever your print statement said
print '------'
splatted = dict(**d)  # note that there are no prints here

Вы увидите похожее поведение для других сценариев. Скажем, ваша фальшивка dict- это обертка вокруг какого-то другого типа данных, так что нет разумного способа сохранить данные в бэк-дикте; **your_dictбудет пустым, независимо от того, что делает любой другой метод.

Это работает правильно MutableMapping, но как только вы наследуете от dictнего становится неуправляемым.


Изменить: в качестве обновления, это работает без единой проблемы в течение почти двух лет, на нескольких сотнях тысяч (а может быть, пару миллионов) линий сложного, устаревшего Python. Так что я очень доволен этим :)

Редактировать 2: очевидно, я неправильно скопировал это или что-то давно. @classmethod __class__не работает для isinstanceпроверок - @property __class__работает: https://repl.it/repls/UnitedScientificSequence

Groxx
источник
Что именно вы подразумеваете под « **your_dictбудет пустым» (если вы подкласс от dict)? Я не видел никаких проблем с распаковкой dict ...
Matt P
Если вы фактически помещаете данные в родительский dict (как это делает LowerDict), это работает - вы получите эти данные, сохраненные в dict. Если вы этого не сделаете (скажем, вы хотите генерировать данные на лету, например {access_count: «стек трассировки доступа»}, который заполняется при каждом чтении), вы заметите, что **your_dictваш код не выполняется, поэтому не могу вывести ничего «особенного». Например, вы не можете считать «чтения», потому что он не выполняет ваш код для чтения. MutableMapping действительно работает для этого (используйте его, если можете!), Но он терпит неудачу, isinstance(..., dict)поэтому я не мог его использовать. Устаревшее программное обеспечение.
Groxx
Хорошо, теперь я понимаю, что вы имеете в виду. Я полагаю, я не ожидал выполнения кода с **your_dict, но я нахожу это очень интересным, что MutableMappingбудет делать это.
Мэтт Р
Да. Это необходимо для ряда вещей (например, я включал вызовы RPC в то, что раньше считывалось локальным диктом, и приходилось делать это по требованию Reasons ™), и, кажется, об этом мало кто знает, даже если **some_dictдовольно распространено. По крайней мере , это происходит очень часто в декораторах, поэтому если у вас есть какие - либо , вы сразу же риск , казалось бы, невозможным проступок , если не объяснить.
Groxx
Возможно, я что-то упускаю, но def __class__()уловка не работает ни с Python 2, ни с 3, по крайней мере для примера кода в вопросе Как зарегистрировать реализацию abc.MutableMapping в качестве подкласса dict? (изменено, чтобы иначе работать в двух версиях). Я хочу isinstance(SpreadSheet(), dict)вернуться True.
Мартино