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.py
model
フォルダにある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_path
をNone
に定義して、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_path
がFalse
の場合(つまり空の場合)、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_path
にos.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.txt
、recommend.txt
、like.txt
、goodby.txt
が用意されています。
つながりを見つける
酒井さんの講座を受講しながら、対話アプリの模範コードを読み解いているのですが、MVCモデルのどのファイルから取りかかればいいのか全くわからなかったため、まずは、どのファイルもインポートしていないview
フォルダ内のconsole.py
から取り掛かってみました。
コードの読み取りでは、まず最初に実行するmain.py
を読んで、その中でインポートされているconversation.py
→robot.py
→console.py
という具合にコードをたどって行けます。
標準ライブラリ以外をインポートしていれば、そのPythonファイルは、どれかのPythonファイルにつながっているので、コードを読んでいる途中で、つながった先のPythonファイルが必要になってきます。
なのでまずは、標準ライブラリ以外をインポートしていないファイルからコードを作っていくのがいいのかと考えて、view
要素を担当するconsole.py
から取り掛かることにしたわけです。
最後の方では、他のファイルから関数が呼び出されて、実際の引数が与えられるので、ちょっとイメージが難しかったのですが、今回わかったのは、次の3つのポイントです。
- デフォルトのテンプレートがあるが、ユーザーが作ることも可能にしている
- テンプレートの絶対パスを取得している
- テンプレートを表示するフォーマットを定義している
表示に関する要素なので、デフォルトのテンプレートや表示するためのフォーマットは用意しているだけでなく、setting.py
でユーザーが作成したテンプレートを使うことができるようになっています。
この対話アプリにどの程度拡張性があるのかはわかりませんが、要素を分けて作成することで、触られたくない部分は触られずに、変更できるところは変更できるように切り分けることができるということをなんとなく理解することができました。
次回は、CSVを扱うモデル部分であるmodel
フォルダのranking.py
を読み解いていこうと思います。
それでは明日も、Good Python!