Flask's `g` - The "Global" That Isn't
Summary
Flask's request context object is called g, and the name is too clever by half. It stands for global. Except each request gets its own isolated g, so you get global-like access without the race conditions a real shared global would hand you. Armin Ronacher chose the letter on purpose. It's quick to type when you reach for it constantly, and a single letter stands out on the page.
Flask's request context object is called g, and once you know why, it's too clever by half.
I was reviewing a Flask codebase and kept seeing this pattern:
from flask import g
def get_db():
if "db" not in g:
g.db = sqlite3.connect("app.db")
return g.db
I always assumed g was just an arbitrary short name. It stands for "global," except it isn't one. g gives you global-like access while being request-local. Each request gets its own isolated g instance.
Why not use an actual global? Because that's a mess:
# DON'T DO THIS - Shared between ALL requests!
db_connection = None
@app.route("/users/<user_id>")
def get_user(user_id):
global db_connection
if not db_connection:
db_connection = sqlite3.connect("app.db")
# Race conditions! Data leaks! Connection errors!
Multiple concurrent requests compete for the same connection, and you end up mixing user data or crashing outright.
g gives you what feels like a global but is isolated per request:
@app.route("/users/<user_id>")
def get_user(user_id):
db = get_db() # Gets THIS request's connection
# Safe, isolated, no interference with other requests
The lifecycle is simple. A request arrives and Flask creates a fresh g object. You store stuff in it (g.db = connection), and you can reach that stuff anywhere in the request (return g.db). When the request ends, g is destroyed and cleanup runs.
Armin Ronacher, Flask's creator, chose g deliberately. Developers keep reaching for real global state when what they need is state scoped to a single request, and the name pokes gentle fun at that habit. The single letter is quick to type, which matters because you use it constantly, and it reads as obviously special, the same way e reads as an exception.
For managing multiple resources I now lean on this:
from flask import g
def get_redis():
if "redis" not in g:
g.redis = redis.StrictRedis()
return g.redis
def get_user():
if "user" not in g:
g.user = load_user_from_token()
return g.user
@app.teardown_appcontext
def cleanup(error):
# Always runs, even if request fails
if hasattr(g, "redis"):
g.redis.close()
So g is "global" only in the sense that your request-handling code can reach it from anywhere. Underneath it's thread-safe storage that gets torn down and rebuilt for every request, dressed up to look like the thing it deliberately isn't.