Как сделать словарь Python, который возвращает ключ для ключей, отсутствующих в словаре, вместо вызова KeyError? - PullRequest
50 голосов
/ 03 июня 2011

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

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

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

Ответы [ 6 ]

60 голосов
/ 03 июня 2011

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

class smart_dict(dict):
    def __missing__(self, key):
        return key
25 голосов
/ 03 июня 2011

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

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'

за исключением ... хорошо: 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 []
13 голосов
/ 18 октября 2011

Первый респондент назвал 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'
6 голосов
/ 23 мая 2017

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

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

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

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

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

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

Введение DefaultDict

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

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

Учитывая этот тип, решение вопроса 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():

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

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

Автоматизация для вопиющего выигрыша.

1 голос
/ 13 декабря 2017

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

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

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

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

0 голосов
/ 03 июня 2011
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...