Як "ідеально" відмінити дік?


218

Як я можу зробити максимально «досконалим» підклас дикту ? Кінцевою метою є створення простого диктанту, в якому клавіші є малі.

Здавалося б, має бути якийсь крихітний набір примітивів, які я можу перекрити, щоб зробити цю роботу, але згідно з усіма моїми дослідженнями та спробами, схоже, це не так:

  • Якщо я перекрию __getitem__/__setitem__ , то get/ setне працювати. Як змусити їх працювати? Невже мені не потрібно їх реалізовувати окремо?

  • Чи заважаю соління працювати, і мені потрібно застосовувати __setstate__тощо?

  • Чи потрібноreprupdate__init__ мені , і ?

  • Чи повинен я просто використовувати мутаційну карту (здається, не слід використовувати 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 s (Анотація базових класів) 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(або інші вбудовані) безпосередньо. Це часто не має сенсу, адже те, що ви насправді хочете зробити, це реалізувати інтерфейсdict . І саме для цього є ABC.


46
Я б запропонував перейменувати, __keytransform__()оскільки це порушує посібник зі стилю PEP 8, який радить "Ніколи не вигадуйте таких імен; використовуйте їх лише задокументованими" в кінці розділу "Описово: Назви стилі ".
martineau

1
Питання, однак - чи не реалізація цього інтерфейсу з визначеним користувачем типом, як правило, призводить до повільніших операцій, що нагадують дікти, ніж використання вбудованого типу?
twneale

2
Чи є спосіб це зробити так, щоб суть (_, dict) == Істинно? Або ви просто використовуєте Mutable Mapping для побудови та підкласу?
Енді Хайден

5
@AndyHayden: Ви повинні написати if isinstance(t, collections.MutableMapping): print t, "can be used like a dict". Не перевіряйте тип об’єкта, перевірте інтерфейс.
Jochen Ritzel

2
@NeilG Це, на жаль, включає JSONEncoder у стандартній бібліотеці python - github.com/python-git/python/blob/…
Енді Сміт

97

Як я можу зробити максимально «досконалим» підклас дикту?

Кінцевою метою є створення простого диктанту, в якому клавіші є малі.

  • Якщо я перекрию __getitem__/ __setitem__, то get / set не працює. Як змусити їх працювати? Невже мені не потрібно їх реалізовувати окремо?

  • Чи заважаю соління працювати, і мені потрібно застосовувати __setstate__тощо?

  • Чи потрібно мені перевидання, оновлення та __init__?

  • Чи повинен я просто використовувати mutablemapping(здається, один не повинен використовувати UserDict або DictMixin)? Якщо так, то як? Документи не зовсім освічуючі.

Прийнята відповідь була б моїм першим підходом, але оскільки в ній є деякі питання, і оскільки ніхто не звертався до альтернативи, насправді підкласу a dict, я збираюся це зробити тут.

Що не так у прийнятій відповіді?

Це здається мені досить простим запитом:

Як я можу зробити максимально «досконалим» підклас дикту? Кінцевою метою є створення простого диктанту, в якому клавіші є малі.

Прийнята відповідь насправді не є підкласом dict, і тест на це не вдається:

>>> isinstance(MyTransformedDict([('Test', 'test')]), dict)
False

В ідеалі будь-який код перевірки типу буде тестуванням інтерфейсу, який ми очікуємо, або абстрактного базового класу, але якщо наші об’єкти даних переходять у функції, які тестуються, dict- і ми не можемо "виправити" ці функції, цей код не вдасться.

Інші вигадки, які можна зробити:

  • Прийнятий відповідь також відсутній Метод класу: fromkeys.
  • Прийнята відповідь також є зайвою __dict__- тому займаючи більше місця в пам'яті:

    >>> s.foo = 'bar'
    >>> s.__dict__
    {'foo': 'bar', 'store': {'test': 'test'}}
    

Фактично підкласифікація dict

Ми можемо повторно використовувати методи викладання шляхом успадкування. Все, що нам потрібно зробити, - це створити інтерфейсний шар, який гарантує, що ключі передаються в дикт у малій формі, якщо вони є рядками.

Якщо я перекрию __getitem__/ __setitem__, то get / set не працює. Як змусити їх працювати? Невже мені не потрібно їх реалізовувати окремо?

Що ж, їх реалізація окремо є недоліком цього підходу та перевагою використання MutableMapping(див. Прийняту відповідь), але насправді це не так вже й багато роботи.

Спочатку давайте визначимо різницю між Python 2 та 3, створимо синглтон ( _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__тощо?

маринування

І соління підкласу Дікт просто чудово:

>>> 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__

Чи потрібно мені перевидання, оновлення та __init__?

Ми визначили updateі __init__, але у вас є гарний __repr__за замовчуванням:

>>> ld # without __repr__ defined for the class, we get this
{'foo': None}

Однак добре написати __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)? Якщо так, то як? Документи не зовсім освічуючі.

Так, це ще кілька рядків коду, але вони мають бути вичерпними. Першим моїм схильністю було б використовувати прийняту відповідь, і якщо з цим виникнуть проблеми, я б тоді подивився на свою відповідь - оскільки це трохи складніше, і немає АВС, яка допомогла б мені правильно встановити інтерфейс.

Передчасна оптимізація потребує більшої складності у пошуку ефективності. 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простіша з меншими можливостями для помилок, але повільніше, займає більше пам’яті (див. зайвий диктант) і не дає змогиisinstance(x, dict)
  • підкласифікація dictшвидша, використовує менше пам'яті та проходить isinstance(x, dict), але вона має більшу складність в реалізації.

Що досконаліше? Це залежить від вашого визначення ідеального.


Як прийнята відповідь видалить надлишковий вислів?
Seanny123

1
Два способи, які негайно приходять в голову, - це або оголосити атрибут store в, __slots__або, можливо, повторно використовувати його __dict__як магазин, але це змішує семантику, ще один потенційний момент критики.
Аарон Холл

1
Чи не було б простіше написати декоратора, який використовує метод і використовує ваш ensure_lowerперший arguemtn (який завжди є ключовим)? Тоді це була б однакова кількість змін, але всі вони мали б форму __getitem__ = ensure_lower_decorator(super(LowerDict, self).__getitem__).
Грайфер

1
Дякую за це - отримання попереджень для поп-енд-ключів, що вони не відповідають підпису методу базового класу.
Mr_and_Mrs_D

1
@Mr_and_Mrs_D Я додав реалізацію copy- я думаю, що це слід зробити, ні? Я думаю, що він повинен перевірити інтерфейс - наприклад, об'єкт панди DataFrame - це не екземпляр Mapping (під час останньої перевірки), але у нього є елементи / iteritems.
Аарон Холл

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 (і великий жирний коментар doc, що пояснює, що ci означає невідчутне до випадку), я думаю, що це ідеальне рішення - оскільки читачі коду повинні бути повністю усвідомленим, що ми маємо справу з нечутливими до обліку структурами даних. Це, сподіваємось, виправить деякі важкі для відтворення помилки, які, як я підозрюю, звелися до чутливості випадку.

Коментарі / виправлення вітаємо :)


CIstr's __repr__повинні використовувати батьківський клас, __repr__щоб пройти тест eval (repr (obj)) == obj (я не думаю, що це робить зараз) і не покладатися на нього __str__.
Аарон Холл

Також перегляньте total_orderingдекоратор класу - це дозволить усунути 4 методи з вашого підкласу unicode. Але підклас дикту виглядає дуже хитро реалізованим. : P
Аарон Холл

Дякую @AaronHall - це ти, хто реалізував це: P Re: загальне замовлення - я навмисно написав методи, накреслені так, як радив Реймонд Хеттінгер тут: stackoverflow.com/a/43122305/281545 . Re: repr: я пам'ятаю, як читав коментар (деяким основним розробником IIRC), що добре, не дуже варто спробувати намагатися зробити repr, щоб пройти тест (це клопоти) - краще зосередитись на тому, щоб він був максимально інформативним ( але не більше)
Mr_and_Mrs_D

Я дозволю вам ваші зайві методи порівняння (ви повинні зауважити про це у своїй відповіді), але CIstr.__repr__, у вашому випадку, ви зможете пройти тест повторної роботи з дуже невеликим клопотом, і це повинно зробити налагодження набагато приємніше. Я також додав би __repr__ваш твір. Я зроблю це у своїй відповіді, щоб продемонструвати.
Аарон Холл

@AaronHall: Я додав __slots__у CIstr - чи має значення зміна продуктивності (CIstr не призначений для підкласу або дійсно використовується поза LowerDict, повинен бути статичним вкладеним кінцевим класом). Ще не впевнений, як вишукано вирішити проблему перевидання (жало може містити комбінацію 'та "цитати)
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


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його, це стає неконтрольованим.


Редагувати: як оновлення, це працює без жодного випуску вже майже два роки, на кількох сотнях тисяч (е, може бути, пару мільйонів) рядків складного, застарілого пітона. Тож я дуже задоволений цим :)

Редагувати 2: мабуть, я давно неправильно скопіював це чи щось. @classmethod __class__не працює для isinstanceперевірок - @property __class__робить: https://repl.it/repls/UnitedScientistSequence


Що саме ви маєте на увазі під " **your_dictкладеться порожнім" (якщо ви підклас від dict)? Я не бачив жодних проблем із розпакуванням
Мет П

Якщо ви фактично помістите дані в батьківський dict (як це робить LowerDict), він працює - ви отримаєте ці збережені дані. Якщо ви цього не зробите (скажіть, ви хотіли генерувати дані під час руху, як-от {access_count: "стек сліду доступу"}, який заповнюється щоразу, коли він читається), ви помітите, що **your_dictваш код не виконує, так що не може вивести нічого "особливого". Наприклад, ви не можете рахувати "читання", оскільки він не виконує ваш код підрахунку читання. MutableMapping робить роботу для цього (використовуйте його , якщо ви можете!), Але він не isinstance(..., dict)так що я не міг використовувати його. yay застаріле програмне забезпечення.
Groxx

Гаразд, я бачу, що ти маєш на увазі зараз. Я вважаю, що я не очікував виконання коду **your_dict, але мені здається, що це дуже цікаво MutableMapping.
Мет П

Так. Це потрібно для ряду речей (наприклад, я переливаюсь дзвінками RPC у те, що раніше було прочитане місцевим малюнком, і потрібно було робити це на вимогу Reasons ™), і, здається, це мало хто знає про це, навіть тхо **some_dictє досить поширеним. Принаймні, це дуже часто трапляється у декораторів, тож якщо у вас є такі , вам негайно загрожує начебто неможлива поведінка, якщо ви цього не зробите.
Groxx

Можливо, мені щось не вистачає, але цей def __class__()трюк, здається, не працює ні з Python 2, ні з 3, принаймні для прикладу коду у питанні Як зареєструвати реалізацію abc.MutableMapping як підклас dict? (модифіковано для роботи в двох версіях). Я хочу isinstance(SpreadSheet(), dict)повернутися True.
мартіно
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.