MikeRand
MikeRand

Reputation: 4828

Python - are there advantages/disadvantages to assignment statements with multiple (target list "=") groups?

From the solution for recursive dictionaries found here, I saw the following Python recipe:

class RecursiveDict(dict):
    """Implementation of perl's autovivification feature."""
    def __missing__(self, key):
        value = self[key] = type(self)()
        return value

About the line value = self[key] = type(self)(): Does it have any advantages or disadvantages over the following code? Or is it just syntax?

self[key] = type(self)()
value = self[key]

Upvotes: 1

Views: 840

Answers (2)

Fred Foo
Fred Foo

Reputation: 363597

It's mostly syntactic sugar, but not for the statements you list; instead, it's equivalent to

value = type(self)()
self[key] = value

To see the difference, type the following at your Python prompt:

>>> class FakeDict(object):
...  def __setitem__(self, k, v):
...   pass
...  def __getitem__(self, k):
...   raise KeyError("boom!")
... 
>>> d = FakeDict()
>>> x = d[1] = 42
>>> d[1] = 42
>>> x = d[1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in __getitem__
KeyError: boom!

Of course, with a well-behaved dict, this won't matter, but

self[key] = type(self)()
value = self[key]

does perform a superfluous dict lookup in the second line so inside a loop the shorthand may give a performance benefit.

In the general case, it's even a bit more complicated than I spelled out above. The shorthand assignment is actually equivalent to

__some_temporary = type(self)()
value = __some_temporary
self[key] = __some_temporary

and the simplified form value = type(self)(); self[key] = value ensues only because value is a simple local variable. If value were replaced by an expression of the form container[key] that might fail, the equivalence would no longer hold.

Upvotes: 4

fog
fog

Reputation: 3391

It is just syntax. Sometimes it makes easier to read the code, sometimes it doesn't.

But what happens? The other answer made me curious: I commented to it saying that the compier probably uses a "register or variable" and that's exactly what happens. Let's decompile a simple function:

>>> import dis
>>> def f():
...     a = b = c = 1
...     return a
... 
>>> dis.dis(f)
  2           0 LOAD_CONST               1 (1)
              3 DUP_TOP             
              4 STORE_FAST               0 (a)
              7 DUP_TOP             
              8 STORE_FAST               1 (b)
             11 STORE_FAST               2 (c)

  3          14 LOAD_FAST                0 (a)
             17 RETURN_VALUE        

The right-most value (1 in my case type(self)() in the real case) is loaded onto the stack (think "local variable") then duplicated onto the stack (in a stack-based VM operations consume the stack so if you want to "keep" a value you need to make multiple copies) then assigned, then duplicated, then assigned and so on. I.e., converted back to to Python this is something like:

def f():
    t = 1
    a = t
    b = t
    c = t
    return a

The compiler even preserves the order of the assignment going from left to right (a, b, c).

Upvotes: 1

Related Questions