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 as

    a = (a['k'] = {})

where `(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:

    multi-assign(a, a['k']) = {}

and therefore first assign to `a`, then to `a['k']`.

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.

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.

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()

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.

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.

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.

Carl, Yes, indeed!

    None.__eq__.__eq__.__eq__.__eq__.__eq__ # and so on ...

This looks like a big oops in language design. Time to upgrade to Python4 for a fix!

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.

    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.

aaaaaaaaaaaaaaaa

> 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.

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.

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.

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.

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)

Oliver, It is possible now with the walrus operator though:

    print(a := 5)

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.

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.

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"?)