Reputation: 4828
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
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
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