Подтвердить что ты не робот

Как сделать словарь python, который возвращает ключ для ключей, отсутствующих в словаре, вместо того, чтобы поднимать KeyError?

Я хочу создать словарь python, который возвращает мне значение ключа для ключей, отсутствующих в словаре.

Пример использования:

dic = smart_dict()
dic['a'] = 'one a'
print(dic['a'])
# >>> one a
print(dic['b'])
# >>> b
4b9b3361

Ответ 1

dict имеют __missing__ для этого:

class smart_dict(dict):
    def __missing__(self, key):
        return key

Ответ 2

Почему бы вам просто не использовать

dic.get('b', 'b')

Конечно, вы можете подклассифицировать dict, как указывают другие, но мне удобно напоминать себе каждый раз, что get может иметь значение по умолчанию!

Если вы хотите перейти в defaultdict, попробуйте следующее:

dic = defaultdict()
dic.__missing__ = lambda key: key
dic['b'] # should set dic['b'] to 'b' and return 'b'

кроме... well: AttributeError: ^collections.defaultdict^object attribute '__missing__' is read-only, поэтому вам придется подкласс:

from collections import defaultdict
class KeyDict(defaultdict):
    def __missing__(self, key):
        return key

d = KeyDict()
print d['b'] #prints 'b'
print d.keys() #prints []

Ответ 3

Первый ответчик упомянул defaultdict, но вы можете определить __missing__ для любого подкласса dict:

>>> class Dict(dict):
        def __missing__(self, key):
            return key


>>> d = Dict(a=1, b=2)
>>> d['a']
1
>>> d['z']
'z'

Кроме того, мне нравится второй подход респондента:

>>> d = dict(a=1, b=2)
>>> d.get('z', 'z')
'z'

Ответ 4

Поздравляем. Вы тоже обнаружили бесполезность стандартный тип collections.defaultdict. Если эта отвратительная скрытая куча запах кода оскорбляет ваши тонкие чувства так же, как и мои, это ваша удача День StackOverflow.

Благодаря запрещенному чуду 3-параметр вариант type() встроенный, создающий не-бесполезный тип словаря по умолчанию - это весело и выгодно.

Что не так с dict.__ missing __()?

Абсолютно ничего, предполагая, что вам нравится избыточный шаблон и потрясающая глупость collections.defaultdict, которая должна вести себя так, как ожидалось, но на самом деле этого не делает. Чтобы быть справедливым, Йохен Ritzel принят решение подкласса dict и реализуя необязательный __missing__() метод является фантастическим обходной путь для небольших случаев использования, требующий только одного словаря по умолчанию.

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

Или, по крайней мере, приятно. Потому что почему бы не исправить то, что сломалось?

Знакомство с DefaultDict

В менее чем десяти строках чистого Python (исключая докстры, комментарии и whitespace), мы теперь определяем тип DefaultDict, инициализированный с помощью определяемого пользователем вызываемый, генерирующий значения по умолчанию для отсутствующих ключей. В то время как вызываемый к стандартному типу collections.defaultdict бесполезно не принимает параметры, вызываемые, передаваемые нашему типу DefaultDict, с готовностью принимают после двух параметров:

  • Текущий экземпляр этого словаря.
  • Текущий отсутствующий ключ для генерации значения по умолчанию для.

Учитывая этот тип, решая sorin вопрос сводится к одной строке Python:

>>> dic = DefaultDict(lambda self, missing_key: missing_key)
>>> dic['a'] = 'one a'
>>> print(dic['a'])
one a
>>> print(dic['b'])
b

здравомыслие. Наконец.

Код или это не произошло

def DefaultDict(keygen):
    '''
    Sane **default dictionary** (i.e., dictionary implicitly mapping a missing
    key to the value returned by a caller-defined callable passed both this
    dictionary and that key).

    The standard :class:`collections.defaultdict` class is sadly insane,
    requiring the caller-defined callable accept *no* arguments. This
    non-standard alternative requires this callable accept two arguments:

    #. The current instance of this dictionary.
    #. The current missing key to generate a default value for.

    Parameters
    ----------
    keygen : CallableTypes
        Callable (e.g., function, lambda, method) called to generate the default
        value for a "missing" (i.e., undefined) key on the first attempt to
        access that key, passed first this dictionary and then this key and
        returning this value. This callable should have a signature resembling:
        ``def keygen(self: DefaultDict, missing_key: object) -> object``.
        Equivalently, this callable should have the exact same signature as that
        of the optional :meth:`dict.__missing__` method.

    Returns
    ----------
    MappingType
        Empty default dictionary creating missing keys via this callable.
    '''

    # Global variable modified below.
    global _DEFAULT_DICT_ID

    # Unique classname suffixed by this identifier.
    default_dict_class_name = 'DefaultDict' + str(_DEFAULT_DICT_ID)

    # Increment this identifier to preserve uniqueness.
    _DEFAULT_DICT_ID += 1

    # Dynamically generated default dictionary class specific to this callable.
    default_dict_class = type(
        default_dict_class_name, (dict,), {'__missing__': keygen,})

    # Instantiate and return the first and only instance of this class.
    return default_dict_class()


_DEFAULT_DICT_ID = 0
'''
Unique arbitrary identifier with which to uniquify the classname of the next
:func:`DefaultDict`-derived type.
'''

Ключ ... получить его, ключ? для этого тайного волшебства - это вызов трехпараметрический вариант type() builtin:

type(default_dict_class_name, (dict,), {'__missing__': keygen,})

Эта отдельная строка динамически генерирует новый подкласс dict, накладывающий необязательный __missing__ метод для вызываемого вызова. Обратите внимание на отдельные отсутствие шаблона, уменьшая использование DefaultDict до одной строки Python.

Автоматизация для вопиющей победы.

Ответ 6

Я согласен, что это должно быть легко сделать, а также легко настроить различные значения по умолчанию или функции, которые каким-то образом преобразуют недостающее значение.

Вдохновленный Сесил Карри ответ, я спросил себя: почему бы не создать генератор по умолчанию (либо константа или вызываемый) как член класса, а не генерировать разные классы все время? Позвольте мне продемонстрировать:

# default behaviour: return missing keys unchanged
dic = FlexDict()
dic['a'] = 'one a'
print(dic['a'])
# 'one a'
print(dic['b'])
# 'b'

# regardless of default: easy initialisation with existing dictionary
existing_dic = {'a' : 'one a'}
dic = FlexDict(existing_dic)
print(dic['a'])
# 'one a'
print(dic['b'])
# 'b'

# using constant as default for missing values
dic = FlexDict(existing_dic, default = 10)
print(dic['a'])
# 'one a'
print(dic['b'])
# 10

# use callable as default for missing values
dic = FlexDict(existing_dic, default = lambda missing_key: missing_key * 2)
print(dic['a'])
# 'one a'
print(dic['b'])
# 'bb'
print(dic[2])
# 4

Как это работает? Не так сложно:

class FlexDict(dict):
    '''Subclass of dictionary which returns a default for missing keys.
    This default can either be a constant, or a callable accepting the missing key.
    If "default" is not given (or None), each missing key will be returned unchanged.'''
    def __init__(self, content = None, default = None):
        if content is None:
            super().__init__()
        else:
            super().__init__(content)
        if default is None:
            default = lambda missing_key: missing_key
        self.default = default # sets self._default

    @property
    def default(self):
        return self._default

    @default.setter
    def default(self, val):
        if callable(val):
            self._default = val
        else: # constant value
            self._default = lambda missing_key: val

    def __missing__(self, x):
        return self.default(x)

Конечно, можно обсуждать, хотите ли вы разрешить изменение функции по умолчанию после инициализации, но это просто означает удаление @default.setter и поглощение его логики в __init__.

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