2

In Python, the following code works:

a = 1
b = 2

def test():
    print a, b

test()

And the following code works:

a = 1
b = 2

def test():
    if a == 1:
        b = 3
    print a, b

test()

But the following does not work:

a = 1
b = 2

def test():
    if a == 1:
        a = 3
    print a, b

test()

The result of this last block is an UnboundLocalError message, saying a is referenced before assignment.

I understand I can make the last block work if I add global a in the test() definition, so it knows which a I am talking about.

Why do I not get an error when assigning a new value to b?

Am I creating a local b variable, and it doesn't yell at me because I'm not trying to reference it before assignment?

But if that's the case, why can I print a, b in the case of the first block, without having to declare global a, b beforehand?

ryantuck
  • 6,146
  • 10
  • 57
  • 71
  • You don't use b before you reference it where using a before you a = means you are trying to use a local variable that you have not yet defined. – Padraic Cunningham Apr 21 '15 at 18:30
  • not exactly the same but related http://stackoverflow.com/questions/29639780/unboundlocalerror-local-variable-referenced-before-assignment-in-python-closure/29639807#29639807 – Padraic Cunningham Apr 21 '15 at 18:33

3 Answers3

6

Let me give you the link to the docs where it is clearly mentioned.

If a variable is assigned a new value anywhere within the function’s body, it’s assumed to be a local.

(emphasis mine)

Thus your variable a is a local variable and not global. This is because you have a assignment statement,

a = 3

In the third line of your code. This makes a a local variable. And referring to the local variable before declaration causes an error which is an UnboundLocalError.

However in your 2nd code block, you do not make any such assignment statements and hence you do not get any such error.

Another use ful link is this

Raised when a reference is made to a local variable in a function or method, but no value has been bound to that variable.

Thus you are referring to the local variable you create in the next line.

To prevent this there are two ways

  • Good way - Passing parameters

    Define your function as def test(a): and call it as test(a)

  • Bad way - Using global

    Have a line global a at the top of your function call.

Python scoping rules are a little tricky! You need to master them to get hold of the language. Check out this

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
  • great answer. i figured the compiler would go line-by-line, but it seems to check to see if I'm assigning a value anywhere in the function before determining how to treat the `a` in the conditional statement. I agree that avoiding using `globals` is a good idea. – ryantuck Apr 21 '15 at 19:00
2

When you modify a, it becomes a local variable. When you're simply referencing it, it is a global. You haven't defined a in the local scope, so you can't modify it.

If you want to modify a global, you need to call it global in your local scope.

Take a look at the bytecode for the following

import dis

a = 9 # Global

def foo():
    print a # Still global

def bar():
    a += 1 # This "a" is local


dis.dis(foo)

Output:

  2           0 LOAD_GLOBAL              0 (a)
              3 PRINT_ITEM
              4 PRINT_NEWLINE
              5 LOAD_CONST               0 (None)
              8 RETURN_VALUE

For the second function:

dis.dis(bar)

Output:

  2           0 LOAD_FAST                0 (a)
              3 LOAD_CONST               1 (1)
              6 INPLACE_ADD
              7 STORE_FAST               0 (a)
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE

The first function's bytecode loads the global a (LOAD_GLOBAL) because it is only being referenced. The second function's bytecode (LOAD_FAST) tries to load a local a but one hasn't been defined.

The only reason your second function works is because a is equal to 1. If a was anything but 1, the local assignment to b wouldn't happen and you'd receive the same error.

That1Guy
  • 7,075
  • 4
  • 47
  • 59
  • 2
    wow, that's neat. hadn't thought about whether causing the `if` statement to fail would affect `b`, but you were right. – ryantuck Apr 21 '15 at 18:56
2

In the third block the compiler has marked a as a local variable since it is being assigned to, therefore when it is used in the expression it is looked for in the local scope. Since it does not exist there, an exception is raised.

In the second block the compiler has marked b as a local variable but not a, hence there is no exception when a is accessed since outer scopes will be searched.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358