1

I wrote a code similar to the following and it gives me the local variable 'a' referenced before assignment error. When I changed that a += [2] into a.append(2), it worked.

def f():
   a = [1]
   def f1():
      a += [2] # => no error with a.append(2)
   f1()
   print(a)

Why? Why the parser can't recognize the outside a with +=?

moon
  • 531
  • 1
  • 5
  • 24
  • 1
    Because it is an *assignment*, and *all assignments to a variable mark that variable as local by the compiler* – juanpa.arrivillaga Jan 13 '22 at 17:43
  • @Matthias no, `+=` modifies the object in-place, `a = a + [2]` does not. – juanpa.arrivillaga Jan 13 '22 at 17:44
  • @juanpa.arrivillaga I stand corrected. – Matthias Jan 13 '22 at 17:45
  • I think Matthias is correct. Do you have evidence for your claim juanpa? – Mike Clark Jan 13 '22 at 17:45
  • Thank you! that makes sense, didn't know that, I tried `global a`, also didn't work, I guess that location isn't a real global. Just out of curiosity, is there a way to make it work if I want to keep the assignment and the a in the outside function? – moon Jan 13 '22 at 17:46
  • @MikeClark just try it out for yourself... it is also basic Python knowledge. Note, `a += b` strictly speaking is up to the type to define the behavior, because it is essentially a call to `a.__iadd__(b)`, but the *whole point* of these operators was to provided *in-place* operations, see: https://www.python.org/dev/peps/pep-0203/ and the built-in types respect this convention – juanpa.arrivillaga Jan 13 '22 at 17:47
  • @moon yes, it is definetly not `global`, use `nonlocal`, but really, `.append` makes the most sense here – juanpa.arrivillaga Jan 13 '22 at 17:47
  • @MikeClark Check the `id` of `a` after `a = a + [2]` and `a += [1]`. – Matthias Jan 13 '22 at 17:47
  • Of course, if a type is immutable, e.g. `int`, then `a += b` will work equivalently to `a = a + b` – juanpa.arrivillaga Jan 13 '22 at 17:49
  • @Matthias `+=` for lists is documented [here](https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types). – chepner Jan 13 '22 at 17:50
  • I couldn't think of how to tell whether it was inplace or a copy. Checking the `id` seems to be the way to do that. Thanks for the info! – Mike Clark Jan 13 '22 at 17:50
  • @MikeClark alternatively, `x = []; y = x; x += [a]; print(x, y)` – juanpa.arrivillaga Jan 13 '22 at 17:51
  • It seems `a += b` when `b` is not iterable is equivalent to `a += [b]`. This I didn't know. I wonder if the first form is slightly faster due to no temporary 1-element array? @juanpa.arrivillaga – Mike Clark Jan 13 '22 at 17:56
  • @MikeClark mmm no, if `a` is a `list` and `b` is not iterable, that would throw a `TypeError` – juanpa.arrivillaga Jan 13 '22 at 17:57
  • @juanpa.arrivillaga `a = ['a']; a += 'b'; print(a)` try it! – Mike Clark Jan 13 '22 at 17:58
  • @MikeClark `"b"` is iterable. Try that with `"abc"` to see what's going on, it is equivalent only in the case of length-1 strings. Now try it with something that is actually not itereable, e.g. a `float` – juanpa.arrivillaga Jan 13 '22 at 17:59
  • @juanpa.arrivillaga Oh, I'm dumb. I forgot that strings are iterable. – Mike Clark Jan 13 '22 at 17:59

1 Answers1

4

It's an assignment to a. It's basically syntactic sugar for

a = a.__iadd__([2])

The assignment makes a a local variable when the code is generated, but then the RHS of the assignment tries to access that variable before at runtime it is defined.

a.append(2), on the other hand, is not an assignment. a is a free variable whose value is taken from the closest enclosing scope.

If you want to assign to a non-local variable, you need to declare the name as non-local first.

def f():
   a = [1]
   def f1():
      nonlocal a
      a += [2]
   f1()
   print(a)
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Thank you @chepner! by "enclosing scope", does it just refer to a function/class/global? is there some special meaning about "enclosing"? – moon Jan 13 '22 at 19:47
  • 1
    Nothing really special. `f1` is defined in the body of `f`, so we say that `f`'s scope encloses `f1`'s. `f` is defined in the global scope, so we say that the global scope encloses `f`'s. (We could stretch and say that the global scope encloses `f1`'s scope, too, but `f`'s is the *closest* enclosing scope to `f1`'s.) The built-in scope encloses all global scopes (which are only global to its module, not the entire process.) – chepner Jan 13 '22 at 19:57