pythonで作ってみよう!!

pythonを楽しく学ぶために、おみくじ、占い、ジャンケンなど、実際に作りながら勉強してみます!

第1章 pythonプログラムの基本構造

はじめに

1.1 pythonプログラムの基本

pythonでも他のほとんどのプログラムと同じように上の行から1行ずつ実行されます。

1.2 文の区切り

プログラムは1行ずつ実行されますが、1行のプログラムを「文(statement)」と言います。
例外を除いて、ソースコード上の改行文字が文の区切りになります。Javaなど他の言語で見られる文末を表す文字は必要ありません。

1.3 コメント

プログラム中に記載する説明書きなどをコメントといいます。コメントとして記載された部分はプログラムとして解釈されません。
コメントはシャープ「#」で始めると行末までがコメントとして扱われます。
また、#はそこまでの行を終端します。

# コメントです
# プログラムの説明などを書きます

# メッセージ文字列を設定
msg = "ここはプログラムです" #プログラムの行末にコメント
msg = "このメッセージが表示されます"
# メッセージを表示
print(msg)
このメッセージが表示されます

コメントがプログラムとして解釈されない性質を利用して、動作確認やバグの切り分けのためにプログラムの一部を無効化するのにも使います。(このことを「コメントアウト」といいます。)

msg = "ここはプログラムです" 
#msg = "このメッセージが表示されます"

print(msg)
ここはプログラムです

1.4 複数行にまたがる文

行末にバックスラッシュ「\」を記述すると次の行と連結して1つの文と見なされます。
またカッコ「( )」「[ ]」「{ }」中(開きカッコがあるが閉じていない状態)は暗黙に行が継続します。

# 2行で1文です
num = 1 + 2 + 3 + 4 + 5\
    + 6 + 7 + 8 + 9 + 10

# カッコが閉じるまで1文です
print(num 
       + 100
       + 200)
355

1.5 インデント

pythonにおいては行頭のスペースによる字下げ(インデント)が言語的な意味を持ちます。
行頭に同じ数のスペースまたはタブが付いている文は同一のブロックとして認識されます。逆に、他の言語でよく見られる波カッコ「{ }」は必要ありません。
コードブロックはif文やfor文、関数やクラス定義などの範囲として文をグループ化するのに用いられます。詳しくは各セクションで説明します。
f:id:inato_gen:20190303210119j:plain
インデントに必要なスペースの数に決まりはありません。スペース4つが多いと思いますが、1つでも2つでも揃っていれば動作します。ブロックが不要な状況でインデントを付けるとエラーになります。
また、コードブロックの内側にさらにコードブロックを作る場合(ネストする場合)、直前のインデントより多くのスペースを付ける必要があります。
f:id:inato_gen:20190303220513j:plain

第8回 適切な割り勘をレコメンドする

9. もっと気の利いた割り勘をする

■要件

前回のプログラムでは1,000円単位だったので、他の人に3,000円ずつ払わせて、幹事(自分)は400円でした。なかなかオイシイですが、ちょっと気まずいですね。
こんなときは500円単位、それでも差が大きければ100円単位にしていくのではないでしょうか。
今回は、まず1,000円単位で割り勘をして他の人と幹事の支払い額の差が500円以上であれば500円単位、100円単位と細かくしていきます。最後のひとつだけ表示することにします。

ソースコード

import math

total = int(input("合計金額: "))
num = int(input("人数: "))
per_capita = math.ceil(total / num / 1000) * 1000
surplus = total - per_capita * (num - 1)
if per_capita - surplus < 500:
    print("1,000円単位では、一人あたり" + str(per_capita) + "円で、余りは" + str(surplus) + "円です。")
else:
    per_capita = math.ceil(total / num / 500) * 500
    surplus = total - per_capita * (num - 1)
    if per_capita - surplus < 500:
        print("500円単位では、一人あたり" + str(per_capita) + "円で、余りは" + str(surplus) + "円です。")
    else:
        per_capita= math.ceil(total / num / 100) * 100
        surplus = total - per_capita * (num - 1)
        print("100円単位では、一人あたり" + str(per_capita) + "円で、余りは" + str(surplus) + "円です。")
合計金額: 12400
人数: 5
500円単位では、一人あたり2500円で、余りは2400円です。
合計金額: 11600
人数: 4
1,000円単位では、一人あたり3000円で、余りは2600円です。

■解説

0. このプログラムを理解するのに必要な知識
  1. if文のネスト
1. 複数の条件分岐を重ねる

今回のプログラムでは、大きな単位で割り勘してみて、ダメだったら細かい単位を試すというロジックになっています。この場合、以前使ったif~elif~else構文では表現できません*1
このようにif文(今回はelse節)の中にif文を入れることをネスト(入れ子)構造といいます。for文でもネスト構造はよく用いられます。
ネストするには、ifブロックの中にifブロックを作るのでさらに深い(行頭スペースが多い)インデントを作ります。
f:id:inato_gen:20190301091737j:plain

10. 自作関数を作って再利用できるようにする

■要件

このプログラムでも要件は満たしますが、よく見ると同じような処理を何度も行っています。
この同じような処理を部品化して保守性の高いコードにしてみましょう。

ソースコード

import math

def split_check(total, num_member, unit):
    per_capita = math.ceil(total / num_member / unit) * unit
    surplus = total - per_capita * (num_member - 1)
    return [per_capita, surplus]

def display(unit, per_capita, surplus):
    message = "{:,}円単位では、一人あたり{:,}円で、余りは{:,}円です。"
    print(message.format(unit, per_capita, surplus))

total = int(input("合計金額: "))
num = int(input("人数: "))
for unit in [1000, 500, 100]:
    per_capita, surplus = split_check(total, num, unit)
    if per_capita - surplus < 500:
         break
display(unit, per_capita, surplus)

■解説

0. このプログラムを理解するのに必要な知識
  1. def文で関数の作成
  2. 引数を持つ関数
  3. return文で返り値を返す
  4. 文字列のフォーマット
  5. break文でループを抜ける
1. 自作関数の定義

今まで、print関数やint関数、random.randint関数など、python組み込みのものを使ってプログラムを作ってきました。こうした関数と同じように独自の処理を関数にすることができます。

def 関数名():
   処理(関数本体ブロック)

関数の定義はdef文を使って行います。

  • 関数名は英大文字小文字、アンダースコア「_」、数字(ただし、数字で始まる関数名はNG)
  • 関数名の後ろに丸カッコ「( )」
  • 次の行からコードブロックで処理内容を定義
  • 関数名()で呼び出し
# 自作関数の定義
def my_func():
    print("my_func関数が実行されました")

# 自作関数の呼び出し
my_func()
my_func関数が実行されました

プログラムは基本的に上から実行されますが、def文に関しては関数が定義され、呼び出し可能になるだけで実際には動作しません。関数の中身が実行されるのは、呼び出された後です。
f:id:inato_gen:20190305162852j:plain

2. 引数を受ける関数を作成する

int関数のように引数を受け付けて動的に動く関数の場合、関数定義の丸カッコ中に受ける引数名を列挙します。

def 関数名(引数1, 引数2, ...):
    関数本体

関数定義の中で受ける引数名を特に仮引数(かびきすう)と言い、関数の中では変数として振る舞います。

def my_func2(x):
    print("my_func2関数が実行されました")
    print(str(x) + "を2倍すると" + str(x * 2))

a = 100
my_func2(a)
my_func2関数が実行されました
100を2倍すると200

f:id:inato_gen:20190306084017p:plain

3. 戻り値の返却

処理中にreturn文を書くと戻り値を呼び出し元に返し、関数呼び出しの次の行からプログラムが再開されます。(「値を返す」とは関数呼び出し部がその値に置き換わることと同じでした。)

def my_func3():
    print("my_func3関数が実行されました")
    return 3
    print("このコードは実行されません")

a = my_func3()
print(a)
my_func3関数が実行されました
3
4. 変数の値を書式変換する

文字列に変数の値を埋め込む場合、文字列の連結でも実現できますが、以下のように値の展開をすることもできます。

文字列.format(値 ,...)

書式化された文字列を返します。
文字列には波カッコ「{}」を埋め込むことで、format関数に渡した値をその部分に展開できます。

a = 1000
temp = "変数aの値は{}です"
msg = temp.format(a)
print(msg)
変数aの値は1000です

このformat関数は、random.randint関数のように外部モジュールの関数の呼び出しと似ていますが、最初に来るのが、モジュール名ではありません。上記サンプルでは変数名ですが、文字列であればいいので、変数名である必要もなく

print("変数aの値は{}です".format(1000))

としても同じ結果になります。
またこの際に波カッコの中に特別な指定をすることで変数の値を書式に従って変換することができます。

処理 書式 記述例 結果
桁区切り {:,} "{:,}".format(1000) 1,000
右揃え {:埋める文字*2>揃える幅 "{:>5".format(123) 123
16進数表記 {:x} "{:x}". format(65535) ffff
5. forループを途中で抜ける

for文を使って繰り返し処理ができることはすでに習得しました。(よね?)
今回のケースでは、1000円単位、500円単位、100円単位と割り勘処理を繰り返しますが、うまく割り勘できたらそれ以降の処理は要らないものとしました。具体的には、幹事と他の人の支払い額の差が500円未満の場合です。
繰り返し処理を抜けるにはbreak文を使います。もちろんうまく割り勘できていないケースでは繰り返し処理(割り勘にトライすること)を続ける必要があるので、if文で判定して満たした場合のみbreak文を実行します。
f:id:inato_gen:20190307224625p:plain

*1:書きようによってはできますが、人がやるときと同じようなロジックで考えています

*2:省略するとスペース

第7回 割り勘電卓を作ってみよう

8. 割り勘電卓を作ってみる

次は計算処理を中心としたプログラムを作って、演算について学んでいきます。

■要件

まず、金額と人数を入力します。1,000円単位で切り上げて端数(幹事の金額)を計算します。

ソースコード

import math

total = int(input("合計金額: "))
num = int(input("人数: "))
per_capita = math.ceil(total / num / 1000) * 1000
surplus = total - per_capita * (num - 1)
print("1,000円単位では、一人あたり" + str(per_capita) + "円で、余りは" + str(surplus) + "円です。")

■実行結果

合計金額: 12400
人数: 5
1,000円単位では、一人あたり3000円で、余りは400円です。

■解説

0. このプログラムを理解するのに必要な知識
  1. math. ceil関数で切り上げ
  2. -演算子で減算、*演算子で乗算、/演算子で除算

新プログラムの初回なのでおさらいもまとめておきましょう

  1. import文で外部モジュールの読み込み
  2. input関数でユーザー入力
  3. int関数で文字列→数値変換
  4. =演算子で変数に代入
  5. print関数で文字列の表示
  6. 固定の文字列は""で囲む
  7. str関数で数値→文字列変換
  8. 文字列+文字列は結合
1. 切り上げをする

math.ceil関数を使います。

math.ceil(切り上げ対象の数値)

小数点以下を切り上げた数値を返します。

切り捨てにはmath.floor関数を、 四捨五入にはround関数を使います。 round関数には外部モジュールが必要ありません。
指定桁数での切り上げには以下のようなテクニックを使います。
math.ceil(数値 / 切り上げ単位) * 切り上げ単位
12500円という数値を、12.5千円と単位変換することで整数の切り上げ問題に変換しています。実際に欲しい数値が円単位であれば切り上げた後に戻します。
math.ceil(12500円 / 1000円) * 1000# 円→千円変換
math.ceil(12.5千円) * 1000# 切り上げ
13千円 * 1000# 千円→円変換
13000
実はこのテクニックは桁だけではなく、切り上げ(切り捨てや四捨五入も)を任意の単位で処理することを意味します。 例えば、タイムカードを押した時刻から15分刻みで始業時刻を求めるようなプログラムです。分だけですが。
import math
punched = 18
start = math.ceil(punched / 15) * 15
print(str(punched) + "分に打刻した場合の始業時刻は" + str(start) + "分です")
18分に打刻した場合の始業時刻は30分です
2. 四則演算をする

四則演算も普通の数学と同じように行えます。記号が少し違うのと、数学では変わった書き方をするもの(2乗とか)も、コードに出来るよう演算子が割り当てられています。
いくつか挙げておきます。

処理 数学記号 演算子 結果
加算 + 1+2 3
減算 - 3-1 1
乗算 × * 2*3 3
乗算 ÷ / 10/4 2.5
剰余 x mod y*1 % 10%4 2
べき乗 x^y ** 5**3 125

*1:数学記号として正しいかは自信がありませんが…wikipedia:数学記号の表を参照しました

第6回 実際に野球対決する

7. バッターのロジックを作ってみよう

■要件

バッターは、ある球種を狙っているものとします。今回は1球勝負ですし、ボールにはならないですし、得意球もありませんので、一番簡単なのはランダムにしてしまうことです。*1
今回はランダムで狙い玉を決めることにします。

ソースコード

import sys
import time
import random

species = ["ストレート", "カーブ", "シュート", "フォーク"]
swing = random.randint(0, len(species) - 1)
print("何を投げますか?")
num = 0
for sp in species:
    num = num + 1
    print(str(num) + ". " + sp)
pitch = int(input(">> ")) - 1
print(species[pitch] + "を投げた!")
for i in range(10):
    time.sleep(0.2)
    sys.stdout.write('・')
print("\nバッターは" + species[swing] + "を狙っていた!")
if(pitch == swing):
    print("逆転サヨナラ! あなたの負けです…")
else:
    print("ストライク バッターアウト!あなたの勝ちです!")
何を投げますか?
1. ストレート
2. カーブ
3. シュート
4. フォーク
>> 1
ストレートを投げた!
・・・・・・・・・・
バッターはストレートを狙っていた!
逆転サヨナラ! あなたの負けです…

*1:ストレート固定とかの方が簡単ですが、ゲームにならないので…

第5回 プログラムをきれいにする

6. リストを使って書き直す

このプログラムでも要件は満たしますが、少し工夫した書き方をしてみましょう。
今のコードでは、球種を増やしたり変えたりする際、最初のプロンプトメッセージと、次のif文の2箇所をずれないように直す必要があります。今回は影響も2箇所と少ないですし、球種もそんなには変動しないと思われますが、この変更が大変なプログラムではそうはいきません。この直しやすさのことを「保守性」といいます。具体的な修正例を見ながら学習してみましょう。

ソースコード

import sys
import time

species = ["ストレート", "カーブ", "シュート", "フォーク"]
print("何を投げますか?")
num = 0
for sp in species:
    num = num + 1
    print(str(num) + ". " + sp)
pitch = int(input(">> ")) - 1
print(species[pitch] + "を投げた!")
for i in range(10):
    time.sleep(0.2)
    sys.stdout.write('・')
何を投げますか?
1. ストレート
2. カーブ
3. シュート
4. フォーク
>> 2
カーブを投げた!
・・・・・・・・・・

■解説

0. このプログラムを理解するのに必要な知識
  1. リストを使うと複数の値を一括で扱える
  2. for文でリストの内容を1つずつ扱う
  3. +演算子で文字の結合
  4. 数値型と文字列型の違いと変換
  5. インデックスを指定してリストの値を参照する
1. リストを扱う

pythonにはリストという変数の型があります。
おみくじではfortuneという変数が登場し、中身は整数でした。同じように文字列を保持する変数もありますが、その他に複数の値を格納するリストという型があります。

変数 = [値1, 値2, ...]

リストを作成するには、「[ ]」(角カッコ、ブラケット)で囲み、複数の値を「,」(カンマ)で区切ります。値が固定の文字列の場合、「'」「"」で囲み、変数を使うこともできます。

2. for文でリストを扱う

for文は繰り返しの構文ですが、実は、リスト*1をひとつずつ取り出しながら繰り返し処理を実行します。

for 変数 in リスト:
    繰り返すコードブロック

リストの要素を変数に代入しながら続くコードブロックを繰り返し実行します。
f:id:inato_gen:20190219194549j:plain
つまり、

species = ["ストレート", "カーブ", "シュート"]
for sp in species:
    print(sp)

の動作はコードで表すと次のようなことになります。

sp = "ストレート" #speciesの1つ目
print(sp)
sp = "カーブ" #speciesの2つ目
print(sp)
sp = "シュート" #speciesの3つ目
print(sp)
#終わり
おみくじで登場した次のfor文
for i in range (10):
では、変数がi、リストがrange(10)となっています。range関数は、この書き方だと0から始まる10個の連続した数字を持つリストを返します*2。 つまり今回覚えた書き方で表すと
rng = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for i in rng:
のようなイメージとなります。これで10回繰り返すことを実現していたんですね!
3. 文字列の結合

複数の文字列を結合(連結)するには+演算子を使います。

文字列 + 文字列 #連結した文字列を返す

+演算子は加算の演算子でもあります。どちらで動作するかは左右のデータ型によって決まります。
そのため、「数値 + 文字列」と記述するとエラーになります。

4. 文字列型と数値型の違い

その名の通りですが、

文字列型:文字としてのデータ(名前やメッセージなど)「'」や「"」で囲む。「数字」でも文字として扱われる
数値型:計算が可能な数値データ

具体的に見てみましょう。

text = "1"
num = 1
print(text)
print(num)
1
1

特に違いはありませんね。
では演算してみます。

print(text + text)
print(num + num)
11
2

違いが出ました。これは文字列は文字結合ができるのに対して、数値型は数学的な(算数?)扱いとして加算ができたということです。

文字列から整数に変換するにはint関数を使います。数値に変換できない文字の場合、エラーになります。

num = 1
text = "2"
print(num + int(text))
 3

逆に数値から文字列にするにはstr関数を使います。

print(str(num) + text)
12
5.リストの要素に対するアクセス

リストは複数の値をまとめて管理できるデータ型ですが、その1つの値を参照するには、どうすればよいでしょうか?
リストの何番目の値が欲しいかを指定して参照することができます。この「何番目」のことをインデックスまたは添字といい、次のように記述します。

print(species[0])
ストレート

リストの添字は0から始まります。図にすると次のような構造になります。
f:id:inato_gen:20190221202647j:plain
また、添字は数値型の値であればいいので変数が使えます。これによってユーザーが入力した番号に対応する球種を取得しています。

ただし、この場合はデータの型についで注意が必要です。input関数の戻り値は文字列、添字に必要なのは数値でした。なので変換が必要です。
pitch = int(input(">> "))
print(species[pitch])
動作を追いかけてみましょう。
#1が入力されたものとする
pitch = int("1") #文字列の1
pitch = 1 #数値の1に変換
pitch = 1
#代入演算子が処理されて変数pitchの値が1になる
print(species[1]) #変数pitchの値を展開
print("カーブ") #リストの要素を取得
print("カーブ")
#print関数が実行され、「カーブ」と表示される

*1:正確にはシーケンス型オブジェクト

*2:正確にはrange型のオブジェクトです

第4回 野球ゲームを作ってみよう

5. 野球ゲームを作ってみよう。

といっても、簡単なものです。ゲームをする人はピッチャーで、球種を選んでもらいます。
9回裏2アウト満塁、カウント2-3の場面で、何を投げるのか。
あらかじめバッターが狙っていた球種と一致すればヒット、違えば三振です。

■要件

まずは、プレイヤーからの入力を学びます。
最初に「何を投げますか?」と言うメッセージと、球種の一覧を表示して入力を待ちます。
選択肢は

  1. ストレート
  2. カーブ
  3. シュート
  4. フォーク

とします。対応する番号を入力すると、「ストレートを投げた!」と表示した後、「・」を2秒かけて10回表示します(ひとつあたり0.2秒)。

ソースコード

import sys
import time

pitch = input("""何を投げますか?
1. ストレート
2. カーブ
3. シュート
4. フォーク
>>""")
if pitch == 1:
    print("ストレートを投げた!")
elif pitch == 2:
    print("カーブを投げた!")
elif pitch == 3:
    print("シュートを投げた!")
elif pitch == 4:
    print("フォークを投げた!")
for i in range(10):
    time.sleep(0.2)
    sys.stdout.write('・')

■解説

このプログラムを理解するのに必要な知識
  1. inputでユーザー入力

また、ここまてで覚えてきた次の知識も使います。復習はバックナンバーを!

  1. print、sys.stdout.write関数で文字を表示
  2. 文字列は「'」「"」改行も含めたい場合は「"""」で囲む
  3. import文で外部モジュールを読み込む
  4. if ~ elif ~ elseで条件分岐
  5. ==で等しいかチェック
  6. インデントでコードブロックに
  7. for i in range(10)で10回繰り返す
  8. time.sleep関数で一時停止
1. 文字入力

javaに比べたら、驚くほど簡単。javaは厳密なので初心者には難しいところがありますね…
input関数を使います。

変数 = input(プロンプトメッセージ)

入力を促すメッセージを表示した後、ユーザー入力を待ちます。input関数はEnterが入力されるまでに入力された値を返します。例文では変数で受け取っていますが、必ずではありません。

第3回 おみくじの気になるところを直す

4. おみくじの気になるところを直す

ここまでで、一応おみくじらしい動きができた訳ですが、みなさん気になっている点があるかと思います。

  1. 表示が縦に長い
  2. 全体が一気に表示される

表示が縦に長い

最初の要件には

■要件
・実行すると「あなたの今日の運勢は・・・」とでて、「・」が10回表示されたあとに「おめでとうございます!大吉です」と表示します。

とあります。
誰もが

実行結果
 あなたの今日の運勢は・・・・・・・・・・・・・

というのを期待したんではないでしょうか?
しかし、実際には、

実行結果
あなたの今日の運勢は・・・
・
・
・
・
・
・
・
・
・
・
おめでとうございます!大吉です

となってしまいます。なぜでしょうか?

これは、

  • print関数は末尾で自動的に改行する

仕様だからです。自動的に改行しないようにするためにはsys.stdout.write関数を使います。

sys.stdout.write(表示する文字列)

ここで、すでに覚えた2つのルールを思い出してください。

  1. 固定の文字列を扱うときは「'」または「"」で囲む。
  2. import文で外部モジュールを読み込む

最初のプログラムを次のように書き換えてみます。

import sys
sys.stdout.write('あなたの今日の運勢は・・・')
for i in range(10):
    sys.stdout.write('・')
sys.stdout.write("おめでとうございます!大吉です")

これだと

実行結果
あなたの今日の運勢は・・・・・・・・・・・・・おめでとうございます!大吉です

これも少し違いますね。
「おめでとうございます!」の前で改行すれば良さそうですが

sys.stdout.write('
おめでとうございます!大吉です')

ではうまく動きません(エラーになります)
pythonでは行末記号(javaでいう「;」)がないので、改行=文の終わりと認識されてしまうためです。
対処方法としては

1. エスケープシーケンスを使う
sys.stdout.write('\nおめでとうございます!大吉です')

※改行記号は「\n」で表します

2. 「"""」文字列を使う

「"""」で囲むと改行を含んだ文字列を記述できます

sys.stdout.write("""
おめでとうございます!大吉です""")

全体が一気に表示される

要件には明記されていませんが、その前に

実行したら、少し遊びの言葉を入れつつ、時間で演出しながら、「おめでとうございます!大吉です」と出すプログラムです。

とあります。ですが、今のプログラムでは「時間で演出」などしていません。
「・」は時間をかけて表示して何かしてる感じを出したいですね。
処理を一時停止させるにはtime.sleep関数を使います。

time.sleep(停止させる秒数)

ここで、次のことを思い出してください。

  1. インデントで繰り返し範囲を指定
  2. import文で外部モジュールを読み込む

for文で10回繰り返している「・」の表示の度に、1秒遅延させるとすると、

import sys
import time
print('あなたの今日の運勢は・・・')
for i in range(10):
    time.sleep(1)
    sys.stdout.write('・')
print('\nおめでとうございます!大吉です')

のようになります。
しれっと2箇所をprint文に戻しましたが、どうなるかは実際に動かしてみてください。