Skip to main content

View Lifecycle and Performance

When you define a class-based view in Flask using the View or MethodView classes, the lifecycle of the view instance is governed by the init_every_request attribute. This setting determines whether Flask creates a fresh instance of your class for every incoming request or reuses a single instance for the lifetime of the application.

Per-Request Instantiation (Default)

By default, View.init_every_request is set to True. In this mode, the view function generated by as_view() acts as a factory. Every time a request matches the route, Flask instantiates the class and calls its dispatch_request method.

This behavior is implemented in src/flask/views.py within the as_view method:

if cls.init_every_request:
def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
self = view.view_class( # type: ignore[attr-defined]
*class_args, **class_kwargs
)
return current_app.ensure_sync(self.dispatch_request)(**kwargs)

The primary advantage of this approach is safety. Because a new instance is created for every request, you can safely store request-specific data as attributes on self without worrying about data leaking between different users or concurrent requests.

Singleton Optimization

For views that perform expensive setup operations—such as loading a machine learning model, establishing a persistent connection, or processing large configuration objects—instantiating the class on every request can introduce significant latency.

By setting init_every_request = False, you instruct Flask to instantiate the class only once, when as_view() is first called. The resulting instance is then captured in a closure and reused for all subsequent requests:

else:
self = cls(*class_args, **class_kwargs) # pyright: ignore

def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
return current_app.ensure_sync(self.dispatch_request)(**kwargs)

This pattern is demonstrated in tests/test_views.py, where a view tracks how many times it has been initialized:

class CountInit(flask.views.View):
init_every_request = False

def __init__(self):
nonlocal n
n += 1

def dispatch_request(self):
return str(n)

app.add_url_rule("/", view_func=CountInit.as_view("index"))

In this example, even after multiple requests to /, the value of n remains 1 because __init__ was only executed once during the registration of the route.

State Management and Thread Safety

The performance gain of the singleton pattern comes with a critical constraint: thread safety.

When init_every_request is False, the same instance is shared across all requests handled by a worker process. If your application uses a multi-threaded server (like the default Flask development server), multiple threads will call dispatch_request on the same instance simultaneously.

  1. In Default Mode (True): You can safely use self.user_id = ... to store data for the duration of the request.
  2. In Singleton Mode (False): You must never store request-specific data on self. Instead, use flask.g to store data that should be local to the current request context.

Initialization Arguments

The as_view method accepts arbitrary arguments and keyword arguments after the required name parameter. These are forwarded to the class's __init__ method.

  • If init_every_request is True, these arguments are stored and passed to the constructor every time a request arrives.
  • If init_every_request is False, these arguments are used immediately to create the singleton instance.

This allows you to customize class-based views at the time of registration:

class RenderTemplateView(View):
def __init__(self, template_name):
self.template_name = template_name

def dispatch_request(self):
return render_template(self.template_name)

app.add_url_rule(
"/about",
view_func=RenderTemplateView.as_view("about", template_name="about.html")
)

Execution Context

Regardless of the lifecycle choice, Flask wraps the call to dispatch_request with current_app.ensure_sync. This ensures that Flask can correctly handle both standard synchronous methods and async def dispatch_request implementations, as seen in tests/test_async.py:

class AsyncView(View):
async def dispatch_request(self):
await asyncio.sleep(0)
return request.method

This integration allows class-based views to benefit from asynchronous execution while still adhering to the lifecycle rules defined by init_every_request.