2
foo = 'asdf'
bar = 'asdf'
foobar = 'asdf'

def test():
  def get_foo():
    print 'foo = ' + foo

  def get_bar():
    try:
      print 'bar = ' + bar
    except Exception as e:
      print 'nobody set bar :('

  def get_foobar():
    try:
      foobar
    except Exception as e:
      print e
      foobar = "nobody set foobar :("
    print foobar

  print 'trying to get foo'
  get_foo()

  print '\ntrying to get bar'
  get_bar()

  print '\ntrying to get foobar'
  get_foobar()

test()

When I run this, I get this:

trying to get foo
foo = asdf

trying to get bar
bar = asdf

trying to get foobar
local variable 'foobar' referenced before assignment
nobody set foobar :(

I set up three variables: foo, bar, foobar. Then, I print them out using three nested functions: get_foo, get_bar, and get_foobar.

get_foo simply prints foo get_bar prints bar and checks for exceptions get_foobar uses a try-except to ensure that foobar is set before printing it.

Somehow, get_foobar fails?!

coda
  • 504
  • 5
  • 13

2 Answers2

2

Add the line global foobar to the top of get_foobar()

This tells the bytecode compiler you want to reference the variable in this function using global scope instead of local.

Assignment implicitly changes a variable's scope to local for the whole function scope.

In bytecode:

## code input (local)
def test():
    print foobar
    foobar = 2

## bytecode output
#
# 3           0 LOAD_FAST                0 (foobar)
#             3 PRINT_ITEM 
#             4 PRINT_NEWLINE
# 
# 4           5 LOAD_CONST               1 (2)
#             8 STORE_FAST               0 (foobar)
#            11 LOAD_CONST               0 (None)
#            14 RETURN_VALUE

## code input (global)
def test():
    global foobar
    print foobar
    foobar = 2

## bytecode output
#
# 4           0 LOAD_GLOBAL              0 (foobar)
#             3 PRINT_ITEM 
#             4 PRINT_NEWLINE 
#
# 5           5 LOAD_CONST               1 (2)
#             8 STORE_GLOBAL             0 (foobar)
#            11 LOAD_CONST               0 (None)
#            14 RETURN_VALUE

Notice the usage of LOAD_FAST and STORE_FAST instead of LOAD_GLOBAL and STORE_GLOBAL. The Python dis module documentation has a list of opcodes for reference.

You can use this code to quickly dump the bytecode for a function:

import compiler, dis
code = compiler.compile('''
def test():
    print foobar
    foobar = 2
''', '__main__', 'exec')
dis.dis(code.co_consts[1])
lunixbochs
  • 21,757
  • 2
  • 39
  • 47
0

Martijn Pieters called it. Because you made an assignment to foobar inside the function, a new, local variable foobar was created, but this happens at compile time not runtime, which is why an uninitialized version of foobar is already existing inside your try block.

Caleb Hattingh
  • 9,005
  • 2
  • 31
  • 44