3

I've come across some very odd handling of global variables in Python. I was hoping someone can explain and justify these surprises!

A) This code prints 10 as expected:

def func():
  print(a)
a = 10
func()

B) This code throws an exception about referencing a too early:

def func():
  print(a)
  a += 1
a = 10
func()

C) But this code prints [10] as expected:

def func():
  print(a)
  a.append(1)
a = [10]
func()

So I can gather that the type of a changes its scope and additionally later statements that haven't even been reached yet change how a is seen. I know I can use global a at the start of the function but it's rather verbose.

Can anyone tell me what rules Python is using to handle its bizarre scoping?

4
  • 1
    It's not bizarre, Python promotes good practices! If you feel the need to declare globals you should probably reconsider your design. Functions are nicer when they accept parameters to mutate or even better(usually), return the new version without mutating the original. In your last example you are not even binding the name a to a different value as @Ignacio said. You are simply accessing a method of a so that shouldn't be a surprise. Commented Jul 25, 2012 at 9:05
  • No I assure you my design is fine. I have one global that represents precision in a single-threaded app. Passing the precision value between functions is just a waste. The issue only arises when working precision increases it slightly. Commented Jul 25, 2012 at 9:10
  • Also I can't see how a later, never executed statement should change the meaning of preceding lines. How can that not be bizarre? I don't know of any other cases of this in Python. Commented Jul 25, 2012 at 9:15
  • 1
    You might also argue that it's weird a syntax error at the end of the file will break the script before it even begins. They are both means to prevent errors: Having a change the meaning between a variable of a parent scope and local scope is quite confusing I think and not very consistent. It's either a local variable, or not. Never both. Commented Jul 25, 2012 at 9:47

2 Answers 2

5

The second instance rebinds a, so the compiler generates a local access for it. The other two only read a, and so normal global scope searching is performed.

Sign up to request clarification or add additional context in comments.

3 Comments

And you'll get the behaviour you probably expected by putting global a first in func B .
Is there a way of always referencing globals first if present?
Not without modifying the compiler.
4

Basically, there are two rules:

  1. When you only read from a variable (inside a scope), Python will travel up the scope chain until it finds a variable of that name.
  2. When you write to a variable at least once, Python will always create the variable in the current scope.

You can change the behavior of #2, however:

  • If you want a name to refer to a module-level variable, you can use global my_module_variable. When you now write to my_module_variable, Python will not create a local variable.
  • As of Python 3, you can also have a name refer to a variable in an enclosing scope: use nonlocal my_non_local_variable to have it refer to the variable in the nearest enclosing scope.

Problems in your code

B) You are using +=: You are trying to write to the variable. So rule number 2 is in effect, it will write to a variable in the current scope. However, it also has to read from it (print(a)), but the variable does not yet have a value, because you haven't written to it before. Python does not allow you to mix rule 1. and rule 2. in a function.

If you wanted func() to work on the a = 10 variable, you could change your code like this:

>>>> def func()
        global a
        print(a)
        a += 1
>>>> a = 10
>>>> func()
10
>>>> func()
11

3 Comments

So that means that the python interpreter, before deciding if a variable within a function is going to be accessed using rule 1 or 2, needs to parse the whole function body to decide which one applies? In a function which could be (in principle) thousands of lines long? That would mean that the first pass of the interpreter (to generate the bytecodes) would be quite slow, doesn't it?
@gonvaled Python always parses the entire file.py and generates byte code. And yes, you trade a little startup time for that. However, the second time you execute the script, the bytecode will aready be there.
Ok, I had the impression that the parser was generating bytecode at the same time that this was being executed (similar to what the shells do - without bytecodes of course)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.