"Reading the source code" is one of the ways to learn programming efficiently. The famous Become a Hacker (original title: How To Become A Hacker) also says:
But let's just say it doesn't work for books and workshop courses. Many, or perhaps most, hackers have studied in their own way. Useful is (a) reading the code, and (b) writing the code.
However, I feel that there are many people who often "write code" but are not very good at "reading code". (It is often more difficult to "read" in terms of difficulty ...) In this article, I'll read Django, Python's major web framework, to give you the feel of reading the source code. I hope this will increase the number of people who want to read open source code!
This time, as the first step, we will follow the flow of class-based View.
I don't know how to write it in Qiita's first post, so if you make a mistake in quoting, it would be helpful if you could point it out in the comments.
We will proceed in the following environment. Django is the latest 2.2.17 at the time of writing (November 2020), you don't have to be too particular about the Python version, but this time we will proceed with 3.8.5. When reading the source code, it is recommended to use your favorite editor or IDE that can use tag jumps such as PyCharm and Eclipse.
Check the installation location with $ python -c" import django; print (django .__ path__) "
for the Django source code, or check the Official Repository Let's pull from .17).
$ python --version
Python 3.8.5
$ python -m django --version
2.2.17
--I can understand Python 3.x somehow --Somehow understand Django's Tutorial
Sample code: https://github.com/tsuperis/read_django_sample
Views can be written in both functions / classes in Django.
hoge/views.py
from django.http.response import HttpResponse
from django.views import View
def function_based_view(request):
"""Function-based view"""
return HttpResponse('function_based_view')
class ClassBasedView(View):
"""Class-based view"""
def get(self, request):
return HttpResponse('class_based_view GET')
def post(self, request):
return HttpResponse('class_based_view POST')
core/urls.py
from django.urls import path
from hoge import views as hoge_views
urlpatterns = [
path('func/', hoge_views.function_based_view, name='hoge_func'),
path('cls/', hoge_views.ClassBasedView.as_view(), name='hoge_cls'),
]
this is,
--When accessing / func /
, "function_based_view" is returned regardless of the HTTP request method.
--When requesting / cls /
with GET, "class_based_view GET" is returned.
--When you make a POST request to / cls /
, "class_based_view POST" is returned.
--When requesting / cls /
other than GET / POST, Method Not Allowed with HTTP status 405 is returned.
It means that · · · Why do function-based views and class-based views behave differently?
First of all, if you look at the simple function base view function_based_view
and urls.py, it seems that the function is [django.urls.path](https://docs.djangoproject.com/en/2.2/ref/urls/# It seems to function as View if set in the second argument of path).
So what about class-based views?
If you look at the urls.py, you'll notice that the biggest difference is as_view ()
.
Let's read from here.
View.as_view()
Type a command in the interpreter to see where it is, or use the tag jump feature to browse the class directly.
>>> from django.views import View
>>> View.__module__
'django.views.generic.base'
(django installation path)/django/views/generic/base.py
# -- (A)
@classonlymethod
def as_view(cls, **initkwargs):
"""Main entry point for a request-response process."""
# -- (B)
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))
# -- (C)
def view(request, *args, **kwargs):
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.setup(request, *args, **kwargs)
if not hasattr(self, 'request'):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
# -- (D)
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
return view
Ignore the classonlymethod
decorator this time.
Recognize something like classmethod
(a decorator attached to a method that can be called in a non-instantiated class).
This is why you can write ClassBasedView.as_view ()
instead of ClassBasedView (). As_view ()
.
Returning to the main line, there are two arguments
cls
is the convention of class methods and contains ClassBasedView
.
Arguments that start with **, such as ** initkwargs
, are called variable-length keyword arguments and are treated as dict types by taking arbitrary named arguments.
For example, if you call as_view (name ='view', number = 1)
, the contents of initkwargs will be {'name':'view', number = 1}
.
>>> def kw(hoge, **kwargs):
... print(f'kwargs: {kwargs}')
...
>>> kw('hoge', name='view', number=123)
kwargs: {'name': 'view', 'number': 123}
There is no difficult processing, so I will go crispy. It seems that initkwargs (dict type) is looped and the argument name is checked.
As an error condition
key
(argument name) exists in ClassBasedView.http_method_names
key
does not exist in the attribute of the ClassBasedView
classWhat is the http_method_names
of 1?
It is defined at the beginning of the View
class.
(django installation path)/django/views/generic/base.py
class View:
"""
Intentionally simple parent class for all views. Only implements
dispatch-by-method and simple sanity checking.
"""
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
That is, in this loop all argument names
as_view
, http_method_names
I found that I was checking.
This is the heart of this time.
Defines the view function. If you also look at the return value of as_view
, you can see that this function is returned.
Let's compare the function-based view we created earlier with the view function.
def function_based_view(request):
def view(request, *args, **kwargs):
Since * args
is called a variable length argument and the input is arbitrary, the only required argument of the view function is the first argument.
In other words, it can be called as function_based_view
, so it can be used as a function-based view.
Let's see the continuation.
I instantiate the class and then call the setup`` dispatch
method.
__init__
looks like this.
I have set the named argument of as_view
as an attribute of the instance, but since I have not specified an argument for as_view
this time, I will ignore it.
(django installation path)/django/views/generic/base.py
def __init__(self, **kwargs):
"""
Constructor. Called in the URLconf; can contain helpful extra
keyword arguments, and other things.
"""
# Go through keyword arguments, and either save their values to our
# instance, or raise an error.
for key, value in kwargs.items():
setattr(self, key, value)
It's easy here, so I'll just put the code on it.
The self.request
, which you often see when using a class-based view, is set at this time.
(django installation path)/django/views/generic/base.py
def setup(self, request, *args, **kwargs):
"""Initialize attributes shared by all view methods."""
self.request = request
self.args = args
self.kwargs = kwargs
request.method
contains the HTTP request method name literally.
You're trying to get the same instance method as the HTTP request method name with getattr
.
In other words
--When you make a GET request to / cls /, "class_based_view GET" is returned.
--Call self.get
--When you make a POST request to / cls /, "class_based_view POST" is returned.
--Call self.post
--When requesting / cls / other than GET / POST, Method Not Allowed with HTTP status 405 is returned.
--Call self.http_method_allowed
Seems to be coming from here.
(django installation path)/django/views/generic/base.py
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed) #here
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
I will briefly summarize the processing flow of the view function.
self.request`` self.kwargs
self.args
(D) update_wrapper
It's not Django code, but it's important when creating original decorators, so I'll briefly introduce it.
console
>>> from functools import update_wrapper
>>> def view():
... """This is the view function"""
... pass
...
>>> def dispatch():
... """This is the dispatch function"""
... pass
...
>>> view.__doc__
'This is the view function'
>>> update_wrapper(view, dispatch)
<function dispatch at 0x7f90328194c0>
>>> view.__doc__
'This is the dispatch function'
The example looks like this, do you understand?
When you call the function, view.__doc__
is replaced with dispatch.__doc__
.
In addition to __doc__
, there is Attribute to be overwritten, but basically the metadata is Used for replacement purposes.
In this case, it's used to let a new function called view inherit the metadata of the class body and the dispatch
method.
A simple summary of the as_view
process is" Create a view function that calls the instance method corresponding to the HTTP request method ".
I tried to follow the source code in a hurry, but when I actually read it
--Read after confirming the actual operation ――For parts that are not related to the main line, take a quick look at the function name and contents to get a feel for it.
I think that is the point. You can follow the detailed processing, but if you don't understand the whole thing, you won't know what you were doing.
I haven't decided in particular, but I'll read FormView or Form. I'd like to read Model as well, but it seems to be long due to metaprogramming, so I'll try it after I get used to Qiita a little more. .. ..
I wrote the continuation
Recommended Posts