2

I have the following code: one.py

import two as t 

t.test1()

t.test2()

two.py

class test_class():
    def __init__(self,arg):
        print("created obj name is {}".format(arg))
        self.name = arg

non_global ="initial global"

obj = test_class("test name")



def test1():
    print("\t in test 1")
    print (obj.name)
    global non_global # wont work without global keyword, value still refers to "initial value"
    print(non_global) # initial value
    obj.name = "changed"
    #non_global = "changed global"


def test2():
    print("\tin test 2")
    print(obj.name)
    print(non_global)

The result is:

created obj name is test name
         in test 1
test name
initial global
        in test 2
changed
changed global

if I change in test1() to:

def test1():
    print("\t in test 1")
    print (obj.name)
    #global non_global # wont work without global keyword, value still refers to "initial value"
    print(non_global) # initial value
    obj.name = "changed"
    non_global = "changed global"

I get the error UnboundLocalError: local variable 'non_global' referenced before assignment on the print line.

If I comment non_global = "changed global" the error goes away.

my questions is:

Why is this happening for non_global and not for obj? I am on python 3.5

Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
PYA
  • 8,096
  • 3
  • 21
  • 38
  • Why did you delete your previous question and then re-ask it? https://stackoverflow.com/questions/45357636/why-no-need-for-global-for-class-object-when-importing – AndyG Jul 27 '17 at 17:55
  • An assignment to `obj.name` isn't an assignment to `obj`. Only an assignment to `obj` would be grounds for the compiler to generate an `obj` local variable. – user2357112 Jul 27 '17 at 17:58
  • 1
    @AndyG I didnt think the format of that was good enough, was too hasty in asking that question. – PYA Jul 27 '17 at 18:13

1 Answers1

4

The difference is basically that non_global is a variable assignment and obj.name is an attribute assignment.

A variable assignment within a function makes that variable a local variable and due to that Python won't look it up anywhere other than local scope, hence print(non_global) fails because it wasn't defined yet in that scope. This works with global statement because by using global you're telling Python to not consider it a local variable and hence its value can be fetched from global scope.

The decision of whether a variable is going to a local is taken when a function body is parsed, hence when to try to use it before the actual declaration you will get an error at runtime.

obj.name on the other hand basically searches for obj using simple LEGB lookup and then sets name attribute with the specified value.

Similarly you can also update global mutable datastructues(list, dict etc) within a function body without using global.

Apart from =, augmented assignments like *=, += also make a variable a local variable.

a = []

def func1():
    a += ['foo']

def func2():
    a.extend(['bar'])

def func3():
    a[0] += 'spam'


func1()  # Fails with UnboundLocalError: local variable 'a' referenced before assignment

func2()  # works fine

func3()  # Also works fine because a[0] is not a variable. 
         # We are telling Python to look for `a` and fetch its 0th item
         # and concatenate 'spam' to it.

print(a) # prints ['barspam']

The bytecode disassemble can also help you in pointing out the differences:

>>> import dis
>>> dis.dis(func1)
 45           0 LOAD_FAST                0 (a)
              3 LOAD_CONST               1 ('foo')  # Load a local variable name foo
              6 BUILD_LIST               1
              9 INPLACE_ADD
             10 STORE_FAST               0 (a)      # Store local variable named foo
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE
>>> dis.dis(func2)
 48           0 LOAD_GLOBAL              0 (a)      # Load from global scope
              3 LOAD_ATTR                1 (extend)
              6 LOAD_CONST               1 ('bar')
              9 BUILD_LIST               1
             12 CALL_FUNCTION            1
             15 POP_TOP
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE
>>> dis.dis(func3)
 51           0 LOAD_GLOBAL              0 (a)      # Load from global scope
              3 LOAD_CONST               1 (0)
              6 DUP_TOPX                 2
              9 BINARY_SUBSCR
             10 LOAD_CONST               2 ('spam')
             13 INPLACE_ADD
             14 ROT_THREE
             15 STORE_SUBSCR
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

# Names of local variables or arguments of each function obtained through its code object
>>> func1.__code__.co_varnames
('a',)
>>> func2.__code__.co_varnames
()
>>> func3.__code__.co_varnames
()

Related: Why am I getting an UnboundLocalError when the variable has a value?

Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
  • if the last line of `test1()` is `non_global = "changed global"` i get the error. I dont understand why a line after the print statement causes an issue. If that line is commented everything is good. A line after the print statement is as good as a comment anyway right? – PYA Jul 27 '17 at 18:26
  • @pyjg Check paragraph 3. – Ashwini Chaudhary Jul 27 '17 at 18:33
  • 1
    Thank you, so the function body is parsed and then executed line by line? I dont quite understand the flow – PYA Jul 27 '17 at 18:40
  • @pyjg Parsing of a module or a function results in a bytecode and that byte code contains instructions to be used at runtime. The instruction under `func1` are to load `a` from local scope(`LOAD_FAST`) but that variable doesn't exists that time and hence we get `UnboundLocalError `. – Ashwini Chaudhary Jul 27 '17 at 18:46
  • looking at the link you provided and its making more sense now. Thank you! – PYA Jul 27 '17 at 18:53