Global Variables in Python: How Scope Really Works
In Python, global means module-level, not program-wide. Reading a global needs no declaration; rebinding one inside a function needs the global keyword.
A global variable in Python is a name bound at the top level of a module, and the “global” namespace a function sees is the namespace of the module where that function was defined. There is no program-wide scope in Python at all. Once that lands, most of the confusing behaviour around globals stops being arbitrary.
Everything else here (the UnboundLocalError, the global keyword, nonlocal, the cross-module config pattern) is a consequence of that one fact.
The short version: “Global” in Python means module-level, not program-wide. A function can read any name from its defining module’s namespace with no ceremony, but assigning to a name anywhere in a function makes that name local for the entire function, so rebinding a module-level name needs
global x. Mutating a global list or dict is not a rebinding, so it needs nothing.
What “Global” Means in Python (It Means Module-Level)
The language reference puts it plainly: “If a name is bound at the module level, it is a global variable.” That is the entire definition. A global is not special, not registered anywhere, and not shared across your program. It is simply a name that lives in one module’s namespace.
Each module gets its own global namespace, and they do not merge. A name defined at the top of utils.py is a global of utils, and code in main.py cannot see it just by being in the same program. More surprising: a function’s global namespace is decided when the function is defined, not when it is called. The docs for globals() say the dictionary it returns “is set when the function is defined and remains the same regardless of where the function is called.” Import a function from utils into main and call it there, and it still reads and writes utils’s globals.
Reading needs no declaration. The Python FAQ states: “variables that are only referenced inside a function are implicitly global.” Assigning is the part that changes:
greeting = "hello" # a global: bound at module level
def show():
print(greeting) # a plain read, no declaration needed
def shadow():
greeting = "local" # a brand new local; the global is untouched
print(greeting)
show(); shadow(); print(greeting)
hello
local
hello
shadow did not overwrite anything. It created a local name that happens to spell the same, used it, and threw it away when the call returned. The tutorial’s wording for this is worth memorising: “if no global or nonlocal statement is in effect, assignments to names always go into the innermost scope.”
“Global” is the name of one namespace, not a rank above the others. Python has module scope and function scope. It has nothing above module scope, so two modules can both define count and they are two unrelated variables.
The Four Scopes Python Searches, in Order
When Python resolves a name, it looks in up to four namespaces, innermost first:
- Local: the current function’s own names.
- Enclosing: the local scopes of any enclosing functions, nearest first.
- Global: the namespace of the module containing the code.
- Built-ins: the namespace of the
builtinsmodule, home oflen,print,dictand friends.
This is commonly taught as LEGB, an acronym from teaching practice rather than the official documentation, which simply lists the scopes. The order is what matters.
name = "module"
def outer():
name = "enclosing"
def inner():
print(name) # finds outer's name before the module's
inner()
outer()
print(len) # `len` comes from builtins, the last stop
enclosing
<built-in function len>
These scopes are worked out textually, at compile time. The reference says the local variables of a code block “can be determined by scanning the entire text of the block for name binding operations.” Where a function is written decides what it can see; where it is called does not.
One category is deliberately missing from the list above: a class body. A class block is its own namespace, but it is not an enclosing scope for the functions defined inside it. The reference is explicit: “The scope of names defined in a class block is limited to the class block; it does not extend to the code blocks of methods. This includes comprehensions and generator expressions, but it does not include annotation scopes, which have access to their enclosing class scopes.”
size = "module"
class Table:
size = "class"
def get(self):
return size # skips the class block entirely
print(Table().get())
module
That is why methods reach class attributes through self.size or Table.size rather than by bare name. A class body sits between the module and its methods in the file, but not in the lookup chain.
The comprehension half of that quote catches people harder, because it fails inside the class body itself:
try:
class Grid:
xs = [1, 2, 3]
n = 2
ys = [x * n for x in xs] # xs resolves, n does not
except NameError as e:
print(f"{type(e).__name__}: {e}")
NameError: name 'n' is not defined
The outermost iterable is the one exception: xs is evaluated in the class block before the comprehension’s own scope begins, so it resolves. Everything else inside the comprehension, n included, looks straight past the class block to the module.
Why count += 1 Raises UnboundLocalError
Here is the error that usually sends people searching for “global variable python” in the first place:
count = 0
def bump():
count += 1 # this is count = count + 1
try:
bump()
except UnboundLocalError as e:
print(f"{type(e).__name__}: {e}")
print(issubclass(UnboundLocalError, NameError))
UnboundLocalError: cannot access local variable 'count' where it is not associated with a value
True
The rule behind it is compile-time and applies to the whole function: “If a name binding operation occurs anywhere within a code block, all uses of the name within the block are treated as references to the current block.” The count = ... half of count += 1 is a binding operation, so count is local for every line of bump, including the read that happens first. The module-level count is never consulted. UnboundLocalError is a subclass of NameError, so except NameError catches it too.
You can watch the compiler make the decision. On CPython 3.12 and later, an unproven local compiles to LOAD_FAST_CHECK, the opcode that raises UnboundLocalError when the slot is empty:
import dis
def bump():
count += 1
print([i.opname for i in dis.get_instructions(bump) if "_FAST" in i.opname])
['LOAD_FAST_CHECK', 'STORE_FAST']
No lookup of the module namespace appears anywhere in that function. The choice between a fast local slot and a namespace lookup was made before the code ever ran.
Compile the same two lines at module level and the opcodes change, because module and class bodies are not optimized scopes and have no local slots to hand out:
import dis
mod = compile("count = 0\ncount += 1", "<module>", "exec")
print([i.opname for i in dis.get_instructions(mod) if i.opname.endswith("_NAME")])
['STORE_NAME', 'LOAD_NAME', 'STORE_NAME']
LOAD_NAME searches locals, then globals, then built-ins at run time, which is why the identical count += 1 that fails inside a function is fine at the top of a file.
Now the distinction that keeps real code working: mutation is not a binding operation. Calling a method on an object does not rebind the name, so it does not make the name local.
items = []
def add(x):
items.append(x) # mutates the object the name points at
def reset():
global items
items = [] # rebinds the name, so this needs the declaration
add("a"); print(items)
reset(); print(items)
['a']
[]
This is the single most useful line to hold on to: items.append(x) never needs global, and items = [] always does.
An assignment on line 40 of a function makes the name local on line 3 as well. Adding a try/except branch that assigns to a name you were previously reading from module scope will break the reads above it, which is why this error so often appears in code that “was working yesterday.”
Read-before-assignment is one of the most common Python mistakes beginners hit, and it is worth being able to explain the compile-time reason rather than just the fix.
global vs nonlocal vs Neither: Picking the Declaration
There are exactly three options for where an assignment inside a function lands.
| Declaration | Where the assignment binds | Reach for it when |
|---|---|---|
| none | the innermost scope: a new local | ordinary local work, which is almost always |
global x | the defining module’s namespace | you must rebind module-level state |
nonlocal x | the nearest enclosing function scope that already binds x | a closure needs to update its own captured state |
global fixes the counter from the previous section, and the bytecode changes to match:
import dis
count = 0
def bump():
global count
count += 1
bump(); bump()
print(count, [i.opname for i in dis.get_instructions(bump) if i.opname.endswith("_GLOBAL")])
2 ['LOAD_GLOBAL', 'STORE_GLOBAL']
global also creates a module-level name if none exists yet: the first assignment in the function binds it, and it is visible to the rest of the module afterwards.
Four rules about global catch people out:
- It applies to the entire current scope, module, function body or class definition, not from that line onward.
- A
SyntaxErroris raised if the name is used or assigned to before itsglobaldeclaration in the same scope. - At module level it does nothing, because everything there is already global. The use-before-declaration requirement still applies, though it “is relaxed in the interactive prompt.”
- It is a directive to the parser. A
globalstatement inside a string passed toexec()does not affect the code block containing theexec()call, and the same is true foreval()andcompile(). The same note applies tononlocal.
The second rule is easy to demonstrate without ever running the function:
src = "def bump():\n print(count)\n global count\n"
try:
compile(src, "<demo>", "exec")
except SyntaxError as e:
print(f"{type(e).__name__}: {e.msg}")
SyntaxError: name 'count' is used prior to global declaration
nonlocal, added by PEP 3104 for Python 3.0, targets the nearest enclosing function binding instead:
def make_counter():
count = 0
def bump():
nonlocal count # rebind make_counter's count
count += 1
return count
return bump
next_id = make_counter()
print(next_id(), next_id(), next_id())
1 2 3
That shared count is neither a local slot nor a namespace entry. It lives in a closure cell, and it gets its own pair of opcodes:
import dis
print([i.opname for i in dis.get_instructions(next_id) if "_DEREF" in i.opname])
['LOAD_DEREF', 'STORE_DEREF', 'LOAD_DEREF']
The boundary between the two is sharp and worth stating outright: nonlocal can never reach module scope. “If a name is not bound in any nonlocal scope, or if there is no nonlocal scope, a SyntaxError is raised.” Before PEP 3104, Python could rebind names only locally or, with global, at module level; nonlocal filled the gap in the middle and nothing more.
Sharing a Global Across Modules Without Losing It
Since each module owns its globals, “share a global between two files” needs an actual mechanism. The Python FAQ gives one: “The canonical way to share information across modules within a single program is to create a special module (often called config or cfg). Just import the config module in all modules of your application; the module then becomes available as a global name.” It works “Because there is only one instance of each module, any changes made to the module object get reflected everywhere.”
# config.py
x = 0 # default value of the 'x' configuration setting
# mod.py
import config
config.x = 1
# main.py
import config
import mod
print(config.x) # 1
The attribute access is doing the work. config.x looks the name up in the config module object every time, so it always sees the current value.
Now the trap. from config import x does something different: the reference says the imported value is stored “in the current namespace, using the name in the as clause if it is present, otherwise using the attribute name.” Your module gets its own binding to whatever the value was at import time, and later rebinding inside config is invisible to it.
# reader.py
from config import x # a separate name, holding config.x's value right now
import mod # mod runs config.x = 1
print(x) # still 0
The mechanic underneath is ordinary name binding, and you can see it without any modules at all. The tutorial’s phrasing is exact: “Assignments do not copy data — they just bind names to objects.”
settings = {"x": 0}
x = settings["x"] # a second name for the current value
settings["x"] = 1 # rebinding inside settings
print(x, settings["x"])
0 1
from config import x snapshots. import config tracks. If the value can change at runtime, import the module and read config.x at the point of use.
globals() Writes Through, locals() No Longer Does
Two built-ins expose these namespaces directly, and they are not symmetric. globals() returns “the dictionary implementing the current module namespace,” and it is live and writable:
counter = 0
def bump():
globals()["counter"] += 1 # writes straight into the module namespace
bump(); bump()
print(counter)
2
locals() is not the mirror image of that. In an optimized scope (functions, generators, coroutines and comprehensions, where the compiler knows the local names ahead of time), “each call to locals() instead returns a fresh dictionary,” and “name binding changes made via the returned dict are not written back to the corresponding local variables.” That was specified by PEP 667 and is marked as changed in Python 3.13. The old trick of writing into locals() is formally dead:
def demo():
x = 1
locals()["x"] = 99 # lands in a throwaway snapshot
return x
print(demo())
1
Treat globals() as a debugging and metaprogramming tool rather than a design. For sharing state, the config module is the pattern to reach for.
When a Global Is Fine and When It Bites: A Three-Tier Rule
The usual advice (“avoid globals”) is too blunt to act on. Sort by who writes the name, and from where.
Tier 1: read-only constants and configuration. Bound once at import, never reassigned. Completely fine, and idiomatic. PEP 8 asks for all capitals with underscores, like MAX_OVERFLOW or TOTAL. MAX_RETRIES = 3 at the top of a module is not a design smell, it is a named constant.
Tier 2: module-private mutable state with a single writer, behind functions. A cache, a registry, a lazily built singleton. Acceptable when exactly one module owns the writes and callers go through an API rather than touching the name. PEP 8 covers the naming: modules designed for from M import * “should use the __all__ mechanism to prevent exporting globals, or use the older convention of prefixing such globals with an underscore.”
_cache = {} # module-private: one writer, behind this API
def get(key):
return _cache.get(key)
def put(key, value):
_cache[key] = value # mutation, so no `global` needed
put("a", 1)
print(get("a"))
1
Tier 3: mutable state written from several call sites, threads, or processes. This is where globals earn their reputation. Any caller can change the value, so a function’s result no longer depends only on its arguments; tests need setup and teardown to undo each other; a bug in one module shows up as wrong behaviour in another. Replace it with a parameter, a closure over the state, or an instance holding it as attributes.
Concurrency is where Tier 3 turns from awkward into wrong. Threads in one process share the module namespace, so they share globals. The standard library FAQ lists which operations are atomic under the GIL, including L.append(x), L1.extend(L2), x = L[i], D[x] = y and D1.update(D2), and which are not, including i = i+1, L.append(L[-1]), L[i] = L[j] and D[x] = D[x] + 1. It closes with: “When in doubt, use a mutex!” A global counter incremented from two threads is the textbook non-atomic case. For state that should not be shared at all, threading.local() gives each thread its own values.
Two assumptions people quietly build on globals have moved recently:
- The GIL is no longer a safe bet. As of Python 3.13, CPython can be built free-threaded with the
--disable-gilconfigure option. In such a build the GIL is off by default and can be forced either way at run time with-X gil=1/-X gil=0orPYTHON_GIL; on a stock build-X gil=0fails at startup, because setting it to 0 “is only available in builds configured with--disable-gil”. PEP 779 moved free-threaded Python to officially supported status in Python 3.14, though it remains an optional, non-default build. “The GIL protects my counter” is now a claim about which build someone runs. - Processes never shared globals, and the default changed. In Python 3.14 the POSIX
multiprocessingstart method changed fromforktoforkserver, andfork“is no longer the default start method on any platform”; code that needs it must ask viaget_context()orset_start_method(). Windows and macOS default tospawn. Under bothspawnandforkserverthe child does not inherit the parent’s memory, so a global you set at runtime before starting a pool does not arrive in the worker. Pass the value as an argument instead.
If threads and globals are where your code is heading, how the GIL actually constrains you is the next thing to read.
Quick Reference: Five Questions Worth Being Able to Answer
What does global do? It makes assignments in the current scope bind names in the defining module’s namespace instead of creating locals. It applies to the whole scope, not from that line onward, and it can create a module-level name that did not exist.
global vs nonlocal? global binds in the module namespace. nonlocal binds in the nearest enclosing function scope that already has the name. nonlocal cannot reach module level, and raises SyntaxError at compile time if no enclosing function binds the name.
Why does mutating a global list work without global? Because items.append(x) changes the object, and never rebinds the name. Only a binding operation, such as items = [], makes the compiler treat the name as local.
Why UnboundLocalError when the global clearly exists? A binding operation anywhere in the function makes the name local for the entire function, so the earlier read looks in an empty local slot. It is a subclass of NameError.
How do you share state between modules? Import a shared config module everywhere and read or write config.x. There is one instance of each module, so every importer sees the same object. from config import x binds a separate name and will not see later rebinding.
These come up alongside mutable default arguments and closure capture; see the topics that actually come up in Python interviews for the wider list.
Where the books fit
Scope is one of the invisible mechanics that never quite fits in a paragraph, which is why it shows up in our handwritten volumes as drawn namespace chains rather than prose. If you are still building the base, Python in One Month covers scope, functions and modules in the order you need them. For interview depth, the compile-time story here (local slots against namespace lookups, closures, and the global/nonlocal boundary) is worked through with exercises in Python in Three Months, and it slots into Phase 3 of the Python roadmap. If Tier 3 is your daily problem, threads and worker processes fighting over shared state, Python for Staff Engineers takes it to the level where you are picking between a module singleton, a passed dependency and per-thread storage, and defending the choice.
Next time you reach for global, ask which tier you are in first. Tier 1 needs no defence, Tier 2 needs a single writer, and Tier 3 needs a different design.
Frequently asked questions
What is a global variable in Python?
A global variable is a name bound at the top level of a module, outside any function or class. Every module has its own global namespace, so global in Python means module-level rather than program-wide. Any function defined in that module can read the name without declaring anything.
Why does Python raise UnboundLocalError on a global?
Because assigning to a name anywhere in a function makes that name local for the whole function, including the lines above the assignment. So count += 1 tries to read a local count that has not been bound yet. Declare global count if you meant the module-level name, or pass the value in as a parameter.
What is the difference between global and nonlocal?
The global statement makes assignments in a scope bind names in the defining module's namespace. The nonlocal statement makes them bind in the nearest enclosing function scope that already has that name. nonlocal can never reach module level; if no enclosing function binds the name, Python raises SyntaxError at compile time.
Do I need global to modify a global list or dict?
No. Calling append, update or item assignment changes the object the name already points to, and mutation is not a name-binding operation. You only need global when you rebind the name itself, such as assigning a fresh list to it.
How do I share a global variable across modules?
The canonical pattern in the Python FAQ is a small config module that every other module imports, then reads and writes as attributes like config.x = 1. Because there is only one instance of each module, the change is visible everywhere. Avoid the from config import x form, which binds a separate name in the importing module and will not see later rebinding.