Python学習【365日チャレンジ!】113日目のマスターU(@Udemy11)です。
昨日もついついAmazonプライムで明け方までシリーズ物を観てしまい、寝不足絶好調です。
前にテレビでちらっと見てからちょっとだけ気になっていたアニメ【Dr.STONE】のシリーズがAmazonプライムにあったので、ちょっとだけ観ようとおもったのが最後、すべてのエピソードをコンプリートしてしまいました。
このままでは単行本にも手を出してしまいそうです。
ほんとやばいですAmazonプライム
それでは、今日もPython学習にはいりましょう!
昨日の復習
昨日は、model
フォルダに用意するCSVファイルの操作をするためのranking.py
について学習しました。
CSVからデータを取り出したり並べ替えたリしたデータを活用したり、書き込んだりするパートを担当していましたが、ファイルのパスを取得するコードは、2日前に学習したconsole.py
と同じような処理ができました。
ranking.py
は、CSVの書き込み読み込みを担当するので、少しコードが難しくなりましたが、非常に重要なファイルなので、しっかりと昨日の記事で復習しておきましょう!
それでは、いよいよJarvisの本体ともいえるrobot.py
のコードをみていきましょう!
robot.py
Jarvisの対話をコントロールするrobot.py
は用意した会話を参照して、ユーザーの情報をinput()
で取得するコードがメインとなっています。
from jarvis.models import ranking
from jarvis.views import console
DEFAULT_ROBOT_NAME = 'Jarvis'
class Robot(object):
def __init__(self, name=DEFAULT_ROBOT_NAME, user_name=''):
self.name = name
self.user_name = user_name
def hello(self):
while True:
template = console.get_template('hello.txt')
user_name = input(template.substitute({'robot_name': self.name}))
if user_name:
self.user_name = user_name.title()
break
class SportsRobot(Robot):
def __init__(self, name=DEFAULT_ROBOT_NAME):
super().__init__(name=name)
self.ranking_model = ranking.RankingModel()
def _hello_decorator(func):
def wrapper(self):
if not self.user_name:
self.hello()
return func(self)
return wrapper
@_hello_decorator
def recommend_sport(self):
new_recommend_sport = self.ranking_model.get_most_popular()
if not new_recommend_sport:
return None
finish_recommend_sports = [new_recommend_sport]
while True:
template = console.get_template('recommend.txt')
is_yes = input(template.substitute({
'robot_name': self.name,
'user_name': self.user_name,
'sport': new_recommend_sport
}))
if is_yes.upper() == 'Y' or is_yes.upper() == 'YES':
break
if is_yes.upper() == 'N' or is_yes.upper() == 'NO':
new_recommend_sport = self.ranking_model.get_most_popular(not_list=finish_recommend_sports)
if not new_recommend_sport:
break
finish_recommend_sports.append(new_recommend_sport)
@_hello_decorator
def ask_user_favorite(self):
while True:
template = console.get_template('like.txt')
sport = input(template.substitute({
'robot_name': self.name,
'user_name': self.user_name,
}))
if sport:
self.ranking_model.increment(sport)
break
@_hello_decorator
def thank_you(self):
template = console.get_template('goodby.txt')
print(template.substitute({
'robot_name': self.name,
'user_name': self.user_name,
}))
robot.py
がインポートするモジュールは、昨日学習したranking.py
とview
フォルダ内のconsole.py
になります。
ranking.py
は、同じ階層にあるので、import ranking
だけでも問題はないのですが、パッケージの場合は、メインの実行ファイルであるmain.py
からのパスを指定することが推奨されているので、jarvis
フォルダからのパスで指定しています。
モジュールをインポートしたあとは、5行目のDEFAULT_ROBOT_NAME = 'Jarvis'
で、対話ロボットの名前を、グローバル変数に代入しています。
次にそれぞれのクラスと関数を個別にチェックしていきます。
class Robot(object)
最初に定義するのは、対話ロボットのベースクラスです。
class Robot(object):
def __init__(self, name=DEFAULT_ROBOT_NAME, user_name=''):
self.name = name
self.user_name = user_name
ベースクラスでは、初期化処理として、グローバル変数DEFAULT_ROBOT_NAME
に代入したJarvis
をself.name
に代入し、後ほど取得する引数user_name
を空にしたものをself.user_name
に代入しています。
def hello(self)
ベースクラスには、初期化以外に、挨拶をするhello(self)
関数を定義しています。
def hello(self):
while True:
template = console.get_template('hello.txt')
user_name = input(template.substitute({'robot_name': self.name}))
if user_name:
self.user_name = user_name.title()
break
この関数では、while
ステートメントでuser_name
を取得するまで繰り返すループが定義されています。
変数template
にインポートしたconsole
モジュールのget_template()
に引数hello.txt
にした返り値を代入します。
こちらの記事を見ればわかるように、変数template
には、hello.txt
を次のように整形した値が代入されます。
////////////////////////////////////////////////////////////
hello.txtの中身
////////////////////////////////////////////////////////////
17行目では、テンプレートの中の文章で定義されている変数robot_name
にself.name
を代入した文章を表示し、ユーザーからインプットされたデータをuser_name
に代入します。
ユーザーのインプットが完了すると、19行目でTrue
が返されるので、user_name
の頭文字を大文字にして、class=”art”>self.user_nameに代入して関数を抜けます。
class SportsRobot(object)
次にRobotクラスを継承した対話ロボットのクラス、つまりJarvis
を作成していきます。
class SportsRobot(Robot):
def __init__(self, name=DEFAULT_ROBOT_NAME):
super().__init__(name=name)
self.ranking_model = ranking.RankingModel()
スポーツについて尋ねるロボットなので、クラス名はSportsRobot
にしています。
酒井さんの講座では、お気に入りのレストランを尋ねるサンプルだったのですが、お気に入りのスポーツを尋ねるロボットに変更しました。
初期化では、引数name
にグローバル変数のDEFAULT_ROBOT_NAME
を代入し、親クラスの__init__
を呼び出して、引数name
に、このクラスの引数name
を代入しています。
初期化の最後では、インポートしたranking
モジュールのRankingModel
クラスからインスタンス(オブジェクト)を作成して、self.ranking_model
としています。
ranking
モジュールについては、上記の記事で詳しく解説していますが、RankingModel
インスタンスは、CSVを書き込んだり読み込んだりするためのクラスオブジェクトになります。
@_hello_decorator
こちらは、デコレーターになり、次の関数以降で使うために定義しています。
def _hello_decorator(func):
def wrapper(self):
if not self.user_name:
self.hello()
return func(self)
return wrapper
デコレーターについては、次の記事で学習しました。
関数の前後でなにか処理をさせたいときにデコレーターを作成して、@
をつけて、デコレートしたい関数の一つ前の行に記述しました。
今回のデコレーターなら、@_hello_dekorator
ですね。
次の関数以降に@_hello_dekorator
をつけていますが、これは後で学習するController
の実行ファイルで、hello()
関数が実行されずにself.user_name
が取得できていない場合、テンプレートにユーザーの名前が表示できないからです。
それぞれの文章を出力するテンプレートには、変数self.user_name
が使われるため、デフォルト引数で空を代入しているので、文章がおかしくなってしまいます。
基本的に、Controller
の実行ファイルで、hello()
関数が実行されるなら、self.user_name
を取得できるので、問題はありません。
念のため、デコレーターを使って、実行する関数より先にhello()
を実行することで、self.user_name
を取得できるようにしているということです。
なので、個人的には、このデコレーターは不要なんじゃないかと感じていますが、実践では、ここまでカバーできるようなコードを書いたほうがいいのかもしれません。
デコレーター@_hello_decolator
の解説をしたので、次以降の関数についてはデコレータが付いていますが、説明は省きます。
def recommend_sport(self)
おすすめのスポーツが好きかどうか尋ねるための関数です。
def recommend_sport(self):
new_recommend_sport = self.ranking_model.get_most_popular()
if not new_recommend_sport:
return None
finish_recommend_sports = [new_recommend_sport]
while True:
template = console.get_template('recommend.txt')
is_yes = input(template.substitute({
'robot_name': self.name,
'user_name': self.user_name,
'sport': new_recommend_sport
}))
if is_yes.upper() == 'Y' or is_yes.upper() == 'YES':
break
if is_yes.upper() == 'N' or is_yes.upper() == 'NO':
new_recommend_sport = self.ranking_model.get_most_popular(not_list=finish_recommend_sports)
if not new_recommend_sport:
break
finish_recommend_sports.append(new_recommend_sport)
39行目でRankingModel
のインスタンスself.ranking_model
のget_most_popular()
を実行して、最もライキングの高いスポーツ名を変数new_recommend_sport
に、代入しています。
40行目、41行目で、new_recommend_sport
がない場合、None
を返しています。つまり、この関数では何も実行されないということです。
new_recommend_sport
が存在する場合は、その値をリスト変数finish_recommend_sports
に代入します。
44行目で、while
ループに入りますが、45行目でconsole
モジュールのget_template
関数で、recommend.txt
をフォーマット(整形)してから、46行目から50行目でテンプレートの整形した文章にロボット名やユーザー名、スポーツ名を変数に代入し出力、input()
で取得したYes
かNoの値を変数is_yes
に代入しています。
52行目、53行目で、変数is_yes
をすべて大文字にして、Y
かYES
の場合はループを抜けます。
is_yes
の大文字化した値がN
かNO
の場合は、インスタンスself.ranking_model
のget_most_popular()
関数の引数not_list
にリスト変数finish_recommend_sport
を代入して取得した値でnew_recommend_sport
を上書きします。
57行目から59行目では、new_recommend_sport
がない場合はループを抜けて、値が存在した場合はその値をリスト変数finish_recommend_spotrs
に加えます。
最終的に、おすすめのスポーツが好きかどうかを尋ねるパートは次の条件分岐のようになります。
- データがない場合:何もしない
- データがある場合:ランキングの高いものが好きかどうか聞く
- 回答がYesの場合:この質問は終了
- 回答がNoの場合:データがなければ終了、あれば次にランキングが高いスポーツが好きかどうか聞く
回答がYesの場合はすぐにループを抜けますが、Noの場合は、データが無くなるまでおすすめのスポーツを変更して質問を繰り返します。
def ask_user_favorite(self)
次の関数では、大好きなスポーツを尋ねます。
def ask_user_favorite(self):
while True:
template = console.get_template('like.txt')
sport = input(template.substitute({
'robot_name': self.name,
'user_name': self.user_name,
}))
if sport:
self.ranking_model.increment(sport)
break
console
モジュールのget_template
を使ってlike.txt
をフォーマットしてtemplate
に代入し、整形したテンプレートの文章に、ロボットの名前やユーザーの名前を代入して出力、input()
で値を取得するのは他の関数と同じですね。
69行目で、取得したsport
があれば、self.ranking_model
インスタンスのincrement(sport)
を実行して、CSVに値を追加してループを抜けます。
increment
関数の処理についてはこちらの記事を御覧ください。
def thank_you(self)
いよいよ最後の関数ですが、終わりの挨拶で締めくくります。
def thank_you(self):
template = console.get_template('goodby.txt')
print(template.substitute({
'robot_name': self.name,
'user_name': self.user_name,
}))
他の関数同様に、console
モジュールのget_template
を使い、goodby.txt
を整形し、ロボットの名前とユーザー名を文章の中の変数に代入して出力しています。
テンプレートにつなげる
あとはテンプレートの文章を仕上げれば、Jarvisほぼ完成します。
標準ライブラリ以外の他のモジュールをインポートしていないPythonファイルから取り掛かりましたが、コードを書いている途中で他のファイルとのつながりを考える必要が出てくるため、一つ一つのファイルを仕上げてから次のファイルに進むのではなく、他のファイルとのつながりが出てきた時点で別のファイルのコードも書き出したほうがいいかもしれません。
同時進行で別ファイルのコードを書いていくのか、一つ一つのファイルを仕上げてから次のファイルに取り掛かるのがいいかは人それぞれなので、いくつかコードを書きながら、自分にあった方法を見つけていくしかありません。
個人的には、ガッツリ全体設計を仕上げて個別のコードを書くのではなく、なんとなくのイメージを作ってコードを書き始めて、必要が出てきたときには途中でも他のファイルに取り掛かるような感じが合っているような気がします。
明日は、Jarvisがユーザーに話しかけるテンプレートを仕上げたいと思います。
それでは明日もGood Python!