Chained Assignment in Python Bytecode

(loriculus.org)

20 points | by wenderen 27 days ago

6 comments

  • jp57 23 days ago
    The author's expectations seem strange. Take another example:

        a = b = random.random()
    
    I would not expect a and b to get different values. It would be very strange if using `[]` had different behavior than a function call in the same place. Am I out of step here?
    • maxnoe 23 days ago
      But this exact example is different, because the critical step of mutation is missing.

      The initial line is the same, but:

          a = b = random.random()
          a += 1
          a == b # False
      
      Only because floats are immutable and thus an implicit copy is made and lists are mutable so the same mutable instance is pointed to by both names.

      This talk still applies despite its age: https://youtu.be/_AEJHKGk9ns?si=q5HjMOM9QS3_bFzH

      • jp57 22 days ago
        But the semantics of python is that variables contain references to lists and lists are mutable while constants are not.
        • jibal 21 days ago
          You seem to have forgotten the point of your comment, which was to offer an analogous case where surely one wouldn't expect two different values ... but the fact that lists have reference semantics and numbers (not just "constants") have value semantics (that's the issue, not mutability--variables containing numbers are mutable in Python--they don't contain a reference to an unmutable number ... numbers are always copied into the cell) makes all the difference in the world. If one actually does analogize from your example, then one might well expect changing a to not change b since it doesn't in your example ... they might not realize that lists have reference semantics and thus think that a = b = [] copies an empty list to a and copies an empty list to b. That's how it works in Perl with `@a = @b = ();` ... changing @a does not change @b.

          On top of all that, the OP made clear in the first paragraph that they didn't have the expectation that you ascribed to them ... they did understand that [] has reference semantics and changing a changes b all along ... but they apparently occasionally have a lapse in their mental model where they forget that (or perhaps they introduced the bug by naively/blindly converting some Perl code ... I've seen that happen.)

          I won't offer corrections of additional errors or respond further in any other way.

    • jibal 23 days ago
      What expectations? The author states right up front "I've known of this behavior for a long time".

      A somewhat trickier example of the same issue is using [] as a default parameter value ... though there are warnings about the problem with that (it's the same list on every call) throughout the documentation.

  • KerrickStaley 23 days ago

      a = b = []
    
    has the same semantics here as

      b = []
      a = b
    
    which I don't find surprising.
  • Twirrim 23 days ago
    I've seen stuff posted about chained assignment footguns in python regularly over the years, and it always surprises me. I don't think I've ever written them, or reviewed code that does. I don't think it'd occur to me to even think about writing a chained assignment.

    Is chained assignment a pattern that comes from another language that people are applying to python?

  • selridge 23 days ago
    “Since Python 3.6, every Python instruction has been given an odd number of arguments (even if it doesn't need any) so that the byte offsets are always even.”

    I don’t think this is true. I think something like it is true for CPython (an argument isn’t added but the byte is kept for functions w no arg) but it’s not a statement about Python.

  • kccqzy 23 days ago
    Chained assignments are banned according to the style guide at my workplace. Too many opportunities for misuse. And if you insist on a one-liner assignment to two variables just use two statements separated by the semicolon. I challenge anyone to work out what this code does:

        a, b = b[a] = 1, [0, 1, 2, 3]
    • PurpleRamen 23 days ago
      > I challenge anyone to work out what this code does

      It's unusual, but pretty obvious. In single steps it's basically this:

          a, b = 1, [0, 1, 2, 3]
          b[a] = b 
      
      which would be b[1]=b because a==1. So this creates a self referencing list. The deeper reason here is, everything is a pointer to data in memory, even if in source code we see the actual data. That's why b[1] is storing the pointer to the list, not the data of the list.

      If someone is doing BS like this, they deserve spanking. But banning the whole concept because people are unaware or how something is to be used properly is strange. But then again, it seems people have a bit of a problem how python really works and how it's different from other languages.

      • js2 23 days ago
        Basically, but not quite. :-) The original result for b is:

          0, (1, [...]), 2, 3]
        
        vs your version:

          [0, [...], 2, 3]
        
        The equivalent statements to the original chained assignment are:

          a, b = 1, [0, 1, 2, 3]
          b[a] = 1, b  # relies on cpython implementation detail¹
        
        
        ¹: Using 1 works because the integers -5 thru 256 are interned in cpython. Otherwise, or if you don't want to rely on an implementation detail, to be truly equivalent (i.e. `id(b[1][0]) == id(a)`), it's this:

          a, b = 1, [0, 1, 2, 3]
          b[a] = a, b
      • kccqzy 23 days ago
        An adjacent thread has some confusion about whether chained assignments happen left to right or right to left. Honestly that’s a factoid I don’t expect most Python programmers to know. It’s usually a bad idea to rely on people knowing arcade details of a language, especially a language like Python that has attracted many non-programmers like data scientists. (I have nothing against data scientists but their brainpower shouldn’t be wasted on remembering these kind of details.)
        • js2 23 days ago
          I've been programming Python since 1.5.2 days and indeed, I didn't know the order of evaluation of chained assignments.

          That said, it's the self-referencing list in your example that's the more confusing part. It's atypical to have self-referencing data structures, so that's something I'd comment in the design if I needed one.

  • mathisfun123 23 days ago
    > list object is constructed once and assigned to both variables

    Ummm no the list is constructed once and assigned to b and then b is assigned to a. It would be crazy semantics if `a = b = ...` meant `a` was assigned `...`.

    Edit: I'm wrong it's left to right not right to left, which makes the complaint in the article even dumber.

    • kccqzy 23 days ago
      It’s assigned left to right, not right to left. It’s documented in the Python language reference.

      > An assignment statement evaluates the expression list and assigns the single resulting object to each of the target lists, from left to right.

      Consider this:

          a = [1, 2]
          i = a[i] = 1
      
      If assignment were to happen right to left, you would get a NameError exception because the first assignment would require an unbound variable.
      • mathisfun123 23 days ago
        Fine but that even moreso illustrates how goofy the expectation that the "ctor" for [] would be called twice.
    • ayhanfuat 23 days ago
      > then b is assigned to a

      Wouldn't that require a LOAD_FAST? Also a is assigned first (from left to right) so a = ... happens either way.