Есть ли умный способ передать ключ defaultdict default_factory?


95

У класса есть конструктор, который принимает один параметр:

class C(object):
    def __init__(self, v):
        self.v = v
        ...

Где-то в коде для значений в dict полезно знать их ключи.
Я хочу использовать defaultdict с ключом, переданным новым значениям по умолчанию:

d = defaultdict(lambda : C(here_i_wish_the_key_to_be))

Какие-либо предложения?

Ответы:


128

Это вряд ли можно назвать умным, но создание подклассов - ваш друг:

class keydefaultdict(defaultdict):
    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError( key )
        else:
            ret = self[key] = self.default_factory(key)
            return ret

d = keydefaultdict(C)
d[x] # returns C(x)

16
Это именно то уродство, которого я пытаюсь избежать ... Даже использование простого dict и проверка наличия ключа намного чище.
Бенджамин Нитлехоо,

1
@ Пол: и все же это ваш ответ. Уродство? Давай!
tzot

4
Думаю, я просто возьму этот кусок кода и вставлю его в свой персонализированный модуль общих утилит, чтобы я мог использовать его, когда захочу. Не так уж и некрасиво ...
weronika 07

24
+1 Непосредственно отвечает на вопрос ОП и не выглядит "уродливым" мне. Кроме того, хороший ответ , потому что многие из них , кажется, не понимают , что defaultdict«s __missing__()метод может быть переопределен (как это может в любой подкласс встроенного dictкласса , начиная с версии 2.5).
Мартино 01

7
+1 Вся цель __missing__ - настроить поведение отсутствующих ключей. Подход dict.setdefault (), упомянутый @silentghost, также будет работать (с положительной стороны, setdefault () короток и уже существует; с другой стороны, он страдает от проблем с эффективностью, и никому не нравится имя "setdefault") .
Раймонд Хеттингер

26

Нет, нет.

defaultdictРеализация не может быть сконфигурирована для передачи отсутствует keyв default_factoryвне коробки. Ваш единственный вариант - реализовать свой собственный defaultdictподкласс, как это было предложено @JochenRitzel выше.

Но это не «умно» или почти так чисто, как было бы стандартное библиотечное решение (если бы оно существовало). Таким образом, ответ на ваш лаконичный вопрос «да / нет» будет однозначно «Нет».

Жаль, что в стандартной библиотеке отсутствует столь часто используемый инструмент.


Да, было бы лучше, если бы фабрика могла взять ключ (унарная функция, а не нулевая). Если мы хотим вернуть константу, легко отказаться от аргумента.
YvesgereY

6

Я не думаю, что тебе defaultdictздесь вообще нужно . Почему бы просто не использовать dict.setdefaultметод?

>>> d = {}
>>> d.setdefault('p', C('p')).v
'p'

Это, конечно, создаст много экземпляров C. Если это проблема, я думаю, подойдет более простой подход:

>>> d = {}
>>> if 'e' not in d: d['e'] = C('e')

defaultdictНасколько я понимаю, это будет быстрее, чем любая другая альтернатива.

ETA относительно скорости inтеста по сравнению с использованием предложения try-except:

>>> def g():
    d = {}
    if 'a' in d:
        return d['a']


>>> timeit.timeit(g)
0.19638929363557622
>>> def f():
    d = {}
    try:
        return d['a']
    except KeyError:
        return


>>> timeit.timeit(f)
0.6167065411074759
>>> def k():
    d = {'a': 2}
    if 'a' in d:
        return d['a']


>>> timeit.timeit(k)
0.30074866358404506
>>> def p():
    d = {'a': 2}
    try:
        return d['a']
    except KeyError:
        return


>>> timeit.timeit(p)
0.28588609450770264

7
Это очень расточительно в тех случаях, когда к d обращаются много раз, и лишь изредка пропускается ключ: C (ключ), таким образом, создаст массу ненужных объектов, которые собирает сборщик мусора. Кроме того, в моем случае возникает дополнительная боль, поскольку создание новых объектов C происходит медленно.
Бенджамин Нитлехоо,

@ Пол: верно. Я бы предложил еще более простой метод, см. Мое редактирование.
SilentGhost

Я не уверен, что это быстрее, чем defaultdict, но я обычно так делаю (см. Мой комментарий к ответу THC4k). Я надеялся, что есть простой способ обойти тот факт, что default_factory не принимает аргументов, чтобы код был немного более элегантным.
Бенджамин Нитлехоо,

5
@SilentGhost: Я не понимаю - как это решает проблему OP? Я думал, что OP хочет любую попытку прочитать, d[key]чтобы вернуть d[key] = C(key)if key not in d. Но ваше решение требует, чтобы он на самом деле ехал и заранее настраивал d[key]? Как он узнал, что keyему нужно?
max

2
Поскольку setdefault чертовски уродлив, а defaultdict из коллекции ДОЛЖЕН поддерживать фабричную функцию, которая получает ключ. Какая упущенная возможность от разработчиков Python!
jgomo3

0

Вот рабочий пример словаря, который автоматически добавляет значение. Демонстрационная задача по поиску повторяющихся файлов в / usr / include. Обратите внимание, что для настройки словаря PathDict требуется всего четыре строки:

class FullPaths:

    def __init__(self,filename):
        self.filename = filename
        self.paths = set()

    def record_path(self,path):
        self.paths.add(path)

class PathDict(dict):

    def __missing__(self, key):
        ret = self[key] = FullPaths(key)
        return ret

if __name__ == "__main__":
    pathdict = PathDict()
    for root, _, files in os.walk('/usr/include'):
        for f in files:
            path = os.path.join(root,f)
            pathdict[f].record_path(path)
    for fullpath in pathdict.values():
        if len(fullpath.paths) > 1:
            print("{} located in {}".format(fullpath.filename,','.join(fullpath.paths)))
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.