3

On page 551 of the 5th edition, there is the following file, named thismod.py:

var = 99

def local():
    var = 0

def glob1():
    global var
    var+=1

def glob2():
    var = 0
    import thismod
    thismod.var+=1

def glob3():
    var = 0
    import sys
    glob = sys.modules['thismod']
    glob.var+=1

def test():
    print(var)
    local(); glob1(); glob2(); glob3()
    print(var)

After which the test is run in the terminal as follows:

>>> import thismod
>>> thismod.test()
99
102
>>> thismod.var
102

The use of the local() function makes perfect sense, as python makes a variable var in the local scope of the function local(). However I am lost when it comes to any uses of the global variables.

If I make a function as follows:

def nonglobal():
    var+=1

I get an UnboundLocalError when running this function in the terminal. My current understanding is that the function would run, and first search the local scope of thismod.nonglobal, then, being unable to find an assignment to var in nonglobal(), would then search the global scope of the thismod.py file, wherein it would find thismod.var with the value of 99, but it does not. Running thismod.var immediately after in the terminal, however, yields the value as expected. Thus I clearly haven't understood what has happened with the global var declaration in glob1().

I had expected the above to happen also for the var = 0 line in glob2(), but instead this acts only as a local variable (to glob2()?), despite having had the code for glob1() run prior to the assignment. I had also thought that the import thismod line in glob2() would reset var to 99, but this is another thing that caught me off guard; if I comment out this line, the next line fails.

In short I haven't a clue what's going on with the global scope/imports here, despite having read this chapter of the book. I felt like I understood the discussion on globals and scope, but this example has shown me I do not. I appreciate any explanation and/or further reading that could help me sort this out.

Thanks.

Chris
  • 33
  • 5
  • 1
    Does this answer your question? [Don't understand why UnboundLocalError occurs](https://stackoverflow.com/questions/9264763/dont-understand-why-unboundlocalerror-occurs) – metatoaster Jan 03 '20 at 01:04
  • No, any assignments to a variable *anywhere* inside function marks that function as local at compile time. There's tons of questions about this on StackOverflow. To assign to a global variable in a function (which you probably **shouldn't**) then you must use the `global` keyword. Why did you expect `glob1` to matter? A `global` statement only affects the function it is in. – juanpa.arrivillaga Jan 03 '20 at 01:04
  • As for the import, it won't reset anything. Imports are cached so that they only run once. Importing the module you are in is strange, but I guess the pedagogical point is that global variables act as attributes to the module object. – juanpa.arrivillaga Jan 03 '20 at 01:08
  • @metatoaster, that answers my confusion with regards to the nonglobal() function. I have been using lists that are declared in the top level of a file and modifying them without local assignment, so this probably set up my confusion there. The list being mutable meant I never came across this problem, but with immutable integers, as juanpa.arrivillaga says, it seems to assign to a new name regardless. I had assumed a global declaration would persist throughout runtime; I guess it only persists during the execution of the function in which the declaration is found? If so, that helps. Thanks – Chris Jan 03 '20 at 01:31
  • @juanpa.arrivillaga, I have read about the imports being cached already, but I didn't understand how it updates the value thismod.var if it is supposedly local to glob2(), since there is no global declaration? Would I be correct in assuming the import is an equivalent of the global declaration, except for an entire namespace, as opposed to a single variable name, and thus it effectively brings the thismod that we imported in the terminal into the scope of glob2? – Chris Jan 03 '20 at 01:31
  • @Chris `thismod.var` **is not local** `glob2`, it is referencing the *global* `var` through the module object itself (admittedly, this is a pretty weird situation, not a normal one). However, `var = 0` in `glob2` is merely assigning to a local variable, which is why `thismod.var` is never reset back to 0. So no, an import is not equivalent to the global declaration. It simply assigns the module object to a local variable, and you can access the global variables of that module as attributes on it, but it doesn't affect the *scope* of the `var` variable inside that function at all. – juanpa.arrivillaga Jan 03 '20 at 01:33
  • @Chris note, **mutable and immutable is irrelevant as far as scoping rules are concerned**. Assignment to variables that are referencing mutable objects works exactly the same as variables referencing immutable objects. – juanpa.arrivillaga Jan 03 '20 at 01:36
  • @Chris so yes, the `global` keyword, i.e. `global x` is **only** ever useful inside a function, and it states, essentially, "this variables is global, whether assigned to or referenced, in this function" – juanpa.arrivillaga Jan 03 '20 at 01:44
  • @juanpa.arrivillaga I made a new function, `def glob_import(): import thismod; return thismod`, then ran `>>> import thismod; thismod.glob_import() is thismod`, which returned True. After reading your comment about it referencing the global var through the module object, this helped clear things up for me. A weird situation, indeed. Thanks a lot. – Chris Jan 03 '20 at 01:55

1 Answers1

2

Unless imported with the global keyword, variables in the global scope can only be used in a read-only capacity in any local function. Trying to write to them will produce an error.

Creating a local variable with the same name as a global variable, using the assignment operator =, will "shadow" the global variable (i.e. make the global variable unaccessible in favor of the local variable, with no other connection between them).

The arithmetic assignment operators (+=, -=, /=, etc.) play by weird rules as far as this scope is concerned. On one hand you're assigning to a variable, but on the other hand they're mutative, and global variables are read-only. Thus you get an error, unless you bring the global variable into local scope by using global first.

Admittedly, python has weird rules for this. Using global variables for read-only purposes is okay in general (you don't have to import them as global to use their value), except for when you shadow that variable at any point within the function, even if that point is after the point where you would be using its value. This probably has to do with how the function defines itself, when the def statement is executed:

var = 10

def a():
    var2 = var
    print(var2)

def b():        
    var2 = var  # raises an error on this line, not the next one
    var = var2 
    print(var)

a()  # no errors, prints 10 as expected
b()  # UnboundLocalError: local variable 'var' referenced before assignment

Long story short, you can use global variables in a read-only capacity all you like without doing anything special. To actually modify global variables (by reassignment; modifying the properties of global variables is fine), or to use global variables in any capacity while also using local variables which have the same name, you have to use the global keyword to bring them in.


As far as glob2() goes - the name thismod is not in the namespace (i.e. scope) until you import it into the namespace. Since thismod.var is a property of what is now a local variable, and not a global read-only variable, we can write to it just fine. The read-only restriction only really applies to global variables within the same file.

glob3() does effectively the same thing as glob2, except it references sys's list of imported modules rather than using the import keyword. This is basically the same behavior that import exhibits (albeit a gross oversimplification) - in general, modules are only loaded once, and trying to import them again (from anywhere, e.g. in multiple files) will get you the same instance that was imported the first time. importlib has ways to mess with that, but that's not immediately relevant.

Green Cloak Guy
  • 23,793
  • 4
  • 33
  • 53
  • 1
    @juanpa.arrivillaga thanks for bringing that up. Did some experimentation, you're absolutely right, I modified my answer to give the actual, *tested*, conditions under which it fails. – Green Cloak Guy Jan 03 '20 at 01:32