【Django】reverseとreverse_lazyの違い

reverseとreverse_lazyの違いの解説ページアイキャッチ

このページにはプロモーションが含まれています

このページでは、Django フレームワークで定義される reversereverse_lazy の違いについて説明していきます。

Django の解説が行われているサイトや参考書等で reversereverse_lazy が使用されているサンプルソースコードを見たことのある方も多いのではないでしょうか?

これらは似た関数ではあるものの当然違いがあり、その違いを認識して使い分ける必要があります。では、具体的にどのような違いがあるのでしょうか?その点について、このページで解説していきたいと思います。

reversereverse_lazy の違い

では reversereverse_lazy の違いについて説明していきます。

URL に変換するタイミングが異なる

最初に結論を言ってしまいます!

reversereverse_lazy は両方とも URL の名前ビューの関数 を URL に変換する関数になります。この点に関しては reversereverse_lazy とで共通です。

ですが、この2つの関数では URL への変換を行うタイミングが異なります。

reverse の場合、実行されたタイミングで URL への変換が行われます。

reverseの説明図

それに対し、reverse_lazy の場合、実行されたタイミングではなく、URL が実際に必要になったタイミングで URL への変換が行われます。つまり、URL への変換が遅延・先延ばしされて実行されることになります。

reverse_lazyの説明図

lazy は「怠けている」等の意味を示す単語です。プログラミングなどでは遅延させる場合や先延ばしする場合に利用される単語となります。

つまり、reverse_lazy は怠け者なので、実行されてもすぐに URL への変換は行ってくれません。そして、実際に URL が必要になった時に慌てて URL への変換を行うようになっています。こういう人って結構いますよね…笑。

reverse_lazyが怠け者であることの説明図

ということで、reverse_lazy は怠け者なので使わないようにしましょう!という結論になりそうなものですが、そうではないです。この「怠け者」という特徴を活かすことで reverse で実現できないことが実現できるようになります。

具体的には、クラスベースでビューを作成する際には働き者の reverse ではなく怠け者の reverse_lazy を利用する必要があります。それ以外で URL への変換が必要な際には基本的に reverse を利用すれば良いです。

以上が reversereverse_lazy の違いの結論になります。

ここからは、上記で説明している内容の説明の補足をしていきます。まず reversereverse_lazy について説明し、それらの特徴を踏まえてクラスベースでビューを作成する際に reverse_lazy を利用する必要がある理由について説明していきます。

スポンサーリンク

reversereverse_lazy

では、reversereverse_lazy について説明していきたいと思います。

両方とも URL への変換を行う関数

まず、reversereverse_lazy は両方とも URL への変換を行う関数となります。細かくいうと reverse_lazy の場合は関数ではないのですが、このページでは簡単に関数と呼んでいきます。

また、reversereverse_lazy は両方とも django.urls からimportして利用します。そして、引数には URL の名前 もしくは ビューの関数 を指定します。これにより、引数で与えた URL の名前 もしくは ビューの関数 の URL への変換結果を返却値として取得することができます。

reverseとreverse_lazy
from django.urls import reverse, reverse_lazy

url = reverse(URLの名前 or ビューの関数)
url = reverse_lazy(URLの名前 or ビューの関数)

URL への変換とは

また、URL の名前 とは、urls.pyurlpatterns リストの中で実行する “path 関数の name 引数に指定する文字列” のことを言います。

path 関数は下記のように第1引数に URL (URL パターン)、第2引数にビューの関数を指定して実行します。さらに、name 引数を指定することで、第1引数に指定する URL に名前を付けることができます。

path関数とURLの名前
from django.urls import path
from . import views

urlpatterns = [
    path(URL, views.関数, name=URLの名前),
]

このname 引数に指定した URL の名前 や第2引数に指定した ビューの関数 から URL を取得すること” を URL の変換と呼んでいます。そして、この URL への変換を行う関数が reversereverse_lazy となります。イメージとしては下の図のように変換が行われることになります。ただし、この図はあくまでもイメージで、アプリの urls.py がプロジェクトの urls.py から include されるようになっている場合は、/アプリに対する URL/各ビューの URL/ といったように複数の URL が結合された形のものに変換されることになります。

URLへの変換の意味合いについての説明図

urlpattenrs と URL への変換

reversereverse_lazy の違いを理解する上で重要になるのは、この URL の変換は “path 関数の実行結果” を要素とするリスト urlpatterns に基づいて実施されるという点になります。

例えば appli というアプリを作成し、プロジェクトの urls.py とアプリの 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'))
]
アプリのurls.py
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') の結果が表示されることになります。

views.py
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への変換が行われるタイミング1
url = reverse('userlist') # ここで変換!
print(url)

reverse_lazy での URL の変換実行タイミング

それに対し、下記においては url = reverse_lazy('userlist') が実行されたタイミングでは URL への変換が行われません。この変換が行われるのは次の行の print 関数実行時になります。この行で reverse 関数が実行され、その reverse 関数の URL への変換結果が print 関数によって表示されることになります。

URLへの変換が行われるタイミング2
url = reverse_lazy('userlist')
print(url) # ここで変換!

で、ここでサラッっと説明しましたが、reverse_lazy の場合も結局は reverse 関数を実行することで URL への変換が行われることになります。つまり、reverse_lazyreverse 関数を遅延させて実行するための関数となります。遅延させられた reverse 関数が実行される際には、reverse_lazy の引数に指定したものと同じものが引数に指定されて実行されることになります。なので、当然同じ結果が得られることになります。

そして reverse_lazy での URL への変換が実行されるタイミングは変換後の URL が実際に必要になった時となります。もっと正確な言い方をすれば、reverse_lazy の結果を参照する変数が評価される際reverse 関数が実行されて URL への変換が行われることになります。reverse_lazy は怠け者なので、実行されたタイミングでは URL への変換が実施されません。仕事を先延ばしし、実際に URL が必要になったときに URL への変換が行われます。

reverse_lazyでURLへの変換が行われるタイミングの説明図

例えば先ほどの例で考えると、print(url) 実行時が変換後の URL が必要になるタイミングになります、これを実行する際に変換後の URL が分からないと print(url) で URL を出力することができません。したがって、このタイミングで reverse 関数が実行されて URL への変換が行われます。

また、reverse_lazy を実行したとしても、変換後の URL が必要にならなければ URL への変換は行われないことになります。例えば下記のような処理のみを行なった場合、'userlist' の URL への変換結果が利用されることはないため、言い換えれば左辺の url が評価されないため(出力したり URL として利用されたりしない)、変換後の URL は不要です。

そのため、この場合は reverse_lazy を実行しても URL への変換は行われないことになります。まぁ URL が不要なので当たり前と言えば当たり前です。

URLの変換が行われない場合
url = reverse_lazy('userlist')

このように、reverse_lazy は URL への変換を遅延して実行する関数となります。この URL への変換は、結局は reverse 関数の実行により実現されます。また、実際に変換後の URL が必要になるまで URL への変換は遅延させられることになります。

URL への変換が行われる回数

もう一点、reverse_lazyreverse の違いを説明しておきます。これは URL への変換が行われる回数になります。

reverse_lazy の場合、変換後の URL が必要になるたびに URL の変換が行われることになります。reverse の場合は、reverse 実行時にのみ URL の変換が行われます。

例えば下記のような index_view が実行された場合、URL への変換が行われるのは reverse 実行時の1回のみとなります。ちなみに HttpResponseRedirectHttpResponse のサブクラスであり、引数で指定した URL へのリダイレクトを行うためのレスポンスとなります。

reverseでの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 として利用したりするたびに行われる)。

reverse_lazyでの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 の方がパフォーマンスの観点で性能が良いと言えます。

スポンサーリンク

reversereverse_lazy の使い分け

大体 reversereverse_lazy との違いについては理解していただけたでしょうか?

でも、reverse_lazy が URL 変換の実行を遅延させると聞いても「それに何のメリットがあるのか分からない」と感じた方も多いのではないでしょうか?

実は、この reverse_lazy で URL への変換の実行を遅延させることには明確なメリットがあります。そのメリットとは「reverse_lazy は urlpatterns の定義が読み込まれる前に実行できる」という点になります。逆に言えば、reverse の実行は urlpatterns の定義が読み込まれる前は実行できません。

この違いがあるため、reverse_lazyreverse とは下記のような考え方で使い分けをしてやれば良いです。

  • urlpatterns の定義が読み込まれる前:reverse_lazy
  • 上記以外:reverse

urlpatterns 読み込み前の reverse 実行はダメ

前述の通り、urlpatternsurls.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.pyimport する形で実装されることになります。これは、views.py で定義される関数を urls.pypath 関数の引数に指定する必要があるためです。

アプリのurls.py
from django.urls import path
from . import views

urlpatterns = [
    path(URL, views.関数, name=URLの名前),
]

重要なのは views.pyimport するタイミングで、上記を見れば分かるように、urlpatterns の定義よりも上側で views.py の import が行われることになります。

そのため、views.py の importurlpatterns の定義の読み込みよりも前に行われます。そして、views.pyimport される際には、定義されている関数やクラス等が解釈され、import したモジュール側でこれらの関数やクラスが参照可能となります。

このときに views.py で定義される関数やクラスのメソッドは基本的には実行されず、前述の通り解釈されるだけになります。ですので、views.py で定義する関数やメソッドの中で reverse を実行するようにしても、結局 urlpatterns の定義が読み込まれた後に実行されるため問題ありません。ですが、クラス変数の設定に関しては import 時に行われることになります。下の図で言えば、青背景部分は import 時に実行されませんが、オレンジ背景部分は import 時に実行されることになります。

import時に実行される処理とされない処理

したがって、クラス変数に URL を設定したくてクラス変数の右辺に reverse 関数を記述した場合、views.py を import するタイミングで reverse 関数が実行されることになります。

reverseを実行するとurlpatternsの定義が読み込まれる前にURLの変換が行われてしまうことを示す図

で、このタイミングでは 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.pyimport が完了して urls.pyurlpatterns の定義の読み込みが行われます。そして、その後であれば success_url の評価が行われるタイミング、すなわち変換後の URL が必要になったタイミングで reverse が実行されて URL の取得を行うことができることになります。

reverse_lazyを実行するとurlpatternsの定義が読み込まれた後にURLの変換が行わレルようになることを示す図

このように、urls.py から views.pyimport されるようになっているため、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 という言葉はプログラミングをしていると見かけることも多いので、是非この意味合いやニュアンスについては覚えておいてください!

同じカテゴリのページ一覧を表示