Python Udemy講座 MVCモデル

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_fileNoneにして、他の引数を受けられるようにしています。

変数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_pathNoneに定義します。

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_PATHcsv_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()keyvalueを取り出して、グローバル変数で定義したNAMECOUNTの行に書き込んでいます。

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_listNoneなので、空のリスト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します。

値がなければ、末行のNAMEname.title()COUNT1を追加します。

最後の76行目で、CSVファイルを保存するself.save()関数を実行します。

つながりが重要

MVCモデルで対話アプリJarvisを作るにあたって、どのファイルから作成していけばいいか考えて、他のファイルをインポートしていないPythonファイルから取り掛かるのがいいと思っていました。

しかし、今日までにconsole.pyranking.pyの2つのファイルを書いてみて思ったのは、他のファイルとのつながりを考えながらでないとコードが書けないということです。

引数を定義するにしても、他のファイルからどのような引数を受け取るのか、はたまた他のファイルにどんな引数を渡すのかを理解しておかないとMVCモデルでコードを書くことはできません。

まずは、全体の設計を掴んだ上で、コードを書きながら、必要な関数や引数などを付け足していく必要があるように感じています。

今学習している酒井さんのサンプルコードについては、酒井さんが作ったコードなので、読み解きながら学習することができますが、これを1から考えるのはかなり経験が必要だということもわかりました。

Udemy講師 酒井さん プログラミング Python

しっかりとサンプルコードを読み解きながら、コードの書き方や処理方法を学習していきたいと思います。

明日は、対話アプリJarvisのメイン動作となる対話ロボット部分のファイルを読み解いていきます。

それでは明日もGood Python!