Предоставление defaultdict как обычного dict

Я использую defaultdict(set) для заполнения внутреннего сопоставления в очень большой структуре данных. После заполнения вся структура (включая сопоставление) становится доступной для клиентского кода. В этот момент я не хочу, чтобы кто-либо модифицировал сопоставление.

И никто не делает этого намеренно. Но иногда клиентский код может случайно ссылаться на несуществующий элемент. В этот момент обычный словарь поднял бы KeyError, но поскольку отображение равно defaultdict, он просто создает новый элемент (пустой набор) в этом ключе. Это довольно сложно уловить, так как все происходит тихо. Но мне нужно убедиться, что этого не произойдет (семантика на самом деле не ломается, но отображение увеличивается до огромных размеров).

Что я должен делать? Я вижу эти варианты:

  1. Найдите все экземпляры в текущем и будущем клиентском коде, где поиск по словарю выполняется для сопоставления, и вместо этого преобразуйте его в mapping.get(k, {}). Это просто ужасно.

  2. "Заморозить" defaultdict после полной инициализации структуры данных, преобразовав ее в dict. (Я знаю, что на самом деле это не заморожено, но я верю, что клиентский код на самом деле не пишет mapping[k] = v.) Неэлегантно и сильно влияет на производительность.

  3. Оберните defaultdict интерфейсом dict. Какой элегантный способ сделать это? Я боюсь, что удар по производительности может быть огромным (этот поиск часто используется в узких циклах).

  4. Подкласс defaultdict и добавьте метод, который «отключает» все функции defaultdict, оставляя его вести себя как обычный dict. Это вариант 3 выше, но я не уверен, что он быстрее. И я не знаю, выполнимо ли это, не полагаясь на детали реализации.

  5. Используйте обычный dict в структуре данных, переписав весь код там, чтобы сначала проверить, есть ли элемент в словаре, и добавить его, если нет. Фигово.


person max    schedule 20.11.2012    source источник
comment
переписывание просто использовало бы метод dict.setdefault... Ничего страшного   -  person JBernardo    schedule 20.11.2012
comment
@JBernardo Вы говорите о варианте 4? Все, что я знаю о defaultdict, это то, что он переопределяет __getitem__ для добавления элемента, если это необходимо. Может быть, он делает это с помощью метода setdefault, может быть, он реализует ту же логику напрямую, даже не вызывая setdefault. Не полагаясь на детали реализации, я не могу ничего предположить, не так ли?   -  person max    schedule 20.11.2012
comment
Он имеет в виду ваш вариант № 5. Просто используйте свой data.setdefault() в своем коде вместо defaultdict   -  person    schedule 20.11.2012
comment
Я думаю, вам должно сойти с рук просто позвонить dict defaultdict, чтобы продиктовать это.   -  person inspectorG4dget    schedule 20.11.2012
comment
@Pyson: ах, ты прав, это имеет смысл. Но это аргумент в пользу того, чтобы никогда не использовать defaultdict, не так ли? (Не то чтобы я не согласен, просто хочу понять логику.)   -  person max    schedule 20.11.2012
comment
@ InspectorG4dget размер структуры данных превышает 1 ГБ, поэтому копирование всех данных (как это произойдет, если я вызову dict) слишком дорого.   -  person max    schedule 20.11.2012
comment
Ну, defaultdict в большинстве случаев быстрее...   -  person    schedule 20.11.2012
comment
@Pyson: Почему? dict.setdefault реализован на C и делает именно то, что делает defaultdict.__getitem__. Разве это не должно быть одинаково быстро?   -  person max    schedule 20.11.2012
comment
Вы бы так подумали, да?   -  person    schedule 20.11.2012


Ответы (3)


Документы defaultdict говорят для default_factory:

Если атрибут default_factory имеет значение None, возникает исключение KeyError с ключом в качестве аргумента.

Что, если вы просто установите default_factory вашего defaultdict на None? Например.,

>>> d = defaultdict(int)
>>> d['a'] += 1
>>> d
defaultdict(<type 'int'>, {'a': 1})
>>> d.default_factory = None
>>> d['b'] += 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'b'
>>> 

Не уверен, что это лучший подход, но, похоже, работает.

person Neal    schedule 20.11.2012
comment
Кто знал, что предложенное мной решение уже реализовано как функция defaultdict? Отличная находка. (+1) - person mgilson; 20.11.2012
comment
Вау, это идеально. Я надеюсь, что безопасно изменить default_factory на существующий объект defaultdict (не понимаю, почему бы и нет). - person max; 20.11.2012
comment
@max - в документации конкретно сказано, что default_factory является атрибутом доступным для записи, поэтому он должен быть безопасным. - person mgilson; 20.11.2012
comment
@max: используйте источник: defdictobject, < a href="http://hg.python.org/cpython/file/3d0686d90f55/Modules/_collectionsmodule.c#l1375" rel="nofollow noreferrer">defdict_members (имя, тип, смещение, флаги, док. ; flags==0 означает, что он доступен для записи), defdict_missing. - person Eryk Sun; 20.11.2012

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

my_dict = dict(my_default_dict)

При желании можно использовать аннотацию типа typing.Final.

Если словарь по умолчанию является рекурсивным словарем по умолчанию, см. этот ответ, в котором используется рекурсивное решение.

person Acumenus    schedule 24.09.2017

Вы можете создать класс, который содержит ссылку на ваш dict и предотвратить setitem()

from collections import Mapping

class MyDict(Mapping):
    def __init__(self, d):
        self.d = d;

    def __getitem__(self, k):
        return self.d[k]

    def __iter__(self):
        return self.__iter__()

    def __setitem__(self, k, v):
        if k not in self.d.keys():
            raise KeyError
        else:
            self.d[k] = v
person pyrospade    schedule 20.11.2012
comment
Разве это не было бы очень медленным, учитывая, что он использует чистый python для критических методов? - person max; 20.11.2012
comment
Для метода getitem? Не уверен, что накладные расходы на производительность с этим по сравнению с defaultdict - person pyrospade; 21.11.2012
comment
В любом случае, я думаю, что решение Нила лучше всего подходит для вашей проблемы. - person pyrospade; 21.11.2012