Python学習【365日チャレンジ!】112日目のマスターU(@Udemy11)です。
久しぶりにたこ焼きを作ろうと思ってスーパーに行ったところ、お好み焼きの元とたこ焼きの元が売り切れになっていました。
ちょっと前までならSTAY HOMEということで、みんな家で粉モンを楽しんでいるんだと思ったのですが、ちょっと時期も外しているので、もしかして粉モンブーム到来?!なんて思ってしまいました。
とりあえず、ホームベーカリー用に強力粉がたくさんあるので、強力粉でたこ焼きを作ってみることに。
どんな分量でどんな材料を合わせればいいのかはネットで調べるとわかったのですが、あんまりたこ焼きっぽくないなんて記事もあったので、ちょっと不安でした。
とはいえ、なんだかんだいいつつもうまくできたので、これからはたこ焼き粉を買わなくてもたこ焼きを作れるので、たこ焼きパーティーの回数が増えるかもしれません。
それでは、お腹いっぱいになったところで、今日もPython学習をはじめていきましょう!
昨日の復習
昨日は、MVCモデルで対話アプリJarvisのview
要素に関係するconsole.py
を学習しました。
全体設計の中で、どのファイルもインポートしていないファイルだったので、console.py
を最初に学習しました。
console.py
は、対話アプリが質問をするための文章を格納したテンプレートファイルの絶対パスを取得し、テンプレートの文章の表示をフォーマットする役割を持っていましたね。
表示のための見た目を整えるconsole.py
の詳しい内容については、昨日の記事をごらんください。
それでは、本日の学習であるmodel
フォルダに格納するCSVの読み書きを担当するranking.py
を書いていきましょう!
ranking.py
まずは、ranking.py
のコードをみてみましょう。
import collections
import csv
import os
import pathlib
RANKING_COLUMN_NAME = 'NAME'
RANKING_COLUMN_COUNT = 'COUNT'
RANKING_CSV_FILE_PATH = 'ranking.csv'
class CsvModel(object):
def __init__(self, csv_file):
self.csv_file = csv_file
if not os.path.exists(csv_file):
pathlib.Path(csv_file).touch()
class RankingModel(CsvModel):
def __init__(self, csv_file=None, *args, **kwargs):
if not csv_file:
csv_file = self.get_csv_file_path()
super().__init__(csv_file, *args, **kwargs)
self.column = [RANKING_COLUMN_NAME, RANKING_COLUMN_COUNT]
self.data = collections.defaultdict(int)
self.load_data()
def get_csv_file_path(self):
csv_file_path = None
try:
import settings
if settings.CSV_FILE_PATH:
csv_file_path = settings.CSV_FILE_PATH
except ImportError:
pass
if not csv_file_path:
csv_file_path = RANKING_CSV_FILE_PATH
return csv_file_path
def load_data(self):
with open(self.csv_file, 'r+') as csv_file:
reader = csv.DictReader(csv_file)
for row in reader:
self.data[row[RANKING_COLUMN_NAME]] = int(row[RANKING_COLUMN_COUNT])
return self.data
def save(self):
with open(self.csv_file, 'w+') as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=self.column)
writer.writeheader()
for name, count in self.data.items():
writer.writerow({
RANKING_COLUMN_NAME: name,
RANKING_COLUMN_COUNT: count
})
def get_most_popular(self, not_list=None):
if not_list is None:
not_list = []
if not self.data:
return None
sorted_data = sorted(self.data, key=self.data.get, reverse=True)
for name in sorted_data:
if name in not_list:
continue
return name
def increment(self, name):
self.data[name.title()] += 1
self.save()
ranking.py
でインポートするライブラリは次の4つです。
- collections
- csv
- os
- pathlib
collections
モジュールは、空の辞書型データを用意するために使います。
csv
モジュールは、CSVファイルの取り扱い、os
モジュールは、ファイルの有無の確認、pathlib
モジュールは、ファイルの作成のために使います。
それではパーツに分けてコードを読み解いていきましょう。
グローバル変数
最初にグローバル変数を定義しています。
RANKING_COLUMN_NAME = 'NAME'
RANKING_COLUMN_COUNT = 'COUNT'
RANKING_CSV_FILE_PATH = 'ranking.csv'
7行目、8行目は、ranking.csv
に保存する見出し行の名前です。
9行目では、扱うCSVの名前をranking.csv
に指定しています。
class CsvModel(object)
12行目から16行目で、CSVを扱うModelのベースクラスを作成しています。
class CsvModel(object):
def __init__(self, csv_file):
self.csv_file = csv_file
if not os.path.exists(csv_file):
pathlib.Path(csv_file).touch()
__init__
で初期化だけするベースクラスです。
初期化は、引数csv_file
を変数self.csv_file
に代入して、csv_file
が存在しない場合は、csv_file
を作成します。
class RankingModel(CsvModel)
次にCsvModel
を継承した実際に使用するRankingModel
クラスを作成します。
class RankingModel(CsvModel):
def __init__(self, csv_file=None, *args, **kwargs):
if not csv_file:
csv_file = self.get_csv_file_path()
super().__init__(csv_file, *args, **kwargs)
self.column = [RANKING_COLUMN_NAME, RANKING_COLUMN_COUNT]
self.data = collections.defaultdict(int)
self.load_data()
初期化で行っているのは、まず、デフォルト引数csv_file
をNone
にして、他の引数を受けられるようにしています。
変数csv_file
がなければ、次に解説する関数get_csv_file_path()
の返り値をcsv_file
に代入しています。
その後、23行目でベースクラス(親クラス)の__init__
を呼び出して、引数にcsv_file
と*args
、**kwargs
を入れています。
24行目では、変数column
リストに2つのグローバル変数を代入し、25行目で空の辞書型データdata
を定義、26行目で後ほど解説するload_data()
関数を実行しています。
def get_csv_file_path(self)
RankingModel
の初期化で実行されている関数get_csv_file_path
は、view
フォルダ内のconsole.py
でも使われている読み込むファイルのパスを取得する関数です。
def get_csv_file_path(self):
csv_file_path = None
try:
import settings
if settings.CSV_FILE_PATH:
csv_file_path = settings.CSV_FILE_PATH
except ImportError:
pass
if not csv_file_path:
csv_file_path = RANKING_CSV_FILE_PATH
return csv_file_path
最初に変数csv_file_path
をNone
に定義します。
try-except
文を使って、setting.py
をインポートして、ファイル内に変数CSV_FILE_PATH
があれば、そのバスを変数csv_file_path
に代入します。
setting.py
がなければ何もしません。
下記の記事でも解説していますが、setting.py
はなくても問題ありません。
もし作る場合は、CSV_FILE_PATH = '/temp/test.csv'
かCSV_FILE_PATH = None
と記述しておきます。
37行目以降では、変数csv_file_path
がなければ、最初に定義しているグローバル関数RANKING_CSV_FILE_PATH
をcsv_file_path
に代入して、返り値にします。
def load_data(self)
次の関数はRankingModel
の初期化の最後で実行される関数です。
def load_data(self):
with open(self.csv_file, 'r+') as csv_file:
reader = csv.DictReader(csv_file)
for row in reader:
self.data[row[RANKING_COLUMN_NAME]] = int(row[RANKING_COLUMN_COUNT])
return self.data
クラスの初期化によって、変数self.csv_file
には値が代入されているので、そのCSVファイルを読み込み書き込みモードで開いてcsv.DictReader
で読み込んだデータを変数reader
に代入しています。
44行目から46行目でfor
ループを使い、変数reader
からグローバル変数で定義したNAME
と整数型にしたCOUNT
の値をself.data
に代入し、返り値にしています。
def save(self)
この関数は、その名のとおりCSVファイルを保存する関数です。
def save(self):
with open(self.csv_file, 'w+') as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=self.column)
writer.writeheader()
for name, count in self.data.items():
writer.writerow({
RANKING_COLUMN_NAME: name,
RANKING_COLUMN_COUNT: count
})
まず、クラスの初期化によって取得したself.csv_file
を書き込み読み込みモードで開いています。
次にクラスの初期化で定義したリストself.column
を引数fieldnames
に代入して、その値をCSVファイルのヘッダに書き込んでいます。
最後に、初期化で取得したself.data
からitems()
でkey
とvalue
を取り出して、グローバル変数で定義したNAME
とCOUNT
の行に書き込んでいます。
def get_most_popular(self, not_list=None)
この関数は、CSVに保存されている値の中から数の多いものを抽出する関数になります。
def get_most_popular(self, not_list=None):
if not_list is None:
not_list = []
if not self.data:
return None
sorted_data = sorted(self.data, key=self.data.get, reverse=True)
for name in sorted_data:
if name in not_list:
continue
return name
関数が呼び出されるときに、not_list
にデフォルトでNone
が代入されますが、引数が指定されない場合はnot_list
がNone
なので、空のリストnot_list
を定義します。
初期化の際に関数load_data()
で取得した変数self.data
に何もデータがない場合は、None
を返し、この関数から抜けます。
67行目は、必然的にself.data
にデータがある場合の処理となり、sorted
関数の引数key=self.data.get
により、COUNT
の値の降順で並び替えたデータを変数sorted_data
に代入します。
68行目から71行目では、降順に並び替えたデータをfor
ループで変数name
に代入しますが、name
が変数not_list
に含まれている場合は、for
ループに戻り、それ以外(name
が変数not_list
に含まれていない場合)はname
を返り値にしています。
この関数は、ロボットモデルの関数から引数を与えられて呼び出されるため、単体では理解しづらいのですが、対話ロボットJarvisがおすすめのスポーツを提案するときに活用される関数です。
前に作成したJarvisでは、ジェネレーターで順番におすすめするスポーツを代入しましたが、このコードでは提示したスポーツをロボットモデルの関数とつなげてnot_list
に加えていき、すでに紹介したスポーツは省くようなコードになっています。
def increment(self, name)
最後の関数は、ユーザーが回答した答えをCSVファイルに追加するための関数です。
def increment(self, name):
self.data[name.title()] += 1
self.save()
74行目のself.data
は、defaultdict
タイプのデータで、引数name
の頭文字を大文字にした値name.title()
と同じNAME
の行のCOUNT
の値にプラス1
します。
値がなければ、末行のNAME
にname.title()
、COUNT
に1
を追加します。
最後の76行目で、CSVファイルを保存するself.save()
関数を実行します。
つながりが重要
MVCモデルで対話アプリJarvisを作るにあたって、どのファイルから作成していけばいいか考えて、他のファイルをインポートしていないPythonファイルから取り掛かるのがいいと思っていました。
しかし、今日までにconsole.py
とranking.py
の2つのファイルを書いてみて思ったのは、他のファイルとのつながりを考えながらでないとコードが書けないということです。
引数を定義するにしても、他のファイルからどのような引数を受け取るのか、はたまた他のファイルにどんな引数を渡すのかを理解しておかないとMVCモデルでコードを書くことはできません。
まずは、全体の設計を掴んだ上で、コードを書きながら、必要な関数や引数などを付け足していく必要があるように感じています。
今学習している酒井さんのサンプルコードについては、酒井さんが作ったコードなので、読み解きながら学習することができますが、これを1から考えるのはかなり経験が必要だということもわかりました。
しっかりとサンプルコードを読み解きながら、コードの書き方や処理方法を学習していきたいと思います。
明日は、対話アプリJarvisのメイン動作となる対話ロボット部分のファイルを読み解いていきます。
それでは明日もGood Python!