Я хочу создать словарь python, который возвращает мне значение ключа для ключей, отсутствующих в словаре.
Пример использования:
dic = smart_dict()
dic['a'] = 'one a'
print(dic['a'])
# >>> one a
print(dic['b'])
# >>> b
Я хочу создать словарь python, который возвращает мне значение ключа для ключей, отсутствующих в словаре.
Пример использования:
dic = smart_dict()
dic['a'] = 'one a'
print(dic['a'])
# >>> one a
print(dic['b'])
# >>> b
dict
имеют __missing__
для этого:
class smart_dict(dict):
def __missing__(self, key):
return key
Почему бы вам просто не использовать
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 []
Первый ответчик упомянул 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'
Поздравляем. Вы тоже обнаружили бесполезность
стандартный тип collections.defaultdict
. Если эта отвратительная скрытая куча запах кода
оскорбляет ваши тонкие чувства так же, как и мои, это ваша удача
День StackOverflow.
Благодаря запрещенному чуду 3-параметр
вариант type()
встроенный, создающий не-бесполезный тип словаря по умолчанию - это весело и выгодно.
Абсолютно ничего, предполагая, что вам нравится избыточный шаблон и потрясающая глупость collections.defaultdict
, которая должна вести себя так, как ожидалось, но на самом деле этого не делает. Чтобы быть справедливым, Йохен
Ritzel принят
решение подкласса dict
и
реализуя необязательный __missing__()
метод является фантастическим
обходной путь для небольших случаев использования, требующий только одного словаря по умолчанию.
Но шаблон такого типа плохо масштабируется. Если вы обнаружите, что несколько словарей по умолчанию, каждая из которых имеет свою собственную немного отличающуюся логику для генерирование отсутствующих пар ключ-значение, альтернатива промышленной прочности автоматизация шаблона гарантирована.
Или, по крайней мере, приятно. Потому что почему бы не исправить то, что сломалось?
В менее чем десяти строках чистого 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.
Автоматизация для вопиющей победы.
Подкласс dict
__getitem__
. Например, Как правильно подклассировать dict и переопределить __getitem__ и __setitem__
Я согласен, что это должно быть легко сделать, а также легко настроить различные значения по умолчанию или функции, которые каким-то образом преобразуют недостающее значение.
Вдохновленный Сесил Карри ответ, я спросил себя: почему бы не создать генератор по умолчанию (либо константа или вызываемый) как член класса, а не генерировать разные классы все время? Позвольте мне продемонстрировать:
# 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__
.
Включение интроспекции в текущее (постоянное) значение по умолчанию может быть добавлено двумя дополнительными строками.