Как я могу сделать defaultdict безопасным для неопытных клиентов?

Несколько раз (даже несколько строк) я был укушен ошибкой defaultdict: забыв, что что-то действительно является дефолтом и рассматривает его как обычный словарь.

d = defaultdict(list)

...

try:
  v = d["key"]
except KeyError:
  print "Sorry, no dice!"

Для тех, кто был укушен, проблема очевидна: когда d не имеет ключевого "ключа", v = d["key"] волшебным образом создает пустой список и назначает его как d["key"], так и v вместо того, чтобы поднимать исключение. Который может быть довольно больно отслеживать, если d приходит из какого-то модуля, детали которого не помнят очень хорошо.

Я ищу способ избавиться от этой ошибки. Для меня лучшим решением было бы как-то отключить мануал defaultdict, прежде чем вернуть его клиенту.

Ответ 1

Вы можете предотвратить создание значений по умолчанию, назначив d.default_factory = None. Однако мне не совсем нравится идея внезапного изменения поведения объекта. Я предпочел бы копировать значения в новый dict, если он не налагает серьезное ограничение производительности.

Ответ 2

Вы все равно можете преобразовать его в нормальный dict.

d = collections.defaultdict(list)
d = dict(d)

Ответ 3

используйте другую идиому:

if 'key' not in d:
    print "Sorry, no dice!"

Ответ 4

Это именно то поведение, которое вы хотите от defaultdict, а не ошибка. Если вы этого не хотите, не используйте defaultdict.

Если вы продолжаете забывать, что такое переменные типа, тогда назовите их соответствующим образом - например, суффикс ваших имен defaultdict с помощью "_ddict".

Ответ 5

Используя идею rkhayrov об отбрасывании self.default_factory, вот подменю подкласса defaultdict:

class ToggleableDefaultdict(collections.defaultdict):
    def __init__(self,default_factory):
        self._default_factory=default_factory
        super(ToggleableDefaultdict,self).__init__(default_factory)
    def off(self):
        self.default_factory=None
    def on(self):
        self.default_factory=self._default_factory

Например:

d=ToggleableDefaultdict(list)
d['key'].append(1)
print(d)
# defaultdict(<type 'list'>, {'key': [1]})

d.off()
d['newkey'].append(2)
# KeyError: 'newkey'

d.on()
d['newkey'].append(2)
# defaultdict(<type 'list'>, {'newkey': [2], 'key': [1]})