In a Django URLconf, why do you route to MyView.as_view() instead of the class itself, and what does as_view() return?
answer
- path() wants a plain callable
- a closure over the class
- new object every request
- setup() then dispatch()
basics
~20 sDjango's path() needs a callable that takes a request and returns a response. MyView.as_view() returns such a function; on every request it builds a fresh MyView instance, calls setup(), then dispatch(), which runs the handler named after the HTTP method.
solid answer
~40 sThe URL resolver calls whatever you register as `view(request, *args, **kwargs)`, so it needs a function, not a class. `View.as_view()` is a class-only method that builds and returns exactly that: an inner function closing over the class and any `**initkwargs`. Each time a request arrives, that function instantiates the class with the initkwargs, calls `setup()` to attach `request`, `args` and `kwargs`, then calls `dispatch()`, which picks `get()`, `post()` and so on from the lowercased HTTP method. `as_view()` itself runs once, when the URLconf is loaded; it also validates the initkwargs, raising `TypeError` for a keyword that is not already a class attribute or that names an HTTP method. The returned function carries `view_class` and `view_initkwargs` so tools can tell which class sits behind a URL.
code
python · 19 linesfrom django.http import HttpResponse
from django.urls import path
from django.views import View
class StatusView(View):
message = "ok"
def get(self, request, *args, **kwargs):
return HttpResponse(self.message)
urlpatterns = [
path("status/", StatusView.as_view()),
path("status/maintenance/", StatusView.as_view(message="maintenance")),
]
# StatusView.as_view(colour="red") -> TypeError when the URLconf loads
# urlpatterns[0].callback.view_class -> StatusViewgo deeper
Recall that path() needs a callable, that as_view() produces it, and that each request gets a brand-new instance which runs setup() and then dispatch().
Explain the closure: as_view() runs once at URLconf import and validates initkwargs, while the returned function instantiates, sets up and dispatches per request.
Show you know what survives between requests: instance state never does, class-level state lives as long as the worker process, and initkwarg objects are shared by every instance.
Frame as_view() as the reason CBVs can be configured per route; weigh how much configuration belongs in initkwargs versus subclasses for readability across a team.
## What the URL resolver expects Django's URL resolver has a very small contract for a **view**: it is a callable that takes an `HttpRequest` plus the arguments captured from the URL pattern, and returns an `HttpResponse`. A plain function satisfies that directly. A class does not: calling `MyView(request)` would only construct an object, and Django has no notion of "call the class and then find a method on it". `View.as_view()` bridges the gap. It turns a class into the function the resolver wants: ```python from django.urls import path from .views import StatusView urlpatterns = [ path("status/", StatusView.as_view(), name="status"), ] ``` Writing `path("status/", StatusView)` is a bug, and Django catches it: the system check framework reports **`urls.E009`** ("pass StatusView.as_view() instead of StatusView") when `runserver` or `manage.py check` runs. Without that check the resolver would call the class with the request as a positional argument, which `View.__init__()` (keyword arguments only) rejects with a `TypeError`. ## What as_view() builds `as_view()` is decorated with `classonlymethod`, so it can be called on the class but raises `AttributeError` if you try it on an instance. It runs **once**, when the URLconf module is imported, and returns an inner function usually shown as `view`. That function is what runs **on every request**: 1. **Instantiate** — `self = cls(**initkwargs)`. The base `View.__init__()` simply does `setattr(self, key, value)` for each initkwarg. 2. **Set up** — `self.setup(request, *args, **kwargs)` stores `self.request`, `self.args` and `self.kwargs`, and aliases `head` to `get` when the class defines `get()` but no `head()`. 3. **Sanity check** — if the instance has no `request` attribute afterwards, Django raises `AttributeError` asking whether you overrode `setup()` and forgot `super()`. 4. **Dispatch** — `return self.dispatch(request, *args, **kwargs)`, which looks up the handler named after `request.method.lower()`. The key consequence: **one instance per request**. Attributes you assign on `self` during a request die with that request; nothing on the instance survives into the next one. ## initkwargs: configuration at URLconf time Keyword arguments passed to `as_view()` override class attributes for that one URL, which lets one class serve several routes: | Call | Result | |---|---| | `StatusView.as_view()` | Uses the class attributes as written | | `StatusView.as_view(template_name="status/brief.html")` | Allowed if `template_name` is already an attribute of the class | | `StatusView.as_view(colour="red")` | `TypeError`: as_view only accepts arguments that are already attributes of the class | | `StatusView.as_view(get=some_function)` | `TypeError`: an HTTP method name is not accepted as a keyword | Both checks run inside `as_view()`, so the mistake surfaces as soon as the URLconf is loaded, not on the first request to that route. ## What the returned function carries The function returned by `as_view()` is decorated with a little metadata: - **`view_class`** — the class, so debugging tools and tests can ask which view serves a URL. - **`view_initkwargs`** — the dict of keyword arguments passed to `as_view()`. - **`__doc__` and `__module__`** — copied from the class. - **Attributes from `dispatch`** — the function's `__dict__` is updated from `cls.dispatch.__dict__`, which is how a flag such as the one `csrf_exempt` sets on `dispatch` reaches the URL-level callable. - **`__name__` is left as the inner function's name** on purpose; Django's comment says to use `view_class` to identify the view robustly. ## What the handlers can rely on Because `setup()` runs before `dispatch()`, every handler and every helper method on the instance can read the request context from `self` instead of threading it through arguments: - `self.request` — the `HttpRequest`, including `self.request.user` once authentication middleware has run. - `self.args` — positional values captured by a regex-based `re_path()` group without a name. - `self.kwargs` — named values captured by `path()` converters, such as `<int:pk>` becoming `self.kwargs["pk"]`. The handlers still receive `request`, `*args` and `**kwargs` as parameters as well, so both styles work; generic views mostly read from `self`. ## Why the design is this way - **Isolation** — a fresh instance per request means handlers can freely store request-specific state on `self` (`self.object`, `self.request`) without locking. - **Reuse** — the same class can be mounted at several URLs with different initkwargs. - **Cheap per-request cost** — instantiating a plain Python object is trivial next to template rendering or a database query. ## Common mistakes - Passing the class instead of `as_view()`, or calling `as_view` on an instance. - Expecting `__init__()` to see the request: it runs before `setup()`, with initkwargs only. - Assuming the instance is reused between requests and caching data on `self` "for next time" — it is thrown away. - Forgetting that **class attributes** are not per-request: the instance is new, the class is not, so mutating a list or dict defined on the class body leaks between requests.
- When exactly does as_view() run, and when does the function it returns run?`as_view()` runs once, when the URLconf module is imported and the `urlpatterns` list is built. The function it returns runs on every matching request. That split is why initkwarg validation errors appear at URLconf load time, while everything in `setup()`, `dispatch()` and the handlers happens per request on a new instance.
- Given only the resolved URL, how can a test find out which class-based view serves it?`resolve(path).func` is the function returned by `as_view()`, and it carries `view_class` and `view_initkwargs`. Comparing `resolve('/status/').func.view_class` with the expected class is more robust than checking `__name__`, which Django deliberately leaves as the inner function's name rather than copying the class name.
saying these in an interview costs you the question
- Registering the class itself in path() and expecting Django to instantiate it, although the urls.E009 check rejects it
- Believing one view instance is created at startup and reused for every request
- Thinking as_view() accepts any keyword and silently ignores unknown ones
- Expecting __init__() to receive the request object
- Assuming class attributes are reset per request because the instance is new