pythonglobal-variables

Python global variable changes depending on how script is run


I have a short example python script that I'm calling glbltest.py:

a = []

def fun():
    global a
    a = [20,30,40]

print("before ",a)
fun()
print("after ",a)

If I run it from the command line, I get what I expect:

$ python glbltest.py
before []
after [20, 30, 40]

I open a python shell and run it by importing, and I get basically the same thing:

>>> from glbltest import *
before []
after [20, 30, 4]

So far so good. Now I comment out those last three lines and do everything "by hand":

>>> from glbltest import *
>>> a
[]
>>> fun() # I run fun() myself
>>> a # I look at a again.  Surely I will get the same result as before!
[]    # No!  I don't!

What is the difference between fun() being run "automatically" by the importing of the script, and me running fun() "by hand"?


Solution

  • global a refers to the name a in the glbltest module's namespace. When you set a by hand, it refers to the name a in the __main__ module's namespace.

    When you use from glbltest import * the names in the module are imported into the __main__ module's namespace. Those are different names but refer to the same objects. When you use global a and a = [20,30,40] in the glbltest module, assignment makes a new object that a in glbltest module's namespace now refers to. The name a in the __main__ module still refers to the original object (the empty list).

    As a simple example, print the id() of a in the fun() function, and print(id(a)) "by hand" after you set it:

    a = []
    
    def fun():
        global a
        print(a, id(a))
        a = [20,30,40]
        print(a, id(a))
    
    # To view the global a object id again
    def show():
        print(a, id(a))
    

    "by hand", with comments:

    >>> from glbltest import *
    >>> a                        # the imported name
    []
    >>> id(a)                    # its object ID
    2056911113280
    >>> fun()
    [] 2056911113280             # start of fun() the object is the same ID
    [20, 30, 40] 2056902829312   # but assignment changes to new object (different ID)
    >>> a
    []                           # main a still refers to original object
    >>> id(a)
    2056911113280
    >>> show()                   # glbltest module still sees *its* global a
    [20, 30, 40] 2056902829312
    

    Note that if you use mutation vs. assignment to change the existing object. You'll see the change:

    a = []
    
    def fun():
        global a
        print(a, id(a))
        a.extend([20,30,40])  # modify existing object, not assigning a new object.
        print(a, id(a))
    
    # To view the global a object id again
    def show():
        print(a, id(a))
    

    Now the object IDs remain the same.

    >>> from glbltest import *
    >>> a, id(a)                 # import object
    ([], 1408887112064)
    >>> fun()                    
    [] 1408887112064             # before change still the same object
    [20, 30, 40] 1408887112064   # mutated the *existing* list
    >>> a, id(a)
    ([20, 30, 40], 1408887112064)  # main's 'a' refers to the same object, same ID
    >>> show()
    [20, 30, 40] 1408887112064     # glbltest refers to the same object, same ID
    

    It's a bit more obvious that the names are different if you just import the module and the module's a can be referred to directly as glbltest.a.

    a = []
    
    def fun():
        global a
        a = [20,30,40]
    
    >>> import glbltest
    >>> glbltest.a
    []
    >>> a = 5   # main's a
    >>> a
    5
    >>> glbltest.a   # module's a
    []
    >>> glbltest.fun()
    >>> a           # main's a doesn't change
    5
    >>> glbltest.a  # module's a does.
    [20, 30, 40]