Udemy講座 Python MVCモデル View

Python学習【365日チャレンジ!】111日目のマスターU(@Udemy11)です。

1が3つ揃うと嬉しくなっちゃいませんか?

Python学習をはじめて今日で111日目になりましたが、最初の頃の何もわからなかった状態に比べて、できることはかなり増えているように感じますが、今回のJarvisのコードを学習していると、まだまだたくさん学ばなければいけないことがあるな〜と改めて感じています。

特に今日からコードを学習していくMVCモデルについてはわからないことだらけなので、何度も繰り返して学習していこうと思っています。

ということで、今日もPython学習にはいりましょう!

昨日の復習

昨日は、アプリケーションを開発する上での基本的な考え方であるMVCモデルで対話アプリJarvisの設計を学習しました。

  • model
  • view
  • controller

上記の要素に加えて、Jarvisが話す文章であるtemplatesフォルダが加えられていました。

全くの初心者にとっては、最初書いたコードのように、一つのファイルでまとめてしまうほうがわかりやすいのですが、大きなプログラムを扱うためには、MVCモデルでパーツを切り分けてコードが書けるようにしなければいけません。

JarvisのMVCモデルの設計については、昨日の記事を参考にしてください。

本日は、具体的にJarvisのviewに関するコードを学習していきます。

console.py

viewフォルダに入っているconsole.pyの中身を見てみると、参照するテンプレートのパスを指定するコードと質問を表示するフォーマットを整形するコードが書かれています。

console.pymodelフォルダにあるrobot.pyからテンプレートのパスや表示フォーマットを参照するためのファイルで、取得したソースをどのように表示するのかを指定しています。

コードは次のようになっています。

import os
import string


def get_template_dir_path():
    template_dir_path = None
    try:
        import settings
        if settings.TEMPLATE_PATH:
            template_dir_path = settings.TEMPLATE_PATH
    except ImportError:
        pass

    if not template_dir_path:
        base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        template_dir_path = os.path.join(base_dir, 'templates')

    return template_dir_path


class NoTemplateError(Exception):
    pass


def find_template(temp_file):
    template_dir_path = get_template_dir_path()
    temp_file_path = os.path.join(template_dir_path, temp_file)
    if not os.path.exists(temp_file_path):
        raise NoTemplateError('次のファイルがありません。 {}'.format(temp_file))
    return temp_file_path


def get_template(template_file_path):
    template = find_template(template_file_path)
    with open(template, 'r', encoding='utf-8') as template_file:
        contents = template_file.read()
        contents = contents.rstrip(os.linesep)
        contents = '{splitter}{sep}{contents}{sep}{splitter}{sep}'.format(
            contents=contents, splitter="/" * 60, sep=os.linesep)
        return string.Template(contents)

上から順番にみていきましょう。

最初に、ファイルを扱うosモジュールと文字列を操作するstringモジュールをインポートしています。

def get_template_dir_path()

def get_template_dir_path():
    template_dir_path = None
    try:
        import settings
        if settings.TEMPLATE_PATH:
            template_dir_path = settings.TEMPLATE_PATH
    except ImportError:
        pass

5〜12行目で、テンプレートのあるディレクトリのパスに関する変数template_dir_pathNoneに定義して、try-except文を使って、setting.pyをインポートしたあと、setting.pyに定義されている変数TEMPLATE_PATHを最初に定義した空の変数template_dir_pathに代入しています。

ちなみに、setting.pyを作る場合、中身はTEMPLATE_PATH = '/temp/templates/'という感じでテンプレートを入れるフォルダのパスを指定したコードか、= Noneを指定します。

exceptで、setting.pyが存在せずに、ImportErrorが起きたときは、何もしません。

    if not template_dir_path:
        base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        template_dir_path = os.path.join(base_dir, 'templates')

    return template_dir_path

次の14行目15行目では、変数template_dir_pathFalseの場合(つまり空の場合)、os.path.abspath(__file__)で、現在のファイルの絶対パスを取得し、os.path.dirnameでそのディレクトリ名を取得し、os.path.dirnameでさらに上の階層のディレクトリ名を取得して、変数base_dirに代入しています。

つまり、この対話アプリJarvisのメインファイルであるmain.pyがある階層の絶対パスを取得して、変数base_dirに渡しています。

16行目のos.path.joinで、取得した絶対パスが入った変数base_direとテンプレートが入ったフォルダ名templatesをくっつけて、templatesフォルダの絶対パスを変数template_dir_pathに入れて、18行目で返り値に変数template_dir_pathを指定しています。

ここまでが最初の関数ですが、これはパッケージがインポートされたときに、テンプレートのフォルダを絶対パスで参照できるようにするための関数です。

関数名get_template_dir_pathのとおり、テンプレートのディレクトリの絶対パスを取得しているわけですね。

class NoTemplateError(Exception)

class NoTemplateError(Exception):
    pass

21行目のクラスは、独自例外のクラスです。

Exceptionを継承したユーザー定義の独自例外クラスを作ることで、エラーの原因を特定することに役立てています。

ここで作成した独自例外は、次の関数で組み込まれています。

def find_template(temp_file)

次に用意されている関数はfind_templateで、その名のとおり、テンプレートを見つける関数です。

def find_template(temp_file):
    template_dir_path = get_template_dir_path()
    temp_file_path = os.path.join(template_dir_path, temp_file)
    if not os.path.exists(temp_file_path):
        raise NoTemplateError('次のファイルがありません。 {}'.format(temp_file))
    return temp_file_path

26行目では、上記の関数get_template_dir_pathを変数template_dir_pathに代入し、27行目で、変数template_file_pathos.path.joinを使って、変数temp_fileの絶対パスを代入しています。

次に、28行目から30行目で、変数temp_fileが存在しなければ、raiseで独自例外であるNotemplateErrorを発生させて、次のファイルがありません。(ファイル名)と表示させますが、問題なければ、変数template_file_pathを返します。

def get_template(template_file_path)

この関数がmodelフォルダのrobot.pyから呼び出される関数になるわけですが、get_template('テキストファイルの名前')という形で呼び出されます。

この関数のコードを確認してみましょう。

def get_template(template_file_path):
    template = find_template(template_file_path)
    with open(template, 'r', encoding='utf-8') as template_file:
        contents = template_file.read()
        contents = contents.rstrip(os.linesep)
        contents = '{splitter}{sep}{contents}{sep}{splitter}{sep}'.format(
            contents=contents, splitter="/" * 60, sep=os.linesep)
        return string.Template(contents)

引数template_file_pathに、テキストファイルの名前が入り、34行目で引数とともに、一つ前の関数find_template(template_file_path)に渡されています。

変数templateには、上記で解説した関数が実行され、返り値temp_file_pathが返されることになるので、最終的にget_templateの引数であるテキストファイルの絶対パスが代入されます。

35行目以降は、指定されたテキストファイルを開いて次のような表示形式に整えたテンプレート文字列を返り値として返します。

////////////////////////////////////////////////////////////
テンプレートファイルの中身
////////////////////////////////////////////////////////////

テンプレートファイルは、最初の設計の記事で解説したhello.txtrecommend.txtlike.txtgoodby.txtが用意されています。

つながりを見つける

酒井さんの講座を受講しながら、対話アプリの模範コードを読み解いているのですが、MVCモデルのどのファイルから取りかかればいいのか全くわからなかったため、まずは、どのファイルもインポートしていないviewフォルダ内のconsole.pyから取り掛かってみました。

コードの読み取りでは、まず最初に実行するmain.pyを読んで、その中でインポートされているconversation.pyrobot.pyconsole.pyという具合にコードをたどって行けます。

標準ライブラリ以外をインポートしていれば、そのPythonファイルは、どれかのPythonファイルにつながっているので、コードを読んでいる途中で、つながった先のPythonファイルが必要になってきます。

なのでまずは、標準ライブラリ以外をインポートしていないファイルからコードを作っていくのがいいのかと考えて、view要素を担当するconsole.pyから取り掛かることにしたわけです。

最後の方では、他のファイルから関数が呼び出されて、実際の引数が与えられるので、ちょっとイメージが難しかったのですが、今回わかったのは、次の3つのポイントです。

  • デフォルトのテンプレートがあるが、ユーザーが作ることも可能にしている
  • テンプレートの絶対パスを取得している
  • テンプレートを表示するフォーマットを定義している

表示に関する要素なので、デフォルトのテンプレートや表示するためのフォーマットは用意しているだけでなく、setting.pyでユーザーが作成したテンプレートを使うことができるようになっています。

この対話アプリにどの程度拡張性があるのかはわかりませんが、要素を分けて作成することで、触られたくない部分は触られずに、変更できるところは変更できるように切り分けることができるということをなんとなく理解することができました。

次回は、CSVを扱うモデル部分であるmodelフォルダのranking.pyを読み解いていこうと思います。

それでは明日も、Good Python!