Say we have this
>>> x = {'a': 1, 'b': 2}
>>> y = {}
>>> for k, y[k] in x.items(): pass
...
>>> y
{'a': 1, 'b': 2}
Why does this work?
Note: I saw this first here
CodePudding user response:
a, b = (c, d)
unpacks the tuple from left to right and assigns a = c
and b = d
in that order.
x.items()
iterates over key-value pairs in x
. E.g. doing list(x.items())
will give [('a', 1), ('b', 2)]
for a, b in x.items()
assigns the key to a
, and the value to b
for each key-value pair in x
.
for k, y[k] in x.items()
assigns the key to k
, and the value to y[k]
for each key-value pair in x
.
You can use k
in y[k]
because k
has already been assigned since unpacking happens left-right
You don't need to do anything in the loop because whatever you needed is done already.
Because the loop already assigned every value in x
to y[k]
, y
is now a shallow copy of x
.
As the tweet you reference says, this is indeed a "terse, unintuitive, and confusing" way to do x.copy()
CodePudding user response:
This is a remarkably odd way to use the loop binding. For each item, the tuple of (key, value)
is assigned into k, y[k]
. Both instances of k
refer to the same variable, so the subscription y[k]
assigns into the dictionary y
. This relies on a destructuring assignment being solved left to right; using y[k], k
doesn't work as k
isn't assigned the first time through the loop.
CodePudding user response:
The iterating variable is assigned values as it iterates through the iterable. In your case, k, y[k]
are assigned with (key, value)
from dict.items()
.
In your case y[k]
actually calls dict.__setitem__
here which is why your y
gets updated.
Example,
class T():
def __setitem__(self, key, value):
print(f"Assigning {key} with value {value}")
x = {"a": 2, "b": 3, "c": 4}
val = T()
for k, val[k] in x.items():
pass
Assigning a with value 2
Assigning b with value 3
Assigning c with value 4
In your case dict.__setitem__
is called while iterating through x.items()
and dict.__setitem__
mutates your dict y
so eventually you are copying x
dict into y
.
As an aside: I have one more "obnoxious, unintuitive, and confusing" way to copy a dict.
x = {'a': 2, 'b': 3, 'c': 4}
it = iter(x.items())
y = {}
[*iter(lambda : y.__setitem__(*next(it)), "Is this confusing enough??")]
print(y)
# {'a': 2, 'b': 3, 'c': 4}