mizzsugar’s blog

Pythonで学んだことや読書録を書きます。

try/except(try/catch)の処理順序を理解する

try/exceptの処理の順序がよくわかっていないことがわかったので、自分の理解のために書きます。

今回は、Djangoでユーザー登録する機能を例にします。

パスワードが以下の条件を満たしていなかったらエラーとなり、

  • 8文字以上

  • 半角英字と半角数字の両方から成り立っている

を満たしていたら、データベースにユーザーの情報が登録されて、ログインした状態でブログの投稿画面に移動します。

f:id:mizzsugar:20180628210859p:plain
ユーザー登録画面です。


urls.pyで URLを指定します。 今回は、必要な分だけ設定します。

urls.py

from django.contrib import admin
from django.urls import path

import myApp.views

urlpatterns = [
    path("post_new_post", myApp.views.post_new_post),
    path('admin/', admin.site.urls),
    path("registration", myApp.views.registation_user)
]
# post_new_postは、ブログ投稿用のURL
# adminは、アプリ管理者しか入れない、管理用のURL
# registrationは、ユーザー登録用の URL

forms.py

from django import forms

class RegistrationForm(forms.Form): # 登録画面で入力するフォームのフィールドを定義します。
    username = forms.CharField()
    password = forms.CharField()
    email = forms.EmailField()

exceptions.pyでは、例外の設定をします。

exceptions.py

class MyAppError(Exception): # Exceptionクラスを継承するエラークラスを定義します。
    pass

class RegistrationError(MyAppError): # Exceptionクラスを継承するエラークラスを引数とします。
    def __init__(self, form): # Registration classのインスタンスを生成するために、__init__メソッドを定義します。例外クラスのインスタンスに、フォームを引数に持たせます。
        self.form = form # エラーになったフォームを例外に持たせて、エラーの詳細としています。

domain.pyでは、アプリのコアとなる処理を設定します。

domain.py

import myApp.exceptions
import uuid
import re
import myApp.forms
from django.contrib.auth.models import User


def has_digit(text): # 文字列に数字を含むかどうかを判定する関数です。
    if re.search("\d", text): 
        return True
    return False

def has_alphabet(text): # 文字列に英字(小文字または大文字)を含むかどうかを判定する関数です。
    if re.search("[a-zA-Z]", text):
        return True
    return False


def register(user_profile): # ユーザー情報を登録する関数です。
    registration_form = myApp.forms.RegistrationForm(user_profile)
    password = registration_form.password
    if len(password) < 8:
        registration_form.add_error('password', "文字数が8文字未満です。") # まず、エラーがあるか判定します。文字列が8文字未満なら、「文字数が8文字未満です。」というエラーをフォームに追加します。
    if not has_digit(password):
        registration_form.add_error('password',"数字が含まれていません") # has_digitがfalseなら、「数字が含まれていません」というエラーをフォームに追加します。
    if not has_alphabet(password): # has_alphabetがfalseなら、「アルファベットが含まれていません」というエラーをフォームに追加します。
        registration_form.add_error('password',"アルファベットが含まれていません")
    if registration_form.has_error('password'): # パスワードがエラーの条件に合致するか判定し、エラーがある場合フォームに追加した後、フォームがエラーを持っているか判定します。
        raise myApp.exceptions.RegistrationError(registration_form) # エラーを持っている場合、RegistrationErrorを発生させます。RegistrationErrorのコンストラクタの引数は、registration_formとします。
    user = User.objects.create_user(
        username=registration_form.username, password=password, email=registration_form.email) # エラーを持っていないと判定された場合、まずデータベースにユーザー情報を登録します。
    return user # 登録した後、登録した内容を代入された変数userを返します。

views.pyでは、http通信する際の処理を設定します。

views.py

import django.http
from django.shortcuts import render
import re
from django.contrib.auth import authenticate, login as django_login
import myApp.exceptions
import myApp.domain


def get_registration_user(request):# URL'registration'でgetメソッドが発生した時に利用する関数
    registration_form = minsta.forms.RegistrationForm()
    return render(request, 'registration.html', {'registration_form': registration_form})

def post_registration_user(request): #  URL'registration'でpostメソッドが発生した時に利用する関数
    try:
        user = myApp.domain.register(request.POST) #まず、tryでdomain.pyで設定したregister関数を実行します。引数user_profileには、HTMLフォームで送信された内容(request.POST)を入れます。
    except myApp.exceptions.RegistrationError as e: # try文でRegistrationErrorという例外が発生した場合
        return render(request, 'registration.html', {'registration_form': e.registration_form, 'user':request.user}) # registration.htmlに移動します。RegistrationErrorのインスタンスに含まれているフォームを利用します。
    django_login(request, user) # 例外が発生せずユーザー情報が登録されたら、userにはユーザー情報が含まれます。それを利用してlogin関数でログインします。
    return django.http.HttpResponseRedirect('/post_new_post') # 例外が発生しなかった場合、ログインした後に、'post_new_post'に移動します。

def registration_user(request):
    if request.method == 'POST': #リクエストメソッドにPOSTが利用されたら、post_registration_userの結果を返します。
        return post_registration_user(request)
    return get_registration_user(request) #リクエストメソッドにGEETが利用されたら、get_registration_userの結果を返します。

def post_new_post(request): # post_new_postが呼び出された時の関数です。
    return render(request, 'new_post.html', {'form': form, 'user':request.user})
# 今回は、投稿画面に移動するだけとします。

try/exceptを利用しているメソッドを見てみます。

# views.pyの一部抜粋
def post_registration_user(request):
    try:
        user = myApp.domain.register(request.POST) # この処理が1番目に実行する処理。
    except myApp.exceptions.RegistrationError as e: # 「except 例外」に、tryの実行結果で「except 例外」で指定した例外が発生されている場合の処理を書きます。例外が発生されているかどうかを判定するのが2番目の処理。
        return render(request, 'registration.html', {'registration_form': e.registration_form, 'user':request.user}) # tryの実行結果にRegistrationErrorが発生していたら、これが3番目の処理。
    django_login(request, user) # 例外が発生していなかったら、これが3番目の処理。domain.pyですでに例外が発生していなかったらユーザー情報をデータベースに登録する処理をするよう設定しているので、views.pyで設定する必要はありません。
    return django.http.HttpResponseRedirect('/post_new_post') # 例外が発生しなかった場合、これが4番目の処理

ーーー勘違いしていたことーーー

最初、try節の文はif文のように条件を示すだけで実行していないかと思っていました。

if文とは違って、try文は実行されているのですが・・・

try文の中にある、

user = ....

は、ただ単にuserという変数を代入しているだけで

評価していないかと思っていました。

しかし、実際は

except以下で、まずエラーをキャッチするか判断し

エラーがなければ

user = ...

でユーザーがデータベースに登録され、かつ代入もされる動きでした。

これからは、

tryは実行されるということと、

exceptと例外が発生しなかった時の処理に関して

上から判定・処理されることを意識してプログラムを書きます。