Course Content
Python Essentials for AI Engineer
6 sections · 48 lessons
What is the global keyword?
What you need to know
When global is and isn't needed
1count = 02seen = []34def bump():5 global count6 count += 1 # rebinds the module-level name78def remember(x):9 seen.append(x) # mutation, not rebinding: no 'global' needed1011bump(); bump(); remember("d1")12print(count, seen) # 2 ['d1']Without the global line, count += 1 raises UnboundLocalError, because assignment makes count local. seen.append never assigns the name seen, so no declaration is needed.
Why it is a code smell
- Hidden inputs.
bump()depends on state that is not in its signature, so you cannot understand it by reading it. - Hard tests. Each test must reset the global, and tests can pass or fail depending on run order.
- Threads.
count += 1is a read, an add and a write. Two threads can interleave and lose updates.
nonlocal — the closure version
1def make_counter():2 n = 03 def inc():4 nonlocal n # rebind n from make_counter, not a global5 n += 16 return n7 return inc89c = make_counter()10print(c(), c(), c()) # 1 2 3The acceptable pattern: a lazy, module-level resource
Loading a model or a tokenizer can take seconds, so you want it once per process:
1_model = None23def get_model():4 global _model5 if _model is None:6 _model = {"name": "sentiment-v2"} # stands in for an expensive load7 return _model89print(get_model() is get_model()) # True -> loaded oncefunctools.cache on a no-argument function achieves the same thing without global.
A real-life example
A FastAPI service tracks how many LLM calls it has made so it can stop before the daily budget:
1import threading23class Budget:4 def __init__(self, limit):5 self.limit, self.used = limit, 06 self._lock = threading.Lock()78 def spend(self, n=1):9 with self._lock: # one thread at a time10 if self.used + n > self.limit:11 return False12 self.used += n13 return True1415budget = Budget(limit=3)16print([budget.spend() for _ in range(4)]) # [True, True, True, False]The first version used global calls_made. Under load with many worker threads, the count drifted below the real number, and the team overspent by about 8% in one day. Moving state into a class with a lock fixed it and made the budget easy to unit-test with a fresh Budget(limit=3). With several processes you would move the counter to Redis or a database.
Follow-up questions to expect
- "What is the difference between
globalandnonlocal?" —globalrebinds a module-level name;nonlocalrebinds a name in the nearest enclosing function. - "Do you need
globalto append to a global list?" — No. Appending mutates the object; only rebinding the name needsglobal. - "Are module-level constants bad too?" — No. Constants in
UPPER_CASEthat never change, likeMAX_TOKENS = 4096, are fine; the problem is mutable shared state.