このページでは、Django フレームワークで定義される reverse
と reverse_lazy
の違いについて説明していきます。
Django の解説が行われているサイトや参考書等で reverse
や reverse_lazy
が使用されているサンプルソースコードを見たことのある方も多いのではないでしょうか?
これらは似た関数ではあるものの当然違いがあり、その違いを認識して使い分ける必要があります。では、具体的にどのような違いがあるのでしょうか?その点について、このページで解説していきたいと思います。
Contents
reverse
と reverse_lazy
の違い
では reverse
と reverse_lazy
の違いについて説明していきます。
URL に変換するタイミングが異なる
最初に結論を言ってしまいます!
reverse
と reverse_lazy
は両方とも URL の名前
や ビューの関数
を URL に変換する関数になります。この点に関しては reverse
と reverse_lazy
とで共通です。
ですが、この2つの関数では URL への変換を行うタイミングが異なります。
reverse
の場合、実行されたタイミングで URL への変換が行われます。
それに対し、reverse_lazy
の場合、実行されたタイミングではなく、URL が実際に必要になったタイミングで URL への変換が行われます。つまり、URL への変換が遅延・先延ばしされて実行されることになります。
lazy
は「怠けている」等の意味を示す単語です。プログラミングなどでは遅延させる場合や先延ばしする場合に利用される単語となります。
つまり、reverse_lazy
は怠け者なので、実行されてもすぐに URL への変換は行ってくれません。そして、実際に URL が必要になった時に慌てて URL への変換を行うようになっています。こういう人って結構いますよね…笑。
ということで、reverse_lazy
は怠け者なので使わないようにしましょう!という結論になりそうなものですが、そうではないです。この「怠け者」という特徴を活かすことで reverse
で実現できないことが実現できるようになります。
具体的には、クラスベースでビューを作成する際には働き者の reverse
ではなく怠け者の reverse_lazy
を利用する必要があります。それ以外で URL への変換が必要な際には基本的に reverse
を利用すれば良いです。
以上が reverse
と reverse_lazy
の違いの結論になります。
ここからは、上記で説明している内容の説明の補足をしていきます。まず reverse
と reverse_lazy
について説明し、それらの特徴を踏まえてクラスベースでビューを作成する際に reverse_lazy
を利用する必要がある理由について説明していきます。
スポンサーリンク
reverse
と reverse_lazy
では、reverse
と reverse_lazy
について説明していきたいと思います。
両方とも URL への変換を行う関数
まず、reverse
と reverse_lazy
は両方とも URL への変換を行う関数となります。細かくいうと reverse_lazy
の場合は関数ではないのですが、このページでは簡単に関数と呼んでいきます。
また、reverse
と reverse_lazy
は両方とも django.urls
からimport
して利用します。そして、引数には URL の名前
もしくは ビューの関数
を指定します。これにより、引数で与えた URL の名前
もしくは ビューの関数
の URL への変換結果を返却値として取得することができます。
from django.urls import reverse, reverse_lazy
url = reverse(URLの名前 or ビューの関数)
url = reverse_lazy(URLの名前 or ビューの関数)
URL への変換とは
また、URL の名前
とは、urls.py
の urlpatterns
リストの中で実行する “path
関数の name
引数に指定する文字列” のことを言います。
path
関数は下記のように第1引数に URL (URL パターン)、第2引数にビューの関数を指定して実行します。さらに、name
引数を指定することで、第1引数に指定する URL に名前を付けることができます。
from django.urls import path
from . import views
urlpatterns = [
path(URL, views.関数, name=URLの名前),
]
この “name
引数に指定した URL の名前
や第2引数に指定した ビューの関数
から URL を取得すること” を URL の変換と呼んでいます。そして、この URL への変換を行う関数が reverse
や reverse_lazy
となります。イメージとしては下の図のように変換が行われることになります。ただし、この図はあくまでもイメージで、アプリの urls.py
がプロジェクトの urls.py
から include
されるようになっている場合は、/アプリに対する URL/各ビューの URL/
といったように複数の URL が結合された形のものに変換されることになります。
urlpattenrs
と URL への変換
reverse
と reverse_lazy
の違いを理解する上で重要になるのは、この URL の変換は “path
関数の実行結果” を要素とするリスト urlpatterns
に基づいて実施されるという点になります。
例えば appli
というアプリを作成し、プロジェクトの urls.py
とアプリの urls.py
をそれぞれ下記のように作成したとします。
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('appli/', include('appli.urls'))
]
from django.urls import path
from . import views
urlpatterns = [
path('', views.index_view, name='index'),
path('users/', views.users_view, name='userlist'),
]
さらに、そのアプリの views.py
を下記のように作成したとします。この場合、index
関数が実行された際には print
関数で reverse('userlist')
と reverse_lazy('userlist')
の結果が表示されることになります。
from django.http.response import HttpResponse
from django.urls import reverse, reverse_lazy
def users_view(request):
return HttpResponse('users_view')
def index_view(request):
url = reverse('userlist')
print(url)
url = reverse_lazy('userlist')
print(url)
return HttpResponse('index_view')
で、ここでアプリの urls.py
で定義される urlpatterns
に注目すると、'userlist'
という名前は path
関数によって 'users/'
という URL にマッピングされていることが分かります。さらに、プロジェクトの urls.py
によってアプリに対する URL が 'appli/'
に設定されていることが分かります。
そのため、reverse('userlist')
や reverse_lazy('userlist')
を実行した際には、'userlist'
から /appli/users/
という URL に変換されることになります。そしてこれが返却値として取得されます。
このように、URL への変換は、urlpatterns
の各要素(path
関数)に基づいて実施されることになります。
各関数で URL への変換が行われるタイミング
このように、reverse
と reverse_lazy
は両方とも URL への変換を行う関数であり、両方とも urlpatterns
に基づいて URL への変換を行います。ここは共通です。
ですが、reverse
と reverse_lazy
とでは URL への変換が行われるタイミングが異なります。
reverse
での URL の変換実行タイミング
この点を具体例で考えてみましょう!
まず、下記においては url = reverse('userlist')
が実行されたタイミングで URL への変換が行われることになります。reverse
は働き者なので、実行されたタイミングで URL への変換が行われます。
url = reverse('userlist') # ここで変換!
print(url)
reverse_lazy
での URL の変換実行タイミング
それに対し、下記においては url = reverse_lazy('userlist')
が実行されたタイミングでは URL への変換が行われません。この変換が行われるのは次の行の print
関数実行時になります。この行で reverse
関数が実行され、その reverse
関数の URL への変換結果が print
関数によって表示されることになります。
url = reverse_lazy('userlist')
print(url) # ここで変換!
で、ここでサラッっと説明しましたが、reverse_lazy
の場合も結局は reverse
関数を実行することで URL への変換が行われることになります。つまり、reverse_lazy
は reverse
関数を遅延させて実行するための関数となります。遅延させられた reverse
関数が実行される際には、reverse_lazy
の引数に指定したものと同じものが引数に指定されて実行されることになります。なので、当然同じ結果が得られることになります。
そして reverse_lazy
での URL への変換が実行されるタイミングは変換後の URL が実際に必要になった時となります。もっと正確な言い方をすれば、reverse_lazy
の結果を参照する変数が評価される際に reverse
関数が実行されて URL への変換が行われることになります。reverse_lazy
は怠け者なので、実行されたタイミングでは URL への変換が実施されません。仕事を先延ばしし、実際に URL が必要になったときに URL への変換が行われます。
例えば先ほどの例で考えると、print(url)
実行時が変換後の URL が必要になるタイミングになります、これを実行する際に変換後の URL が分からないと print(url)
で URL を出力することができません。したがって、このタイミングで reverse
関数が実行されて URL への変換が行われます。
また、reverse_lazy
を実行したとしても、変換後の URL が必要にならなければ URL への変換は行われないことになります。例えば下記のような処理のみを行なった場合、'userlist'
の URL への変換結果が利用されることはないため、言い換えれば左辺の url
が評価されないため(出力したり URL として利用されたりしない)、変換後の URL は不要です。
そのため、この場合は reverse_lazy
を実行しても URL への変換は行われないことになります。まぁ URL が不要なので当たり前と言えば当たり前です。
url = reverse_lazy('userlist')
このように、reverse_lazy
は URL への変換を遅延して実行する関数となります。この URL への変換は、結局は reverse
関数の実行により実現されます。また、実際に変換後の URL が必要になるまで URL への変換は遅延させられることになります。
URL への変換が行われる回数
もう一点、reverse_lazy
と reverse
の違いを説明しておきます。これは URL への変換が行われる回数になります。
reverse_lazy
の場合、変換後の URL が必要になるたびに URL の変換が行われることになります。reverse
の場合は、reverse
実行時にのみ URL の変換が行われます。
例えば下記のような index_view
が実行された場合、URL への変換が行われるのは reverse
実行時の1回のみとなります。ちなみに HttpResponseRedirect
は HttpResponse
のサブクラスであり、引数で指定した URL へのリダイレクトを行うためのレスポンスとなります。
from django.urls import reverse
from django.http import HttpResponseRedirect
def index_view(request):
url = reverse('comments')
print(url)
return HttpResponseRedirect(url)
それに対し、下記のような index_view
が実行された場合、URL への変換は複数回実行されることになります。具体的に言えば、print(url)
で1回 URL への変換が行われ、さらに HttpResponseRedirect(url)
で2回 URL への変換が行われることになります。要は、reverse_lazy
を実行した場合、変換後の URL が利用されるたびに URL への変換が行われることになります(出力したりリダイレクト先の URL として利用したりするたびに行われる)。
from django.urls import reverse_lazy
from django.http import HttpResponseRedirect
def index_view(request):
url = reverse_lazy('comments')
print(url)
return HttpResponseRedirect(url)
そのため、reverse
の方がパフォーマンスの観点で性能が良いと言えます。
スポンサーリンク
reverse
と reverse_lazy
の使い分け
大体 reverse
と reverse_lazy
との違いについては理解していただけたでしょうか?
でも、reverse_lazy
が URL 変換の実行を遅延させると聞いても「それに何のメリットがあるのか分からない」と感じた方も多いのではないでしょうか?
実は、この reverse_lazy
で URL への変換の実行を遅延させることには明確なメリットがあります。そのメリットとは「reverse_lazy
は urlpatterns
の定義が読み込まれる前に実行できる」という点になります。逆に言えば、reverse
の実行は urlpatterns
の定義が読み込まれる前は実行できません。
この違いがあるため、reverse_lazy
と reverse
とは下記のような考え方で使い分けをしてやれば良いです。
urlpatterns
の定義が読み込まれる前:reverse_lazy
- 上記以外:
reverse
urlpatterns
読み込み前の reverse
実行はダメ
前述の通り、urlpatterns
は urls.py
で定義されるリストで、その定義が読み込まれることで、それ以降にウェブアプリがリクエスト受け取った際には urlpatterns
に従ってリクエストされた URL に応じたビューの関数やクラスのメソッドが実行されるようになります。
そして、reverse
関数では、この urlpatterns
に基づいて URL の名前
or ビューの関数
から URL への変換が行われることになります。要は、この urlpatterns
によって URL と URL の名前
、および、URL と ビューの関数
とのマッピングが管理されています。したがって、reverse
関数で URL への変換を行うためには、事前に urlpatterns
の定義を読み込んでおく必要があります。もし urlpatterns
が読み込まれる前に reverse
関数を実行すれば下記のような例外が発生することになります。
django.core.exceptions.ImproperlyConfigured: The included URLconf 'project.urls' does not appear to have any patterns in it. If you see the 'urlpatterns' variable with valid patterns in the file then the issue is probably caused by a circular import.
前述の通り、reverse
関数では urlpatterns
に基づいて URL への変換が行われますので、urlpatterns
の定義が読み込まれる前に reverse
関数を実行すると例外が発生することに関しては納得していただけるのではないかと思います。
urlpatterns
読み込み前では reverse_lazy
を利用
ですが、reverse_lazy
は怠け者です。実行してもそのタイミングでは URL への変換は行われません。先延ばしされます。したがって、reverse_lazy
自体は urlpatterns
の定義が既に読み込まれているかどうかに関わらず実行可能です。
では、urlpatterns
が読み込まれる前に実行できることに何のメリットがあるのでしょうか?
このメリットが最大に発揮されるのはクラスベースのビューを作成する時になります。クラスベースのビューを作成する場合、reverse_lazy
が大活躍します。なぜなら、クラスベースでビューを作成する場合、urlpatterns
読み込み前の URL の設定が必要となるからです。必ず必要になるというわけでもないですが、多くのケースで urlpatterns
読み込み前の URL の設定が必要となります。続いて、この点について解説していきたいと思います。
reverse
実行による例外の発生
まず、前述でも紹介した urls.py
(アプリ側の) は基本的に下記のように views.py
を import
する形で実装されることになります。これは、views.py
で定義される関数を urls.py
で path
関数の引数に指定する必要があるためです。
from django.urls import path
from . import views
urlpatterns = [
path(URL, views.関数, name=URLの名前),
]
重要なのは views.py
を import
するタイミングで、上記を見れば分かるように、urlpatterns
の定義よりも上側で views.py
の import
が行われることになります。
そのため、views.py
の import
は urlpatterns
の定義の読み込みよりも前に行われます。そして、views.py
が import
される際には、定義されている関数やクラス等が解釈され、import
したモジュール側でこれらの関数やクラスが参照可能となります。
このときに views.py
で定義される関数やクラスのメソッドは基本的には実行されず、前述の通り解釈されるだけになります。ですので、views.py
で定義する関数やメソッドの中で reverse
を実行するようにしても、結局 urlpatterns
の定義が読み込まれた後に実行されるため問題ありません。ですが、クラス変数の設定に関しては import
時に行われることになります。下の図で言えば、青背景部分は import
時に実行されませんが、オレンジ背景部分は import
時に実行されることになります。
したがって、クラス変数に URL を設定したくてクラス変数の右辺に reverse
関数を記述した場合、views.py
を import
するタイミングで reverse
関数が実行されることになります。
で、このタイミングでは urlpatterns
の定義が読み込まれていないため、reverse
関数が実行された際に例外が発生します。つまり、下記のようなクラスは views.py
には定義不可ということになります。
from .forms import PostForm
from django.views.generic import CreateView
from django.urls import reverse
class Post(CreateView):
form_class = PostForm
template_name = 'forum/post.html'
success_url = reverse('comments')
reverse_lazy
への置き換えによる例外の解消
ですが、上記のようにクラス変数に URL を設定するという定義の仕方は Django のクラスベースビューを作成する際の典型的な実装パターンとなります。上記でも定義している success_url
は、例えばレコードの新規作成等に成功した際にリダイレクトする先の URL を設定するためのクラス変数になります。この success_url
はクラスベースビューでビューを作成する際には多くの場合で定義が必要になります。
では、このようなクラスの定義はどのようにして実現されているのでしょうか?
ここまで読んでくださった方であればもうお分かりですね!?
そうです。reverse
ではなく reverse_lazy
を利用すれば良いだけです。
例えば先ほどの例であれば、reverse
実行部分を reverse_lazy
に置き換えてやるだけで例外の発生を防ぐことができます。なぜなら、reverse_lazy
は怠け者で、この時点では URL の変換は行われないからです。なので、この時点では urlpatterns
の定義は不要です。
from .forms import PostForm
from django.views.generic import CreateView
from django.urls import reverse_lazy
class Post(CreateView):
form_class = PostForm
template_name = 'forum/post.html'
success_url = reverse_lazy('comments')
さらに、この reverse_lazy
によって URL への変換が遅延されたのち、views.py
の import
が完了して urls.py
で urlpatterns
の定義の読み込みが行われます。そして、その後であれば success_url
の評価が行われるタイミング、すなわち変換後の URL が必要になったタイミングで reverse
が実行されて URL の取得を行うことができることになります。
このように、urls.py
から views.py
が import
されるようになっているため、urlpatterns
の定義が読み込まれる前に views.py
の処理が実行されることがあります。そして、そこで働き者の reverse
関数を実行すると例外が発生することになります。ですが、怠け者の reverse_lazy
であればそれを解決することができます。まさに怠け者であることを利用したナイスな解決策ですよね!
特に、クラスベースでビューを作成している場合、クラス変数に URL の設定を行うのは典型的な実装パターンとなっていますので、クラスベースでビューを作成する際は特に注意が必要となります。
逆に言えば、それ以外で reverse_lazy
を使う機会は少ないと思います。ですので、まずは views.py
で定義するクラス変数に URL を設定する場合のみ reverse_lazy
を利用するようにし、他の箇所では URL への変換には revese
を使うようにする考え方で使い分けをすれば良いと思います。
ここまでの説明を読んで、常に reverse_lazy
を使えば良いと感じた方もおられるかもしれませんが、URL への変換が行われる回数 で解説したようにパフォーマンスの観点では reverse
の方が優れています。ですので、reverse_lazy
に関しては必要な箇所でのみ利用し、他の箇所では reverse
を使うようにした方が良いです。
また、これらの使い分けが間違っている場合は下記のような例外が発生することになります。この例外は reverse
を利用している箇所を reverse_lazy
に変更することで解決できる可能性が高いので、このことも覚えておきましょう!
django.core.exceptions.ImproperlyConfigured: The included URLconf 'project.urls' does not appear to have any patterns in it. If you see the 'urlpatterns' variable with valid patterns in the file then the issue is probably caused by a circular import.
スポンサーリンク
まとめ
このページでは Django の reverse
と reverse_lazy
について解説を行いました!
両方とも URL の名前 や ビューの関数 から URL への変換を行うものになりますが、これらでは URL への変換が行われるタイミングが異なります。reverse
は働き者なので実行されたタイミングですぐに URL への変換を行います。それに対し、reverse_lazy
は怠け者なので実行されたタイミングでは URL への変換を行わず、URL が実際に必要になったタイミングで URL への変換が行われることになります。
そして、この違いがあるため、クラスベースのビューでクラス変数に対して定義時に URL の設定を行いたい場合は reverse_lazy
を利用する必要があります。なぜなら、クラス変数の定義時には urlpatterns
の定義がまだ読み込まれていないからです。reverse_lazy
によって URL への変換を urlpatterns
の定義読み込み後まで先延ばししてやれば、urlpatterns
の定義が読み込まれていないタイミングでもクラス変数の定義自体は可能となります。
ということで、クラスベースのビューでクラス変数に対して定義時に URL の設定を行いたい場合は reverse_lazy
を利用するようにしましょう!
また、この lazy
という言葉はプログラミングをしていると見かけることも多いので、是非この意味合いやニュアンスについては覚えておいてください!