Comments on Peculiar Self-References
JWMH said:
In my opinion, it would make more sense to just treat assignments as expressions, as is done in C-like languages. Maybe I'm just more accustomed to C-like languages, though.
I think that one thing that makes the statement confusing is that the first equals sign serves a purpose completely different from the second equals sign, but both of them are represented by the same character.
Oshiar said:
XG15, Yet another reason is all the references in LHS expressions are resolved before the inner expression is executed. Thus
a['k'] = a = {}
won't still work.
o11c said:
Yeah, Python really messed up the order of multiple assignments. This could be fixed by deprecating arbitrary multiple assignments. Instead, only allow:
- any number of assignments to simple variable names (whether local, nonlocal, or global - I'm pretty sure these all are safe), or
- only one assignment to an arbitrary lvalue.
Use of "or" rather than "and" is deliberate, to fix an obscure edge
case. We can't even limit the restriction to "assignments involving
a function call". Notably, self.a = a = {}
is not
always the same as a = self.a = {}
. Proof:
#!/usr/bin/env python3
class Foo:
def __init__(self):
self.get_a = lambda: a
a = 1
a = self.a = 2
a = 3
self.a = a = 4
def __setattr__(self, name, val):
if name != 'get_a':
print(self.get_a())
self.__dict__[name] = val
if __name__ == '__main__':
Foo()
Susam Pal said:
For those who are wondering what the code in the previous comment does, here is its output:
2 3
When a = self.a = 2
is evaluated, the assignment
to self.a
triggers the __setattr__
call
which tells us that the __init__
method's local
variable a
has already been updated to 2
.
That's the first line of output.
When self.a = a = 4
is evaluated, once again the
assignment to self.a
triggers
the __setattr__
call which tells us that the value of
local a
is still 3
. It has not been
updated to 4
yet. It gets updated to 4
after the assignment to self.a
completes.
Both lines of output demonstrate the left-to-right assignment quite clearly.
Kaushik Ghose said:
This is very neat, but I would like to approach it a bit differently. Python allows self-referential objects and is able to handle this gracefully while printing them.
>>> a = {} >>> a['self'] = a >>> a {'self': {...}} >>> a['self'] {'self': {...}} >>> a['self']['self'] {'self': {...}}
Python also allows chained assignments.
>>> a = b = c = d = 23.4567 >>> a 23.4567 >>> b 23.4567 >>> c 23.4567 >>> d 23.4567
Also, a dictionary has to be initialized before a key access.
>>> a['k'] = {} Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'a' is not defined
So what the following example teaches us is how Python goes about allocating things in a chained assignment.
>>> a = a['k'] = {} >>> a {'k': {...}}
Let us look at something else with recursion which is easier for me to illustrate
>>> a = a[0] = [0, 1] >>> a [[...], 1]
>>> a = a[1] = [0, 1] >>> a [0, [...]]
The operations look like following:
[0, 1] → a
a → a[0]
If we extend the chain (a = b = c = d = ...
) what is
the pattern?
I could not think of a test that would let me dissociate if what is happening is
a = x
b = a
c = a
d = a
or
a = x
b = a
c = b
d = c
I might think of something tomorrow.
Carl M. Johnson said:
Every Python object has an infinitely recursive set of attributes because methods are functions and functions are objects and all objects have methods. It is a fun way to hit a stack overflow chasing them down.
Susam Pal said:
Carl, Yes, indeed!
None.__eq__.__eq__.__eq__.__eq__.__eq__ # and so on ...
Blind Mathematician said:
This looks like a big oops in language design. Time to upgrade to Python4 for a fix!
Knome said:
Wow. That is surprising. It makes sense but strikes me as the exact opposite of what I would expect looking at the code. I would definitely have expected the binding order to be the more common right-to-left and for the original code to generate a name error, rather than the latter.
Knome said:
a = a[len(a)] = a[len(a)] = a[len(a)] = a[len(a)] = a[len(a)] = a[len(a)] = {}
This is knowledge that should have remained hidden.
Ayra said:
aaaaaaaaaaaaaaaa
Shellac said:
Knome: ... It makes sense ...
Knome,
I think you are overstating that. It is not outright crazy or inconsistent, but I quibble with it being sensible.
Piu Tevon said:
The thing I learned today is that multiple assignments are evaluated
left-to-right. I assumed it would be the other way around.
Like a[0] = a = [0]
would have been a lot less
surprising.
Anitil said:
I am amazed at how long I have been working in Python and never known that assignment works in this way. I suppose I just avoid parts of the language that I do not understand.
Looking through the archives, it is a really interesting blog in general.
Kache said:
I think it would be elegant to let assignments be expressions, and from that simple core rule, your parsing naturally follows.
I find it irksome that Python makes a distinction between statements and expressions, instead.
Oliver said:
It is a fallout of the fact that assignment must be a statement, and cannot be an expression. For example, this is wrong in Python, while it would be allowed in C-like languages:
print(a = 5)
Tom Forbes said:
Oliver, It is possible now with the walrus operator though:
print(a := 5)
Heavyset Go said:
Some languages wear the things that make you go WTF on their sleeves. In contrast, Python's surprises pop up slowly over time, and in places where you'd least expect them.
Solar Fields said:
Heavyset, I wonder if it's because of their subtlety. I assume you are referencing JavaScript, where the warts are impossible to ignore because you can't write an everyday program as a beginner without running into them. This sort of thing from the article is just not something I would typically be trying to do in the normal course of shuffling data around.
Malf said:
This is the first true "PHPism" I have seen in a "real language". (Is there a name for "we did not want the general thing (e.g., assignment expressions) but we wanted one narrow subset of it (serial assignment), so we implemented that, but with different semantics for no good reason"?)
XG15 said:
This is an interesting example that the internal models of two programming languages can be quite different, even if they ostensibly support the same features.
a = a['k'] = {}
is also valid JavaScript syntax, however running it throws an exception and generally works as OP probably expected the statement to work. The reason is that in JS, the statement is parsed aswhere
(a['k'] = {})
is executed first and also returns the assigned value. Meanwhile in Python, apparently multi-assignment is a grammar production of its own, so the above would be parsed as sort of:and therefore first assign to
a
, then toa['k']
.