Одно незначительное раздражение с dict.setdefault
заключается в том, что он всегда оценивает свой второй аргумент (когда он задан, конечно), даже когда первый первый аргумент уже является ключом в словаре.
Например:
import random
def noisy_default():
ret = random.randint(0, 10000000)
print 'noisy_default: returning %d' % ret
return ret
d = dict()
print d.setdefault(1, noisy_default())
print d.setdefault(1, noisy_default())
Это приводит к следующим выводам:
noisy_default: returning 4063267
4063267
noisy_default: returning 628989
4063267
По завершении последней строки второе выполнение noisy_default
, так как к этой точке ключ 1
уже присутствует в d
(со значением 4063267
).
Возможно ли реализовать подкласс dict
, метод setdefault
которого оценивает его второй аргумент лениво?
РЕДАКТИРОВАТЬ:
Ниже приведена реализация, вдохновленная комментарием BrenBarn и ответом Павла Аносова. В то время как на этом я пошел вперед и реализовал ленивую версию, так как основная идея по сути то же самое.
class LazyDict(dict):
def get(self, key, thunk=None):
return (self[key] if key in self else
thunk() if callable(thunk) else
thunk)
def setdefault(self, key, thunk=None):
return (self[key] if key in self else
dict.setdefault(self, key,
thunk() if callable(thunk) else
thunk))
Теперь, фрагмент
d = LazyDict()
print d.setdefault(1, noisy_default)
print d.setdefault(1, noisy_default)
производит вывод следующим образом:
noisy_default: returning 5025427
5025427
5025427
Обратите внимание, что второй аргумент, d.setdefault
выше в d.setdefault
теперь является вызываемым, а не вызовом функции.
Когда второй аргумент LazyDict.get
или LazyDict.setdefault
не является вызываемым, они ведут себя так же, как и соответствующие методы dict
.
Если вы хотите передать вызываемое значение как значение по умолчанию (т.е. Не предназначено для вызова), или если вызываемый вызываемый требует аргументов, добавьте lambda:
к соответствующему аргументу. Например:
d1.setdefault('div', lambda: div_callback)
d2.setdefault('foo', lambda: bar('frobozz'))
Те, кому не нравится идея переопределения get
и setdefault
, и/или возникающая в результате необходимость тестирования на возможность вызова и т.д., Могут использовать эту версию:
class LazyButHonestDict(dict):
def lazyget(self, key, thunk=lambda: None):
return self[key] if key in self else thunk()
def lazysetdefault(self, key, thunk=lambda: None):
return (self[key] if key in self else
self.setdefault(key, thunk()))