Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python += versus .extend() inside a function on a global variable

I've read a few other SO (PythonScope and globals don't need global) but nothing seems to explain as explicitly as I would like and I'm having trouble mentally sifting through whether or not PyDocs tells me the answer to my question:

myList = [1]

def foo():
    myList = myList + [2, 3]
def bar():
    myList.extend([2, 3])
def baz():
    myList += [2, 3]

Now, understandably,

>>> foo()
UnboundLocalError: local variable 'myList' referenced before assignment

and

bar()  # works
myList # shows [1, 2, 3]

but then

>>> baz()
UnboundLocalError: local variable 'myList' referenced before assignment

I thought, however, that things like += implicitly called the method operators, in this case extend(), but the error implies that for some reason it does not actually treat += as extends(). Is this consistent with how Python parsing ought to work?

I would have thought that calling functions that are equivalent to method-operators, they would be equivalent in all cases. Instead it seems that it treats += as an actual assignment operator. Except, this isn't completely true, because if I do something (admittedly contrived):

myList = range(50000000) # wait a second or two on my laptop before returning
myList += [0]            # returns instantly
myList = myList + [1]    # wait a second or two before returning

all of which is expected, if += actually just calls extend().

Is there some finer distinction (or very obvious point...) that I'm missing that makes it clear that myList in baz() needs to be treated as a local variable, and that therefore the += cannot be implicitly converted to an extend() such that it recognizes the global variable?

like image 502
dwanderson Avatar asked Apr 21 '13 03:04

dwanderson


People also ask

How do you modify a global variable in a function Python?

Use of “global†keyword to modify global variable inside a function. If your function has a local variable with same name as global variable and you want to modify the global variable inside function then use 'global' keyword before the variable name at start of function i.e.

How can you access a global variable inside the function if function has a variable with same name?

To access a global variable in a function, if the function has a local variable with the same name, we use the global keyword before the variable name.

How do you set a global variable inside a function?

The global Keyword Normally, when you create a variable inside a function, that variable is local, and can only be used inside that function. To create a global variable inside a function, you can use the global keyword.


1 Answers

+= doesn't implicitly call extend(). Firstly, it is an augmented assignment operator.

If you look at the section on assignment it says:

Assignment of an object to a single target is recursively defined as follows.

If the target is an identifier (name):

If the name does not occur in a global statement in the current code block: the name is bound to the object in the current local namespace. Otherwise: the name is bound to the object in the current global namespace.

Since an augmented assignment is:

Augmented assignment is the combination, in a single statement, of a binary operation and an assignment statement:

It plays by the same rules. As you can see:

>>> def baz():
        myList += [2, 3]


>>> dis.dis(baz)
  2           0 LOAD_FAST                0 (myList)
              3 LOAD_CONST               1 (2)
              6 LOAD_CONST               2 (3)
              9 BUILD_LIST               2
             12 INPLACE_ADD         
             13 STORE_FAST               0 (myList)
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE  

An augmented assignment evaluates the target (which, unlike normal assignment statements, cannot be an unpacking) and the expression list, performs the binary operation specific to the type of assignment on the two operands, and assigns the result to the original target. The target is only evaluated once..

The first call trys to evaluate myList, this results in LOAD_FAST since there was no global statement it is assumed to be a local variable:

LOAD_FAST(var_num)

Pushes a reference to the local co_varnames[var_num] onto the stack.

It can't be found so the error is raised. If it was found, then we get to the oppcode INPLACE_ADD which calls the method myList.__iadd__ which does the job of extend, once this operation completes the result will be assigned back to the variable but we never get this far.

You shouldn't really be manipulating globals anyway, return the new result from your function or pass it as a parameter.

like image 83
jamylak Avatar answered Nov 01 '22 15:11

jamylak