Python 対話アプリ Jarvis

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

ホットケーキミックスが品薄になっていたらしですが、大量の買いだめには注意が必要なようです。

というのも、小麦粉の中には、ダニの卵が含まれているので、長期間保存することでダニが大量に発生して、ダニアレルギーでアナフラキシーショックを起こしてしまうことがあるらしいからです。

2004年の記事なので、少し古いですが、この報告によると、小麦粉によるアナフラキシーショックが報告されていて、その後の調査で、日本の小売店の176パッケージの中から3個、家庭の127パッケージの中から7個にダニの蔓延が見つかったそうです。

ただ、冷蔵庫に保存されていたパッケージからはダニは検出されなかったそうなので、小麦粉は冷蔵庫に保存したほうがいいようですね。

そういえば、以前米不足になったときに米屋さんから聞いた話では、日本の米は1年もほっておけば、中で虫が成長してカサカサ音がなってくるけど、輸入したタイ米は、何年立っても虫がわかないなんてことを言ってました。

タイ米には、それだけ強力な殺虫剤が使われているということですね。

毎日口にするものなので、このあたりのことには注意したいものです。

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

昨日の復習

昨日は、オブジェクト指向作成したJarvisクラスで2人目の質問に対応させました。

1人目と2人目の違いは、条件分岐があるかどうかだったので、1人目に比べるとコードが長くなりましたが、何も考えずに作った最初のコードに比べるとかなりわかりやすくなったかと思います。

1人目と大きくちがったのは、__init__(self)による初期化とCSVの書き込みのための関数を加えたところでした。

内容については、昨日の記事をごらんください。

それでは、クラスを使ったコードを完成させましょう!

追加するコード

1人目、2人目をカバーする質問と3人め以降の質問で大きく違うのは、CSVに保存しているデータを並び替えておすすめするスポーツを抽出することと、大好きなスポーツがすでにあるデータかどうかを判断して、CSVに追加するところです。

まず、CSVのデータを取り出して順番に並べ替えるためにoperatorモジュールをインポートしてデータを並び替える必要があります。

次に、おすすめするスポーツが好きかどうかの判断によって条件分岐を加えます。

最後は、CSVファイルへの書き込みも条件によって変える必要があります。

これらの追加コードについて、前回のコードから変更された部分について紹介していきます。

__init__(self)の変更

まず、インポートするモジュールを追加し、そのモジュールを使って、データを並び替え、おすすめするスポーツを取り出せるジェネレーターを作ります。

import csv
import operator
import os


class Jarvis(object):
    def __init__(self):
        sel.check = ps.path.exixts('ranking.csv')
        self.fieldnames = ('NAME', 'COUNT')
        if self.check is True:
            with open('ranking.csv', 'r') as rank_csv:
                ranking = csv.DictReader(rank_csv)
                result = sorted(ranking, key=operator.itemgetter('COUNT'), reverse=True)
                self.recommend = (i['NAME'] for i in result)

あとでCSVファイルに書き込むためのself.fieldnamesも初期化の際に定義しておきます。

ranking.csvが存在するときに、ファイルを開いて、おすすめするスポーツをCOUNTの数が多い順に並べ替えています。

y_or_n(self)の変更

次に、おすすめするスポーツが好きかどうかの条件分岐は、昨日は、ここで条件を判断しませんでしたが、3人目以降は回答によって次の質問が変わってくるので、条件分岐を入れる必要があります。

def y_or_n(self):
    r_sport = next(self.recommend)
    while True:
        try:
            y_n = input('\nわたしのおすすめのスポーツは{}です。\nあなたはこのスポーツが好きですか? [Yes/No]\n'.format(r_sport))
            if 'Y' in str.upper(y_n):
                break
            elif 'N' in str.upper(y_n):
                r_sport = next(self.recommend)
                continue
            else:
                print('\nYes か No で答えてください。')
        except:
            break

回答がYesYの場合は、breakでこのループを抜けます。

NoもしくはNの場合は、初期化で取得したジェネレーターから次の値をr_sportに代入して、ループを繰り返します。

回答に、YNも含まれていない場合は、YesNoで答えるように質問し、ループを繰り返します。

try exceptでジェネレーターの値がなくなったときのエラーを返さずにループを抜けるようにしています。

write_csv(self)の変更

def write_csv(self):
    if self.check is True:
        with open('ranking.csv', 'r') as rank_c:
            ranking = csv.DictReader(rank_c)

            refresh_csv = []
            for i in ranking:
                if i['NAME'] == self.c_like_sports:
                    num = i['COUNT']
                    a_num = int(num) + 1
                    i['COUNT'] = a_num
                refresh_csv.append(i)
            rank_c.seek(0)
            check_sports = []
            for j in ranking:
                check_sports.append(j['NAME'])

        if self.c_like_sports in check_sports:
            with open('ranking.csv', 'w') as rank_csv:
                writer = csv.DictWriter(rank_csv, fieldnames=self.fieldnames)
                writer.writeheader()
                writer.writerows(refresh_csv)
        else:
            with open('ranking.csv', 'a') as rank_csv:
                writer = csv.DictWriter(rank_csv, fieldnames=self.fieldnames)
                writer.writerow({'NAME': self.c_like_sports, 'COUNT': 1})
    else:
        with open('ranking.csv', 'w', encoding='UTF-8') as rank_csv:
            writer = csv.DictWriter(rank_csv, fieldnames=self.fieldnames)
            writer.writeheader()
            writer.writerow({'NAME': self.c_like_sports. 'COUNT':1})

大きな条件分岐であるranking.csvの有無については昨日と同じで、ない場合は、ranking.csvを作成して、取得したデータを書き込みます。

ranking.csvがある場合は、データを取得して、回答されたデータがすでにある場合は、そのCOUNTの値に1を足して、それ以外の場合は、そのまま変数redresh_csvに代入していきます。

次に、取得したデータの読み込み位置をrank_c.seek(0)でもとに戻してから変数check_sportsにスポーツ名を代入します。

最後に、check_sportsの中に、ユーザーが入力したスポーツc_like_spotrsが含まれていれば、ranking.csvに、変数refresh_csvの値を上書きし、含まれていなければ、スポーツ名c_like_sportsranking.csvに追記します。

完成したコード

上記の部分を昨日のコードに追加すれば、3人目以降の質問に対応できるコードの完成です。

import csv
import operator
import os


class Jarvis(object):
    def __init__(self):
        self.check = os.path.exists('ranking.csv')
        self.fieldnames = ('NAME', 'COUNT')
        if self.check is True:
            with open('ranking.csv', 'r') as rank_csv:
                ranking = csv.DictReader(rank_csv)
                result = sorted(ranking, key=operator.itemgetter('COUNT'), reverse=True)
                self.recommend = (i['NAME'] for i in result)

    def say_hello(self):
        name = input('こんにちは。わたしはジャービスです。あなたの名前を教えてください。\n')
        self.c_name = name.title()
        return self.c_name

    def y_or_n(self):
        r_sport = next(self.recommend)
        while True:
            try:
                y_n = input('\nわたしのおすすめのスポーツは{}です。\nあなたはこのスポーツが好きですか? [Yes/No]\n'.format(r_sport))
                if 'Y' in str.upper(y_n):
                    break
                elif 'N' in str.upper(y_n):
                    r_sport = next(self.recommend)
                    continue
                else:
                    print('\nYes か No で答えてください。')
            except:
                break

    def ask_main(self):
        like_sports = input('\n{}さん、あなたの好きなスポーツは何ですか?\n英語で答えてください\n'.format(self.c_name))
        self.c_like_sports = like_sports.title()
        return self.c_like_sports

    def write_csv(self):
        if self.check is True:
            with open('ranking.csv', 'r') as rank_c:
                ranking = csv.DictReader(rank_c)

                refresh_csv = []
                for i in ranking:
                    if i['NAME'] == self.c_like_sports:
                        num = i['COUNT']
                        a_num = int(num) + 1
                        i['COUNT'] = a_num
                    refresh_csv.append(i)
                rank_c.seek(0)
                check_sports = []
                for j in ranking:
                    check_sports.append(j['NAME'])

            if self.c_like_sports in check_sports:
                with open('ranking.csv', 'w') as rank_csv:
                    writer = csv.DictWriter(rank_csv, fieldnames=self.fieldnames)
                    writer.writeheader()
                    writer.writerows(refresh_csv)
            else:
                with open('ranking.csv', 'a') as rank_csv:
                    writer = csv.DictWriter(rank_csv, fieldnames=self.fieldnames)
                    writer.writerow({'NAME': self.c_like_sports, 'COUNT': 1})
        else:
            with open('ranking.csv', 'w', encoding='UTF-8') as rank_csv:
                writer = csv.DictWriter(rank_csv, fieldnames=self.fieldnames)
                writer.writeheader()
                writer.writerow({'NAME': self.c_like_sports, 'COUNT': 1})

    def __del__(self):
        print('\n{}さん、回答ありとうございました。\n良い1日をお過ごし下さい!'.format(self.c_name))


if __name__ == '__main__':
    jarvis = Jarvis()
    jarvis.say_hello()
    if jarvis.check is True:
        jarvis.y_or_n()
    jarvis.ask_main()
    jarvis.write_csv()
    del jarvis

最後には、実行コードの場合に実行するためのif __name__ = '__main__':に続けて、オブジェクトJarvisを作成し、関数を実行します。

今あるスキルで

今日までの6日ほどの内容は、これまで学習してきた入門編の内容を考えながら、自分なりにコードを考えて書いたので、理想的なコードとはほどとおい内容になってしまっているので、参考にはならないかもしれません。

とはいえ、自分で考えずに、先に模範解答を見てからコードを考えるのと、自分でなんとか課題をクリアするコードを考えるのでは、大きな違いが出てくると思います。

教えられたものをそのままやるのと、自分なりに工夫してやるのでは、身につくスキルも違ってきます。

答えを知った上で復習するか、やってみてから答えを知るのとの違いですよね。

明日からは、アプリケーションを開発する上での基本的な構造の考え方を踏まえた上で、模範となるコードを学習していきます。

それでは、明日もGood Python!