0

Seems like the fallback is called even if the key is present inside the dictionary. Is this an intended behaviour? How can workaround it?

>>> 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
martineau
  • 119,623
  • 25
  • 170
  • 301
Federico Ponzi
  • 2,682
  • 4
  • 34
  • 60
  • 1
    That's how the python's interpreter works, it first evaluated the arguments that you've passed to caller then executes the function. – Mazdak Jul 28 '16 at 11:42

6 Answers6

1

When doing c.get(0, i.pop()), the i.pop() part gets evaluated before the result it returns is passed to the c.get(...). That's the reason that the error appears if a list i is empty due to the previous .pop() calls on it.

To get around this, you should either check if the list is not empty before trying to pop an element from it, or just try it an catch a possible exception:

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

default_val = i.pop()

or

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

default_val = i.pop()

The first approach is called LBYL ("look before you leap"), while the second is referred to as EAFP ("easier to ask for forgiveness than permission"). The latter is usually preferred in Python and considered more Pythonic, because the code does not get cluttered with a lot of safeguarding checks, although the LBYL approach has its merits, too, and can be just as readable (use-case dependent).

plamut
  • 3,085
  • 10
  • 29
  • 40
1

This is the expected results because you're directly invoking i.pop() which gets called before c.get().

notorious.no
  • 4,919
  • 3
  • 20
  • 34
1

The default argument to dict.get does indeed get evaluated before the dictionary checks if the key is present or not. In fact, it's evaluated before the get method is even called! Your get calls are equivalent to this:

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

If you want to specify a callable that will be used only to fill in missing dictionary values, you might want to use a collections.defaultdict. It takes a callable which is used exactly that way:

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

Note that unlike a get call, the value returned by the callable will actually be stored in the dictionary afterwards, which might be undesirable.

Blckknght
  • 100,903
  • 11
  • 120
  • 169
1

This is intended behavior because i.pop() is an expression that is evaluated before c.get(...) is. Imagine what would happen if that weren't the case. You might have something like this:

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

myfunction(int('kkk'))

When would you have int('kkk') to be evaluated? Would it be as soon as myfunction() uses it (after the parameters)? It would then finally have the ValueError after the long, complicated setup. If you were to say x = int('kkk'), when would you expect the ValueError? The right side is evaluated first, and the ValueError occurs immediately. x does not get defined.

There are a couple possible workarounds:

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

That will probably work in most cases, but won't work if c.get(0) might return a Falsey value that is not None. A safer way is a little longer:

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

Of course, we like EAFP (Easier to Ask Forgiveness than Permission), but you could ask permission:

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

(Credits to @soon)

zondo
  • 19,901
  • 8
  • 44
  • 83
1

There is no real way to workaround this except using if...else... !
In your case, this code would work:

c[0] if 0 in c else i.pop()
Rafael Albert
  • 445
  • 2
  • 8
0

Both the arguments are evaluated before calling the get function. Thus in all calls the size of list is decreased by 1 even if the key is present.

Try something like if c.has_key(0): print c[0] else: print i.pop()

Afif
  • 66
  • 1
  • 4