Python 対話アプリケーション 実践

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

【JIN-仁-】の漫画を一気読みして寝不足気味です。

ついつい先が知りたくなり、1巻を読み終えると次の巻に手を出してしまうので、寝る時間がおそくなるんですよね。

漫画が終わると、今度は、TBSオンデマンドをAmazonプライムで一気に観てしまいそうなので、これまた寝不足確定です。

Pythonも学習しないといけないし、ほんと時間がいくらあっても足りません。

それでは今日もPython学習にはいりましょう!

昨日の復習

昨日は、1人目のやり取りについてコードを書いてみました。

2人目以降の処理を考えずに、ただの対話として書いたので、ある程度スムーズにコードを書くことができました。

とはいえ、単純な質問をして、その値をCSVファイルに書き込むだけだったので、基本は、csvライブラリとinput関数を使うだけでした。

今日は、1人目と2人目までに対応したコードになるので少し複雑になってくると思います。

気を引き締めていきましょう!

1つ目の質問

JarvisJarvis

こんにちは。わたしはジャービスです。あなたの名前を教えてください。

1つ目の質問は1人目のときと変わりません。名前を取得して頭文字を大文字にします。

name = input('こんにちは。私はジャービスです。あなたの名前を教えてください。')
c_name = name.title()

CSVファイルの有無を確認

一番最初にこの動作をさせても問題はありませんが、一人目も二人目も最初の質問が同じなので、1つ目の質問が終わってから、ranking.csvが存在するかどうかの確認を行いました。

import os

check = os.path.exists('ranking.csv')

osライブラリをインポートして、os.path.exists()ranking.csvファイルがあるかどうかを確認します。

変数checkTrueFalseが代入されます。

条件分岐

ranking.csvファイルの有無で条件分岐をします。

ranking.csvファイルが存在しない場合は一人目の質問と同じ流れになり、ranking.csvファイルが存在する場合は、2人目の質問に移ります。

if check is True:
    # ranking.csvが存在するときの処理
else:
    # ranking.csvが存在しないときの処理
    # (一人目の質問と同じ処理)

if文を使って条件分岐をしました。

1人目の質問については、すでに完成しましたので、昨日書いたコードの内、2つ目の質問以降のコードを書きますが、最後の挨拶のprint出力のみ、インデントの外側(elseと同じレベル)に記述します。

else:
    # ranking.csvが存在しないときの処理
    sports = input('\n{}さん、あなたの好きなスポーツは何ですか?\n英語で答えてください\n'.format(c_name))
    c_sport = sports.title()
    with open('ranking.csv', 'w') as rank_csv:
        fieldnames = ('NAME', 'COUNT')
        writer = csv.DictWriter(rank_csv, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerow({'NAME': c_sport, 'COUNT': 1})
print('\n{}さん、回答ありとうございました。\n良い1日をお過ごし下さい!'.format(c_name))

最後のお礼の出力は、ranking.csvファイルが存在するときの処理が終わったあとにも出力する必要があるので、インデントのレベルを上げています。

CSVを読み込み

ranking.csvが存在する場合は、その中身を読み込み、記録されているスポーツ名を抽出します。

with open('ranking.csv', 'r') as rank_r:
    ranking = csv.DictReader(rank_r)
    for row in ranking:
        e_row = row['NAME']

ranking.csvを読み込みモードで開いて、キーがNAMEに保存されている値を変数e_rowに代入します。

これは、CSVファイルに保存されているデータが1行のみ場合だけを想定しているので、3人目以降の質問を想定する場合はコートの改良が必要です。
今回は、あくまで1行のみ保存されている前提でのコードになります。

2つ目の質問の条件分岐

JarvisJarvis

私のおすすめのスポーツは、Baseballです。
あなたはこのスポーツが好きですか? [Yes/No]

次の質問は、抽出した値を提示して、YesNoで答えてもらいますが、YでもyでもNでもnでも次の質問に移ります。

ただし、それ以外の答えの場合は、次のように質問をやり直します。

JarvisJarvis

Yes か Noで答えてください

私のおすすめのスポーツは、Baseballです。
あなたはこのスポーツが好きですか? [Yes/No]

この質問の条件分岐では、whileステートメントを利用しました。

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

YesでもNoでも次の質問に移るので、どちらでもbreakでループを抜けます。

ちなみに、どちらも回答をupper()関数で大文字にして、YNが含まれていた場合という条件にしています。

他の質問の場合は、Yes か Noで答えてくださいと出力したあと、ループに戻るようにしました。

3つ目の質問

JarvisJarvis

Masteruさん、あなたの大好きなスポーツはなんですか?
英語で答えてください。

3つ目の質問は、一人目の質問とほぼ同じですが、「好きなスポーツ」を「大好きなスポーツ」に変更しています。

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

コードは一人目の質問のときと同じく、title()で頭文字を大文字にして変数c_sportに代入しています。

書き込み用データの整形

次に取得したデータがもともとranking.csvに保存されていたデータと同じかどうか判断して、同じ場合はCOUNT1を足して、違う場合は行を追加するための準備をします。

rank_r.seek(0)
sport_data = []

for i in ranking:
    if i['NAME'] == c_sport:
        num_s = i['COUNT']
        count_s = int(num_s) + 1
        i['COUNT'] = count_s
    sport_data.append(i)

check_sport = []
for i in sport_data:
    check_sport.append(i['NAME'])

まずは開いていたranking.csvの読み込み位置を戻すためにrank_r.seek(0)を入れています。

次に、空のリストsport_dataを作り、CSVファイルから読み込んだデータを代入し、3つ目の質問で受け取ったc_sportと同じ行のCOUNT1をプラスしています。

DictReaderで読み込んだデータは文字列になっているので、int()で一旦整数にしたあと1を足してCOUNTに戻します。

最後に変数sport_dataappend()で行を追加していきます。

これでranking.csvに書き込むためのデータが変数sport_dataに入ったことになります。

次に変数sport_dataからスポーツ名(NAME)だけを取り出したリストをcheck_sportに代入しているコードです。

条件にあったデータを書き込む

上記のリストcheck_sportの中に、3つ目の質問で入力されたc_sportがなければ、新しく行を追加し、含まれていれば、上記の変数sport_dataranking.csvに書き込みます。

if c_sport not in check_sport:
    with open('ranking.csv', 'a') as new_d:
        writer = csv.writer(new_d)
        writer.writerow([c_sport, 1])
else:
    with open('ranking.csv', 'w') as add_d:
        fieldnames = ('NAME', 'COUNT')
        writer = csv.DictWriter(add_d, fieldnames=fieldnames)
        writer.writerows(sport_data)

withステートメントの読み込みモードで開いているインデントの中でさらにranking.csvを追記モード('a'で開いてもいいのか疑問なのですが、3つ目の質問で答えたスポーツ名(c_sportが先程用意した変数check_sportの中に入っていない場合はスポーツ名と1を追記します。

それ以外の場合(すでにスポーツ名がranking.csvにある場合)、先程用意した変数sport_dataranking.csvに上書きします。

最終のコード

最終的にすべてのコードをまとめると次のようになります。

import os
import csv

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

check = os.path.exists('ranking.csv')

if check is True:
    with open('ranking.csv', 'r') as rank_r:
        ranking = csv.DictReader(rank_r)
        for row in ranking:
            e_row = row['NAME']

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

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

        rank_r.seek(0)
        sport_data = []

        for i in ranking:
            if i['NAME'] == c_sport:
                num_s = i['COUNT']
                count_s = int(num_s) + 1
                i['COUNT'] = count_s
            sport_data.append(i)

        check_sport = []
        for i in sport_data:
            check_sport.append(i['NAME'])

        if c_sport not in check_sport:
            with open('ranking.csv', 'a') as new_d:
                writer = csv.writer(new_d)
                writer.writerow([c_sport, 1])
        else:
            with open('ranking.csv', 'w') as add_d:
                fieldnames = ('NAME', 'COUNT')
                writer = csv.DictWriter(add_d, fieldnames=fieldnames)
                writer.writerows(sport_data)

else:
    sports = input('\n{}さん、あなたの好きなスポーツは何ですか?\n英語で答えてください\n'.format(c_name))
    c_sport = sports.title()
    with open('ranking.csv', 'w') as rank_csv:
        fieldnames = ('NAME', 'COUNT')
        writer = csv.DictWriter(rank_csv, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerow({'NAME': c_sport, 'COUNT': 1})

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

あくまで1人目、2人目の質問までをカバーしたコードなので、次の3人目の質問をしてしまうと、Javisがおすすめするスポーツがranking.csvに保存された最後の行のスポーツになってしまいます。

withステートメントで開いたファイルの中でさらにwithステートメントでファイルを開いてもいいのかという疑問が残るのですが、エラーが起こらないので、とりあえず動いているコードという認識をしておいたほうがいいかもしれません。

やってみることが大切

これまでに学習した内容を活用して、コードを書いていますが、プログラマーの方から見れば、ものすごい効率の悪い汚いコードかもしれません。

ただ、自分が書いたコードを記録に残すことで、あとから見れば「よくあんなコード書いてたな〜」というくらい成長できればいいなと思っているので、恥ずかしながらも汚いコードを公開しています。

もちろん、間違っているところもあるだろうし、もっときれいに短く書くこともできると思います。

Python学習初心者の方には、こんなめちゃくちゃなコードを書いてもいいんだなという気持ちになっていただけると幸いです。

この演習問題は1度きりで終わるのではなく、より見やすい効率的なコードを書けるよう、改良していこうと思っています。

繰り返しより良いコードを求めることでスキルも上がっていくわけなので、やっぱり大切なのは継続することですね。

それでは、明日もGood Python!