2016-07-28 5 views
0

Кажется, что резервный вызов вызывается, даже если ключ присутствует внутри словаря. Это намеченное поведение? Как это может обойти это?странное поведение dict.get

>>> i = [1,2,3,4] 
>>> c = {} 
>>> c[0]= 0 
>>> c.get(0, i.pop()) 
0 
>>> c.get(0, i.pop()) 
0 
>>> c.get(0, i.pop()) 
0 
>>> c.get(0, i.pop()) 
0 
>>> c.get(0, i.pop()) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
IndexError: pop from empty list 
+1

Вот как работает интерпретатор питона, он первым оценил аргументы, которые вы передаются абоненту затем выполняет функцию. – Kasramvd

ответ

1

При выполнении c.get(0, i.pop()), то i.pop() часть получает оценку перед тем Результат возвращается передается c.get(...). Вот почему ошибка появляется, если список i пуст из-за предыдущих вызовов .pop().

Чтобы обойти эту проблему, вы должны либо проверить, если список не пуст, прежде чем пытаться выскочить элемент из него, или просто попытаться ему поймать возможное исключение:

if not i: 
    # do not call do i.pop(), handle the case in some way 

default_val = i.pop() 

или

try: 
    c.get(0, i.pop()) 
except IndexError: 
    # gracefully handle the case in some way, e.g. by exiting 

default_val = i.pop() 

Первый подход называется LBYL («семь раз отмерь»), в то время как второй называется ЭСПЦ («проще попросить прощения, чем разрешения»). Последнее обычно предпочитается в Python и считается более Pythonic, потому что код не забивается множеством защитных чеков, хотя подход LBYL также имеет свои достоинства и может быть таким же читаемым (зависит от конкретного случая).

+0

Как я могу это сделать? –

+0

@FedericoPonzi Я обновил свой ответ - либо проверьте, нет ли пустого списка, и не выскользнут из него, если он есть, или еще раз сохраните возможное исключение – plamut

1

Это ожидаемые результаты, потому что вы вызываете непосредственно i.pop(), который вызывается до c.get().

1

Аргумент по умолчанию dict.get действительно оценивается до того, как словарь проверяет наличие ключа или нет. Фактически, он оценивается до того, как метод get даже называется! Ваши get вызовы эквивалентны следующему:

default = i.pop() # this happens unconditionally 
c.get(0, default) 
default = i.pop() # this one too 
c.get(0, default) 
#... 

Если вы хотите указать отзывной, который будет использоваться только для заполнения недостающих значений словаря, вы можете захотеть использовать collections.defaultdict. Он принимает отзывной, который используется именно таким образом:

c = defaultdict(i.pop) # note, no() after pop 
c[0] = 0 
c[0] # use regular indexing syntax, won't pop anything 

Обратите внимание, что в отличие от get вызова, значение, возвращенное вызываемым фактически хранятся в словаре впоследствии, что может быть нежелательным.

1

Это предназначенное поведение, поскольку i.pop() - это выражение, которое оценивается до c.get(...). Представьте, что произойдет, если это не так. Вы могли бы иметь что-то вроде этого:

def myfunction(number): 
    print("Starting work") 
    # Do long, complicated setup 
    # Do long, complicated thing with number 

myfunction(int('kkk')) 

Когда у вас есть int('kkk') быть оценены? Будет ли это, как только myfunction() использует его (после параметров)? После этого, после долгой, сложной настройки, у него получится ValueError. Если вы скажете x = int('kkk'), когда вы ожидаете ValueError? Сначала оценивается правая сторона, и значение ValueError происходит немедленно. x не определяется.

Есть несколько возможных способов решения:

c.get(0) or i.pop() 

Это вероятно, будет работать в большинстве случаев, но не будет работать, если c.get(0) может возвращать значение Falsey, которое не None. Более безопасный способ немного больше:

try: 
    result = c[0] 
except IndexError: 
    result = i.pop() 

Конечно, нам нравится ЭСПЦ (Легче попросить прощения, чем разрешения), но вы можете спросить разрешение:

c[0] if 0 in c else i.pop() 

(Кредиты @soon)

0

Оба аргумента вычисляются перед вызовом функции get. Таким образом, во всех вызовах размер списка уменьшается на 1, даже если ключ присутствует.

Try что-то вроде если c.has_key (0): печать с [0] еще: печатью i.pop()

1

Там нет реального способа обойти это, за исключением использования если .. .else ...!
В вашем случае, этот код будет работать:

c[0] if 0 in c else i.pop() 
Смежные вопросы