Выставляя `defaultdict` как обычный` dict`

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

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

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

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

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

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

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

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

Ответ 1

defaultdict docs говорят для default_factory:

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

Что делать, если вы задали default_factory default_factory 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'
>>> 

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

Ответ 2

Вы можете создать класс, содержащий ссылку на ваш 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

Ответ 3

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

my_dict = dict(my_default_dict)

Регулярный dict, конечно, эффективно заморожен.

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