Flask's `g` - The "Global" That Isn't
Today I learned why Flask's request context object is called g
, and 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, but it turns out it stands for "global" - with a twist. It's intentionally ironic: g
provides global-like access but is actually request-local. Each request gets its own isolated g
instance.
The Problem It Solves
Without g
, you might be tempted to use actual globals:
# 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!
This is a disaster waiting to happen. Multiple concurrent requests would compete for the same connection, potentially mixing user data or causing crashes.
How g
Actually Works
Flask's g
gives you what feels like a global variable but is actually 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:
- Request arrives → Flask creates a fresh
g
object - You store stuff in it →
g.db = connection
- Access it anywhere in that request →
return g.db
- Request ends →
g
is destroyed, cleanup runs
The Name Is Part of the Lesson
Armin Ronacher (Flask's creator) chose g
deliberately. It's a subtle joke about what developers think they want (global state) versus what they actually need (request-scoped state). The single letter makes it:
- Quick to type (you'll use it constantly)
- Easy to remember ("g for global")
- Obviously special (like
e
for exception)
Practical Example
Here's a real pattern I now will use for managing multiple resources:
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()
The Key Insight
g
is "global" in the sense that it's globally accessible within your request handling code, but it's actually providing thread-safe, request-isolated storage.