Чи є розумний спосіб передати ключ до defaultdict_factory?


93

Клас має конструктор, який приймає один параметр:

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

Десь у коді для значень у дикті корисно знати їх ключі.
Я хочу використати вирок за замовчуванням із ключем, переданим значенням за замовчуванням для новонародженого:

d = defaultdict(lambda : C(here_i_wish_the_key_to_be))

Будь-які пропозиції?

Відповіді:


127

Навряд чи це кваліфікується як розумний, але підкласифікація - це ваш друг:

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
Саме цього потворства я намагаюся уникнути ... Навіть користуючись простим словом та перевіряючи наявність ключів, набагато чистіше.
Бенджамін Нітлху

1
@ Пол: і все ж це ваша відповідь. Потворність? Давай!
tzot

4
Я думаю, що я просто візьму цей фрагмент коду і поміщу його в свій персоніфікований загальний модуль утиліт, щоб я міг використовувати його, коли захочу. Не надто потворно в такий спосіб ...
weronika

24
+1 Безпосередньо звертається до питання ОП і не здається мені "потворним". Також хороша відповідь, оскільки багато хто, здається, не усвідомлюють, що метод defaultdict'' __missing__()можна замінити (як це можна в будь-якому підкласі вбудованого dictкласу, починаючи з версії 2.5).
martineau

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-exclu:

>>> 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 (ключ), таким чином, створює тонни непотрібних об'єктів для збирання GC. Крім того, у моєму випадку виникає додатковий біль, оскільки створення нових об’єктів C відбувається повільно.
Бенджамін Нітлху

@ Пол: це правильно. Тоді я б запропонував ще більш простий метод, див. Мою редакцію.
SilentGhost

Я не впевнений, що це швидше, ніж за замовчуванням, але я зазвичай це роблю (див. Мій коментар до відповіді THC4k). Я сподівався, що існує простий спосіб зламати той факт, що default_factory не потребує аргументів, щоб зберегти код трохи елегантнішим.
Бенджамін Нітлху

5
@SilentGhost: Я не розумію - як це вирішує проблему OP? Я думав, що ОП хоче, щоб будь-яка спроба читання d[key]повернулася, d[key] = C(key)якщо key not in d. Але ваше рішення вимагає, щоб він насправді пішов і заздалегідь визначився d[key]? Звідки він знатиме, що keyйому потрібно?
максимум

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.