Visibility of global variable declared in imported module - python

I just bumped into an unexpected (at least, for me!) behavior, and I'm trying to understand it.
Let's say I have a main file:
main.py
from my_packages.module_00 import my_function
def main():
my_function()
if __name__ == "__main__":
main()
and, in the folder "my_packages", the module module_00 containing the function definition for "my_function" and a global variable:
module_00.py
global_var = 'global variable'
def my_function():
print(f'Do I know {global_var}???')
When I run main.py, it outputs:
Do I know global variable???
And I'm trying to figure out why it's working.
I would expect the variable global_var to have a scope limited only to the module where it's defined (the answer to this question seems to confirm it).
Basically, I assumed that importing my_function by
from my_packages.module_00 import my_function
was equivalent to copy/pasting the function definition in main.py. However, it seems that...the imported function somehow keeps track of the global variables declared in the module where the function itself has been defined?
Or am I missing something?

However, it seems that...the imported function somehow keeps track of the global variables declared in the module where the function itself has been defined?
That's exactly what it is doing.
>>> from module_00 import my_func
>>> my_func.__globals__['global_var']
'global variable'
>>> module_00.global_var is my_func.__globals__['global_var']
True
>>> module_00.global_var = 3
>>> my_func.__globals__['global_var']
3
__globals__ is a reference to the global namespace of the module where my_func was defined.

Fix a couple of typos and your code works as expected. Each module in Python has its own private symbol table for the module's global variables.
import in main.py must be my_func not my_function matching the function name as defined in module_00.py.
ref in f-string in module_00.py must be {global_var} not {global variable}.
main.py
from my_packages.module_00 import my_func
^^^^^
def main():
my_func() # my_func not my_function
if __name__ == "__main__":
main()
module_00.py
global_var = 'global variable'
def my_func():
print(f'Do I know {global_var}???')
^^^^^^^^^^
Output:
Do I know global variable???
If you want to access a module's global variable then you can import the module and access the variable with the syntax: package.module_name.variable_name; e.g. my_packages.module_00.global_var

Related

I declared variable t1 outside the function but was able to use it without using global keyword but for counta and counto I have to use global why? [duplicate]

From my understanding, Python has a separate namespace for functions, so if I want to use a global variable in a function, I should probably use global.
However, I was able to access a global variable even without global:
>>> sub = ['0', '0', '0', '0']
>>> def getJoin():
... return '.'.join(sub)
...
>>> getJoin()
'0.0.0.0'
Why does this work?
See also UnboundLocalError on local variable when reassigned after first use for the error that occurs when attempting to assign to the global variable without global. See Using global variables in a function for the general question of how to use globals.
The keyword global is only useful to change or create global variables in a local context, although creating global variables is seldom considered a good solution.
def bob():
me = "locally defined" # Defined only in local context
print(me)
bob()
print(me) # Asking for a global variable
The above will give you:
locally defined
Traceback (most recent call last):
File "file.py", line 9, in <module>
print(me)
NameError: name 'me' is not defined
While if you use the global statement, the variable will become available "outside" the scope of the function, effectively becoming a global variable.
def bob():
global me
me = "locally defined" # Defined locally but declared as global
print(me)
bob()
print(me) # Asking for a global variable
So the above code will give you:
locally defined
locally defined
In addition, due to the nature of python, you could also use global to declare functions, classes or other objects in a local context. Although I would advise against it since it causes nightmares if something goes wrong or needs debugging.
While you can access global variables without the global keyword, if you want to modify them you have to use the global keyword. For example:
foo = 1
def test():
foo = 2 # new local foo
def blub():
global foo
foo = 3 # changes the value of the global foo
In your case, you're just accessing the list sub.
This is the difference between accessing the name and binding it within a scope.
If you're just looking up a variable to read its value, you've got access to global as well as local scope.
However if you assign to a variable who's name isn't in local scope, you are binding that name into this scope (and if that name also exists as a global, you'll hide that).
If you want to be able to assign to the global name, you need to tell the parser to use the global name rather than bind a new local name - which is what the 'global' keyword does.
Binding anywhere within a block causes the name everywhere in that block to become bound, which can cause some rather odd looking consequences (e.g. UnboundLocalError suddenly appearing in previously working code).
>>> a = 1
>>> def p():
print(a) # accessing global scope, no binding going on
>>> def q():
a = 3 # binding a name in local scope - hiding global
print(a)
>>> def r():
print(a) # fail - a is bound to local scope, but not assigned yet
a = 4
>>> p()
1
>>> q()
3
>>> r()
Traceback (most recent call last):
File "<pyshell#35>", line 1, in <module>
r()
File "<pyshell#32>", line 2, in r
print(a) # fail - a is bound to local scope, but not assigned yet
UnboundLocalError: local variable 'a' referenced before assignment
>>>
The other answers answer your question. Another important thing to know about names in Python is that they are either local or global on a per-scope basis.
Consider this, for example:
value = 42
def doit():
print value
value = 0
doit()
print value
You can probably guess that the value = 0 statement will be assigning to a local variable and not affect the value of the same variable declared outside the doit() function. You may be more surprised to discover that the code above won't run. The statement print value inside the function produces an UnboundLocalError.
The reason is that Python has noticed that, elsewhere in the function, you assign the name value, and also value is nowhere declared global. That makes it a local variable. But when you try to print it, the local name hasn't been defined yet. Python in this case does not fall back to looking for the name as a global variable, as some other languages do. Essentially, you cannot access a global variable if you have defined a local variable of the same name anywhere in the function.
Accessing a name and assigning a name are different. In your case, you are just accessing a name.
If you assign to a variable within a function, that variable is assumed to be local unless you declare it global. In the absence of that, it is assumed to be global.
>>> x = 1 # global
>>> def foo():
print x # accessing it, it is global
>>> foo()
1
>>> def foo():
x = 2 # local x
print x
>>> x # global x
1
>>> foo() # prints local x
2
You can access global keywords without keyword global
To be able to modify them you need to explicitly state that the keyword is global. Otherwise, the keyword will be declared in local scope.
Example:
words = [...]
def contains (word):
global words # <- not really needed
return (word in words)
def add (word):
global words # must specify that we're working with a global keyword
if word not in words:
words += [word]
This is explained well in the Python FAQ
What are the rules for local and global variables in Python?
In Python, variables that are only referenced inside a function are implicitly global. If a variable is assigned a value anywhere within the function’s body, it’s assumed to be a local unless explicitly declared as global.
Though a bit surprising at first, a moment’s consideration explains this. On one hand, requiring global for assigned variables provides a bar against unintended side-effects. On the other hand, if global was required for all global references, you’d be using global all the time. You’d have to declare as global every reference to a built-in function or to a component of an imported module. This clutter would defeat the usefulness of the global declaration for identifying side-effects.
https://docs.python.org/3/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python
Any variable declared outside of a function is assumed to be global, it's only when declaring them from inside of functions (except constructors) that you must specify that the variable be global.
global makes the variable visible to everything in the module, the modular scope, just as if you had defined it at top-level in the module itself. It's not visible outside the module, and it cannot be imported from the module until after it has been set, so don't bother, that's not what it is for.
When does global solve real problems? (Note: Checked only on Python 3.)
# Attempt #1, will fail
# We cannot import ``catbus`` here
# as that would lead to an import loop somewhere else,
# or importing ``catbus`` is so expensive that you don't want to
# do it automatically when importing this module
top_level_something_or_other = None
def foo1():
import catbus
# Now ``catbus`` is visible for anything else defined inside ``foo()``
# at *compile time*
bar() # But ``bar()`` is a call, not a definition. ``catbus``
# is invisible to it.
def bar():
# `bar()` sees what is defined in the module
# This works:
print(top_level_something_or_other)
# This doesn't work, we get an exception: NameError: name 'catbus' is not defined
catbus.run()
This can be fixed with global:
# Attempt #2, will work
# We still cannot import ``catbus`` here
# as that would lead to an import loop somewhere else,
# or importing ``catbus`` is so expensive that you don't want to
# do it automatically when importing this module
top_level_something_or_other = None
def foo2():
import catbus
global catbus # Now catbus is also visible to anything defined
# in the top-level module *at runtime*
bar()
def bar():
# `bar` sees what is defined in the module and when run what is available at run time
# This still works:
print(top_level_something_or_other)
# This also works now:
catbus.run()
This wouldn't be necessary if bar() was defined inside foo like so:
# Attempt 3, will work
# We cannot import ``catbus`` here
# as that would lead to an import loop somewhere else,
# or importing ``catbus`` is so expensive that you don't want to
# do it automatically when importing this module
top_level_something_or_other = None
def foo3():
def bar():
# ``bar()`` sees what is defined in the module *and* what is defined in ``foo()``
print(top_level_something_or_other)
catbus.run()
import catbus
# Now catbus is visible for anything else defined inside foo() at *compile time*
bar() # Which now includes bar(), so this works
By defining bar() outside of foo(), bar() can be imported into something that can import catbus directly, or mock it, like in a unit test.
global is a code smell, but sometimes what you need is exactly a dirty hack like global. Anyway, "global" is a bad name for it as there is no such thing as global scope in python, it's modules all the way down.
It means that you should not do the following:
x = 1
def myfunc():
global x
# formal parameter
def localfunction(x):
return x+1
# import statement
import os.path as x
# for loop control target
for x in range(10):
print x
# class definition
class x(object):
def __init__(self):
pass
#function definition
def x():
print "I'm bad"
Global makes the variable "Global"
def out():
global x
x = 1
print(x)
return
out()
print (x)
This makes 'x' act like a normal variable outside the function. If you took the global out then it would give an error since it cannot print a variable inside a function.
def out():
# Taking out the global will give you an error since the variable x is no longer 'global' or in other words: accessible for other commands
x = 1
print(x)
return
out()
print (x)

Can I ask an imported module to modify main.py's globals?

Obviously this doesn't work:
#module.py
def modifyglobals():
global a
a = 12
#main.py
from module import modifyglobals
modifyglobals()
print a # NameError: name 'a' is not defined
since I think it modifies module.py's globals, but not main.py's.
On the other hand, I was expecting that this would work:
#module.py
def modifyglobals(g):
g()['a'] = 12
#main.py
from module import modifyglobals
modifyglobals(g=globals) # pass a reference to **main's globals** to modifyglobals
print a
But still it gives NameError: name 'a' is not defined.
Question: Why doesn't main.py pass a reference to its own globals, such that the called function can modify main.py's globals?
Why doesn't main.py pass a reference to its own globals, such that the called function can modify main.py's globals?
Because g=globals passes the globals function, not any particular global variable dict. When globals is called, it returns the global variable dict of whatever code called globals. You still end up calling globals in module.py rather than in main.py, so you get module.py's globals.
Pass the actual global variable dict:
#module.py
def modifyglobals(g):
g['a'] = 12
#main.py
from module import modifyglobals
modifyglobals(g=globals())
print a
Of course, most of the time, having a function modify its caller's global variables directly is a bad idea. Don't actually do this.
In case you want to access module's globals, you could do something like this:
# module.py
def modifyglobals():
global a
a = 12
# main.py
import module
module.modifyglobals()
print(module.a)
In case you want to modify your main.py's global, you could do something like this instead:
# module.py
def modifyglobals(mod):
mod['a'] = 12
# main.py
import module
a = None
module.modifyglobals(globals())
print(a)
That said, I really discourage you to use neither these 2 ways at all. Personally I rarely consider the usage of module globals at all. If I decide to define module globals is either because I'll use them to be private at the module level, or as a singletons, modules's constants or maybe module's cache. There are much better ways to do data transfer between modules and the above ones shouldn't be used.

Python global variables and callbacks

I'm writing a program that involves callbacks called from another module, and which need to access a global variable.
It seems that changes assigned to the global variable are not seen in the callback function, which only sees the original assignment. I'm guessing due to the import from the other module.
What is the proper way to write this pattern?
First module:
# a.py
from b import runb
myGlobal=None
def init():
global myGlobal
myGlobal=1
def callback():
print myGlobal
def main():
init()
runb()
if __name__=='__main__':
main()
Second module:
#b.py
def runb():
from a import callback
callback()
I would expect this program to print '1', but instead it prints 'None'
EDIT:
init can only be called once (it is a simplification of a complex program)
Python imports the main module as __main__. When b.py imports a by its actual name, a new instance of the module is loaded under the name a. Each instance has its own myGlobal.
One solution is this:
#b.py
def runb():
from __main__ import callback
callback()
Another solution is to create a new main module. Import a there and make an explicit call to a.main().
If you do this, "from a import callback", then again "myGlobal=None" will be executed, making it to print "None"
The main() function is not called when you import the file as a module. __name__ == "main" is true only when a.py is executed directly.

__main__ and scoping in python

I was somehow surprised by the following behavior:
def main():
print "%s" % foo
if __name__ == "__main__":
foo = "bar"
main()
i.e. a module function has access to enclosing variables in the __main__. What's the explanation for it?
Variables in the current modules global scope are visible everywhere in the module -- this rule also holds for the __main__ module.
From Guido's tutorial:
At any time during execution, there are at least three nested scopes whose namespaces are directly accessible:
the innermost scope, which is searched first, contains the local names
the scopes of any enclosing functions, which are searched starting with the nearest
enclosing scope, contains non-local, but also non-global names
the next-to-last scope contains the current module’s global names
the outermost scope (searched last) is the namespace containing built-in names
The thing here is that:
if __name__ == "__main__":
foo = "bar"
defines a global variable named foo in that script. so any function of that module will have access to it.
The piece of code listed above is global to the module and not inside any function.
foo is a module global variable (it's not in any function). All scopes within the module can access it.
In python there's the global scope, and functions have their own scopes. So it you define foo under the name==main, it's in the global scope. Also, it's not a mistake to use a variable which hasn't been declared yet, in a function, if it will be declared by the time the function will be called.
As sinelaw pointed out, the way out this annoyance and inadvertent bug/s is to use a function. This function can be within the 'if main:' like this:
if __name__ == "__main__":
def mainlet():
foo = "bar"
mainlet()

Global variable with imports

first.py
myGlobal = "hello"
def changeGlobal():
myGlobal="bye"
second.py
from first import *
changeGlobal()
print myGlobal
The output I get is
hello
although I thought it should be
bye
Why doesn't the global variable myGlobal changes after the call to the changeGlobal() function?
Try:
def changeGlobal():
global myGlobal
myGlobal = "bye"
Actually, that doesn't work either. When you import *, you create a new local module global myGlobal that is immune to the change you intend (as long as you're not mutating the variable, see below). You can use this instead:
import nice
nice.changeGlobal()
print nice.myGlobal
Or:
myGlobal = "hello"
def changeGlobal():
global myGlobal
myGlobal="bye"
changeGlobal()
However, if your global is a mutable container, you're now holding a reference to a mutable and are able to see changes done to it:
myGlobal = ["hello"]
def changeGlobal():
myGlobal[0] = "bye"
I had once the same concern as yours and reading the following section from Norman Matloff's Quick and Painless Python Tutorial was really a good help. Here is what you need to understand (copied from Matloff's book):
Python does not truly allow global variables in the sense that C/C++ do. An imported Python module will not have direct access to the globals in the module which imports it, nor vice versa.
For instance, consider these two files, x.py,
# x.py
import y
def f():
global x
x = 6
def main():
global x
x = 3
f()
y.g()
if __name__ == ’__main__’:
main()
and y.py:
# y.py
def g():
global x
x += 1
The variable x in x.py is visible throughout the module x.py, but not in y.py. In fact, execution of the line
x += 1
in the latter will cause an error message to appear, “global name ’x’ is not defined.”
Indeed, a global variable in a module is merely an attribute (i.e. a member entity) of that module, similar to a class variable’s role within a class. When module B is imported by module A, B’s namespace is copied to A’s. If module B has a global variable X, then module A will create a variable of that name, whose initial value is whatever module B had for its variable of that name at the time of importing. But changes to X in one of the modules will NOT be reflected in the other.
Say X does change in B, but we want code in A to be able to get the latest value of X in B. We can do that by including a function, say named GetX() in B. Assuming that A imported everything from B, then A will get a function GetX() which is a copy of B’s function of that name, and whose sole purpose is to return the value of X. Unless B changes that function (which is possible, e.g. functions may be assigned), the functions in the two modules will always be the same, and thus A can use its function to get the value of X in B.
Python global variables are not global
As wassimans points out above they are essentially attributes within the scope of the module they are defined in (or the module that contains the function that defined them).
The first confusion(bug) people run into is not realizing that functions have a local name space and that setting a variable in a function makes it a local to the function even when they intended for it to change a (global) variable of the same name in the enclosing module. (declaring the name
in a 'global' statement in the function, or accessing the (global) variable before setting it.)
The second confusion(bug) people run into is that each module (ie imported file) contains its own so called 'global' name space. I guess python things the world(globe) is the module -- perhaps we are looking for 'universal' variables that span more than one globe.
The third confusion (that I'm starting to understand now) is where are the 'globals' in the __main__ module? Ie if you start python from the command line in interactive mode, or if you invoke python script (type the name of the foo.py from the command shell) -- there is no import of a module whose name you can use.
The contents of 'globals()' or globals().keys() -- which gives you a list of the globals -- seems to be accessible as: dir(sys.modules['__main__'])
It seems that the module for the loaded python script (or the interactive session with no loaded script), the one named in: __name__, has no global name, but is accessible as the module whose name is '__main__' in the system's list of all active modules, sys.modules

Categories

Resources