Open In Colab

2. Pythonの基本 その2:

[この章の目的] プログラミングで非常に重要な概念である配列や繰り返し処理について学ぶ。

※授業では最初に、一番最後の節にある条件分岐から説明します。

2.1. リスト

実際にいろんなデータを扱う際には、値や変数をまとめて処理したくなります。 こうしたときに必要になるのが以下で扱うリスト型を始めとする配列です。

リストは、値や変数などを角括弧[ ]で括り、要素をカンマで区切ることで作ることができます。
例:

[1.0, 2.0, 3.0]

リスト自体を変数にすることも可能です。

heights=[178.0, 180.0, 153.0]

# 変数heightsをprint
print(heights)

# 変数heightsの型をprint
print("変数heightsの型(type)は", type(heights))  

変数名に関する注釈

一時的な変数を使う場合はatmpといった意味のない変数名でも十分なのですが、自身でコードを書くときはなるべく後で自分や他人が見たときになんの変数のつもりか分かるような変数名にしましょう。 5日後の自分も他人のようなものです。

リストの要素にできるのは数値だけではなく、文字列のリストも作ることができます。

names = [ "Aさん", "Bさん", "宇大太郎さん"]

リストに入っている要素の数は、len()関数(lenはlengthの略)で見ることができます。

print("リストnamesは", names)
print("長さは", len(names))

当然、長さに名前をつけて適当な変数として定義することもできます。
関数の出力の結果を何度も後で再利用する場合などは、このように変数に代入しておくと便利です。

ln_a = len(names)
print("リストnamesの長さは", ln_a, "で、型は", type(ln_a)) 

文字列と数値を組み合わせたリストも作ることができます。
例:名前、身長、体重 =>

["Aさん", 178, 66]

また、これを拡張して、入れ子に(リストのリストを作成)することもできます。

 [ ["Aさん", 178,66], ["Bさん", 180, 70] ]

2.1.1. リストに関する禁止事項

リストの変数名を絶対に!絶対に!!絶対に!!!listにしない。

list = [1,2,3] #これはエラーにはならないが、絶対ダメ

listには、リストと互換性のある配列をリストに変換する組み込み関数としてあらかじめ使われているため、このような変数名を使うと、その後list関数が使えなくなる。

Google Colab.では、予約語(1章で前述)や組み込み関数(3章で後述)と呼ばれるものを入力した時点で色が変わる。
もし、色が変わるようであれば、名前の衝突を割けるため変数名を変えること。

もし誤って予約語や組み込み関数と同じ名前の変数を定義・代入してしまい、想定する挙動が得られない場合は、一度ランタイムを再起動することで、その変数を消去することができる。

2.1.2. リストの要素にアクセスする, インデックス(index)

リストの中の要素にアクセスするときは、半角括弧を使って[整数]といった形で”番地”を指定します。
このときの番地(あるいは座標といっても良いですが)を指定する整数のことをインデックス(index)と呼びます。

【重要】Pythonでは要素にアクセスするためのインデックスは1からではなく、0からカウントします!

person = ["Aさん", 178, 66]
print(person[0])
print(person[1])
print(person[2])

ですので、person[3]にアクセスしようとすると、

print(person[3])

list index out of range(リストのインデックスが用意された範囲(range)を逸脱している)というエラーが出ます。

慣れないうちは0からカウントするのを変に思うかもしれませんが、これはプログラミング言語の仕様によるもので、 Pythonの他にもC/C++なども0からカウントします(ちなみにFORTRAN, Juliaなどは1からカウントします。)。
こうした0-based indexingと呼ばれる言語の利点はいくつかありますが、代表的なものは負のインデックスが自然に使えることでしょうか。

理工系分野では、教科書などで行列やベクトルの要素を指定するのに1-based indexingを使うことも多いため、混乱するかもしれません。慣れるまでは注意が必要です。

person = ["Aさん", 178, 66]
print(person)
print(person[-1])
print(person[-2])
print(person[-3])

[-1]でアクセスすれば末尾の要素に、[-2]番目の要素は後ろから二番目といった具合です。

長いリストを作って「えっとこれ前から何番目の要素だったっけ…?」というときに、
後ろからX番目の要素といったように、サクッと取得できるのが便利なときもあります。

入れ子にしたリストの要素を見たり、使いたいときには、少々慣れが必要です。

mylist = [ [[1,2], [3,4]], 5, 6]

このような場合では、一番外側の括弧から数えて、何番目の階層になっているかを考えることが必要になります。

練習してみましょう。

練習

  1. [+コード]でセルを以下に追加し、下のリストを作成する
    data = [ ["Aさん", 178,66], ["Bさん", 180, 70] ]

  2. data[i][j]のi,jに可能な整数を入れて、Aさんの身長、Bさんの体重などをprint関数で表示してみましょう。

  3. 2.と同じことを、負のインデックスを駆使してやってみましょう。

  4. data = [ [[1,2], [3,4]], 5, 6]を作成して、 len(data[0]) #0番目の要素の数をprintしてみましょう。

  5. print(data[0][0][1])を実行してみましょう。

  6. 4.5.の挙動から推測して、aのリストの中から4を取り出したい場合どうすればいいのかprint関数を使いながら考えてみましょう。

テキストセルの内容をコードセルなどにコピー&ペーストしたければ、対象をドラッグして、Ctrl+Cでコピー,Ctrl+Vでペースト出来ます(Macの場合はCtrlに置き換えてください)

2.1.3. リストの結合

2つのリストを和+で結合することもできます。

a=[1,3]
b=[2,4]
c= a+b
print(c)

上のリストを”座標”だと思えば、要素ごとの和になってほしいような気もしますが、リストの足し算は要素ごとの和ではなく”リストの結合”を意味します。

補足: 要素ごとの和のような、数学などで必要な演算は以降で扱うnumpyというモジュールを使ってndarray型と呼ばれるものにすれば簡単に実行できます

長さや階層の異なる2つのリストでも可能です.

c=["Aさん"]
d=[[170.5,60.0],["東京都出身", "250歳"]]
c+d

2.1.4. リストに要素を加える

リストにあとから要素を加えたくなるときもあります.
そんなときは append関数か+=を使います。

person_1 = ["Aさん", 178,66] 
person_1.append("O型")
print("person_1", person_1)

person_2 = [ "Bさん", 180,70] 
person_2 += ["A型"]
print("person_2", person_2)

厳密には両者は違うのですが、この授業では見た目がスッキリするので後者をよく使います。
入れ子のリストを作るときは

data = [ ]
data +=  [ ["Aさん",178,66] ]
data +=  [ ["Bさん",180,70] ]
print("data", data)

などとする。空リストに既存(新規)のリストを追加したいときに、上のように が二重に必要な理由はリスト同士の和の演算が結合となることから理解できる。 実際に1重の括弧で試してみよう:

data2 = [ ]
data2 +=  ["Aさん", 178,66] # 1重カッコ これだと右辺のリストを空リストに結合することになる
data2 +=  [ "Bさん",180,70] # これも同様
print(data2)
print("data2", data2)

こうしてしまう(リスト同士の単なる結合)と、人ごとにデータが区切られていないので扱うのに不便となる。

2.1.5. リストの値の更新

リストの要素は後から更新することもできます.

data =  [ ["Aさん", 178,66],["Bさん",180,70]  ]

というリストがあったとして、Aさんの体重を修正したければ、

data[0][2] = 58 #Aさんの体重を更新
print(data) #リストを表示

入れ子になっているリストに血液型を追加したければ以下のようにすればよい。

data[0] += [ "A型" ]
data[1] += [ "B型" ]

print(data)

2.1.6. 要素に対応するインデックスの取得

index関数を使ってリスト内の、[興味のある要素]のインデックスを取得することができます。

tlist = [ "いちご", "りんご", "ぶどう"]
tlist.index("りんご")

重複する要素がある場合、初めにヒットしたインデックスを返します。

tlist2 = [ "いちご", "りんご", "ぶどう","メロン","りんご"]
tlist2.index("りんご")

複雑なデータを扱う際に「あれ、あの要素ってどこの番地にあるんだっけ?」といった状況に便利な関数です。

2.1.7. スライスを用いた部分リストの取得

以下のaのようなリストがあったとき、

data = [ "years", 1990, 1995, 2000, 2005, 2010, 2015, 2020]

始点,コロン(:),終点でインデックスの範囲を指定して、部分的に取り出すことが出来ます。

data[2:5]

終点のインデックスに相当する要素は取り出されないことに注意しましょう。

また、

data[1:]

とすると、最後の要素まで含まれます。

負のインデックスを使用することもできます。

data[1:-1]

2.1.8. \(\clubsuit\) リスト操作の注意点

授業で扱う程度の内容のプログラミングに関する疑問は、ググれば自分でだいたい解決することができます。
ただしこの項目で述べることは(初学者にとって)「直感に反するのだけど、どこがおかしいのか分からないのでそもそもどうググっていいかが分からない」という点で、少し事情が異なります。

例を見せるために、以下の2種類のリストを用意します.

data1 = [[ "Aさん", 178,66], [ "Bさん",180,70] ]
tmp = ["Aさん", 178,66]
data2 =[ tmp, tmp] 

print("data1", data1)
print("data2", data2)

data2のようにまず雛形のリストtmpを作って人数分の長さを持つリストを作ってから中身を編集しようと考えた場合、

data2[1][0]="Bさん"
data2[1][1]=180
data2[1][2]=70

という操作を思いつきます。このとき、data2の2つ目(0から数えて1番目)の要素だけを編集したつもりでもdata2printすると

print(data2)

data2の最初の要素(Aさんのままであってほしいリスト)まで上書きされてしまっています。
これは直感に反しているという点で、初学者が陥りやすい落とし穴です。
※C言語などを学習していれば、参照渡し(ポインタ渡し)などである程度馴染みがあるかもしれません。

tmp = ["Aさん", 178,66]
data2 =[ tmp, tmp] 
print(id(data2[0]), id(data2[1])) #それぞれのidを調べてprint
print("idが等しいか", id(data2[0])== id(data2[1])) #id()は変数のidを確認する関数

今の場合、data2を最初に作ったときには0番目と1番目の要素(リスト)は同一のidを持つtmpというリストです。
したがってtmpの中身を書き換える操作(data2[1][0]="Bさん")は、tmpの更新を通してdata2の要素いずれもに影響します。

このように、(特に)リストを入れ子にする際には、注意が必要です。なんかへんだな?と思ったときは要素のidに気を配ってみるのも重要です.
上のコードで、意図したものと違う挙動を起こした原因は、リストtmp参照する形data2を作ったことでした。
これはcopyモジュールのcopy関数を用いて配列のコピーを作成することで回避できます。(モジュールについては4章で説明します)

import copy #copyというモジュールをインポートする
tmp=["Aさん",178,66]
data2 = [ tmp, copy.copy(tmp)]
print(id(data2[0]) == id(data2[1])) # ← data2の0番目と1番目のidが同じ(参照元が同じ)だと困るのでFalseであってほしい

また、リストのリスト(ネストされたリストといったりします)それ自体をcopyしたいときは、 copy.deepcopy()を使います。

import copy
data = [[ "Aさん", 178,66], ["Bさん",180,70] ]

copydata = copy.copy(data)
deepcopydata = copy.deepcopy(data)


print(id(data), id(copydata),id(deepcopydata))
print(id(data[0]), id(copydata[0]), id(deepcopydata[0]))

上を見るとdataというリストとcopydataという”リスト同士”が異なるidを持っていても、それぞれの0番目の要素のidを見てみると、同じものを参照していることがわかります。
このように、ネストされたリストをコピーして別々に扱いたい場合は特に注意が必要で、deepcopyのような作業が必要となります。(私も初めてプログラミングで配列を使ったときに、この点に気が付かずに時間をかなり溶かしました)

2.2. \(\clubsuit\) タプル・辞書型

\(\clubsuit\)のついたセクションは発展的な内容で、授業では時間の都合上スキップすることが多い項目になります。
以下のタプル・辞書型は使いこなせれば便利ですが、授業では必ずしも必要ではない(リストで代用できる)ので、興味に応じて学習してください。

リストのときと同様に、タプルや辞書型の変数名を絶対にtupledictにしないように注意してください。

2.2.1. \(\clubsuit\) タプル(tuple)型

タプルは”immutable”(要素が変更不可)なリストと覚えておけばよいでしょう。 リストは要素を[]で囲むことで作ることができました。タプルは丸括弧()で囲むことで作ることができます

values = (1.0, 2.0, 3.0)
print(values, type(values))
print("0番目の要素は", values[0]) ##要素にアクセスするときはタプルのときでもやはり角括弧を使う

値をリストのように格納しておきたい、という状況下かつ、リストの値を後で変更することがないなら タプルを使うのも一つの手です.タプルを使うメリットとしては以下の通りです:

  1. (予期せず)値を更新しようとするとエラーを吐いて教えてくれる

  2. (場合によりけりですが)リストよりも早く処理が実行される

# 1.の例 円周率のべきを保持しておいて再利用する状況をイメージ
pi = 3.141592653589793
pi_pow = (1, pi, pi**2, pi**3)
print("piの0乗から3乗まで", pi_pow)

# (誤って)要素を変更しようとするとエラーとなる
pi_pow[0] = 0

2.の例: 次に中身が同じ(1から5000までの整数)リストとタプルを用意して、1万回要素の和を計算するという計算をしてみましょう. この計算自体に意味はありません。
timeというライブラリを使って2つの作業に係る時間を調べてみると…

import time 
itnum=10_000 

#リストを使った計算
t0= time.time() 
a = [ i for i in range(1,5001) ] #リストを定義
for i in range(itnum):
    sum(a)
t1 = time.time()

#タプルを使った計算
t2= time.time() 
b = ( i for i in range(1,5001)) #タプルを定義
for i in range(itnum):
    sum(b)
t3 = time.time()

print("リストの処理にかかった時間", t1-t0)
print("タプルの処理にかかった時間", t3-t2)

タプルの方が実行時間が短い事がわかります.
今の例では差は人間にとっては気にならない程度の差ですが、複雑な処理になってコードがなかなか計算を完了しないときには、リストをタプルにするなど、コードのパフォーマンスを改善する作業が必要となります。

2.2.2. \(\clubsuit\) 辞書型

辞書型は、キーと値の2つの要素をあわせ持つ型です.リストにいれたものをいっぺんに扱うときに、毎回インデックスを指定したりループで要素を回して、望むものを持ってくるのは面倒です。

たとえば以下の名前と年齢のリスト

data=[[ "Aさん",25],["Bさん",21],["Cさん",18]]

があったとき、この章で扱う知識(ループ&条件分岐)だけを駆使して Bさんの年齢を取得するには例えば以下のようなコードになります

data=[[ "Aさん",25],["Bさん",21],["Cさん",18]]

for person in data:
    if person[0] == "Bさん" :
        print("Bさんの年齢=", person[1])

このようなfor文とif文などを組み合わせた要素の探索が面倒なら、予め名前と年齢という2つの関係のある量を”辞書”として定義してしまえばよいのです。辞書型は波括弧{}で囲むことで構成できます.

Dict_age = {'Aさん' : 25, 'Bさん': 21, 'Cさん': 18}

Bさんの値(今は年齢)を知りたければ以下のように一行で取得可能です.

Dict_age["Bさん"]

辞書を作る際には、 要素を取り出すためのキー(key)と値(value)の組み合わせで指定します. その際

{"key" : value}

keyvalueをコロン:をつかって区切り、複数要素を入れる際はカンマで区切ります.
keyは文字列や数字(たとえば小中学校の出席番号とか)を使用することができvalueは様々な型が使えます。

値(value)として、リストを保持することもできます.次のように年齢と出身県のリストを値にもつ辞書にしてみましょう.

Dict = {'Aさん' : [25,"栃木県"], 'Bさん': [21,"茨城県"], 'Cさん': [18,"群馬県"]}

Cさんの個人情報にアクセスする際は

Dict["Cさん"]

とすればいいことがわかります。 慣れないうちはタプルや辞書を使わずリストだけ覚えておけば問題ないのですが、 複雑な処理になると(タプルにしないと)パフォーマンス差が顕著になったり(辞書にしないと)コードの可読性が低くなったりミスの原因になるので、タプルや辞書型もうまく組み合わせながら使えると良いです。

2.3. ループ処理

ループ処理は、プログラミングの中で最も重要な概念の一つです。
ループ処理を使うことで、同じ処理を繰り返し行うことができます。

2.3.1. for文

まず以下のコードを実行してみましょう:

for i in range(5):
    print(i)

上のコードは、

iを0から4まで変化させながらiをprintする作業を繰り返しなさい

という処理になっています。新しく登場したいくつかの要素を順に説明します。

まずrange()関数は、range型のオブジェクトを生成する関数で、range(5)は0から整数を5つ(0,1,2,3,4)を生成する関数です。やはり0始まりで、5が含まれないことに注意です。

range(5)をそのままprintすると、range(0,5)と表示されるだけで、よくわかりません。
このようにrange関数の出力はそれ自体が、rangeという抽象的な型を持ったオブジェクトになっています。

print( range(5) )
print( type(range(5)))

rangeは、リストと互換性を持つため、list関数を使ってリストに変換することができます。

print( list(range(5)) )

なおrange関数の引数をrange(始点,終点,間隔)と3つにすると、より複雑な整数の集合を作ることもできます。

for i in range(0,6,2):
    print(i) #やはり6は含まれないことに注意

in(予約語なので色が変わる)は、irange()で指定した数値の範囲(リストみたいなもの)を順番に突っ込んでくれる機能を持つものと理解しておけばひとまずOKです。

次に、for文を使ってリストの中身を順番に表示させてみましょう。

fruits = ["いちご", "りんご","ぶどう","メロン"] #リストを定義
for tmp in fruits:
    print(tmp)

上のコードでは、tmpという変数にfruitsというリストの中身が順番に当てはめられている事がわかります。
ループを用いてリストの中身にアクセスする方法は主に2通りで、

  1. インデックスのループを回してリストにアクセスする

  2. リストの要素に順番にアクセスする

で、上のコードは2.に相当します。上の例で、1.の方法を採る場合は

for i in range( len(fruits) ) :
    print( fruits[i] )

とすればよいです。

インデックスと要素を同時に取得して使いたいときにはenumerate関数を使って

for i, tmp in enumerate(fruits):
    print("リスト内の"+str(i)+"番目の要素は:", tmp)

上記のほか、forinを用いて、リスト(またはリストと互換性のある多くの型)の中身を順番に取り出すことができます。

2.3.2. ブロックとインデント

さて、上のfor文のコードには、関数の前に半角スペースが4つあるのに気がついたでしょうか?
Pythonでは、for文を始め様々な処理の際に一連の作業ブロックを、半角スペース4つ分インデントを下げることで表現します。

Google Colaboratory環境では、[ツール]→[設定]→[エディタ]→[インデント幅(スペース)]で変更ができます。もし、インデントが2つになっている場合は、4つに変更してください。 環境によっては、2つや、スペースの数が混在していてもよしなに推定して実行してくれるものもありますが、自身でコードを作成する際には、必ず4つ半角スペースを開けるようにしましょう。

下のように、for文を使っているにも関わらず正しくインデントされていない場合はエラーが出ます。

for i in range(2):
print(i)

さて簡単な例でブロックごとの処理の挙動を確認しておきましょう。

練習問題

以下のコードを実行すると、何回数字がprintされるでしょうか?
コードを実行する前に、三択で答えてみてください。

A. 8回

B. 11回

C. 13回

for i in range(2):
    print(i)    
    for j in range(5):
        print(i,j)
print(i,j)

iに関する繰り返し(ループとも呼びます)は、i=0,1の2回で、インデントが1ブロック下がった操作を2回実行します。つまり、print(i)を2回と、jに関するループを2回繰り返します。 2つめのfor文で、jはj=0,1,2,3,4をとりますが、iのループのブロックに入っているので、print(i,j)は合計2×5=10回呼ばれます。 最後の行にあるprint(i,j)はどのループ(ブロック)の中にも入っていませんから、実行されるのは1回だけです。

ということで、答えはC.の13回でした。

慣れないうちは

for i in range(2):
    print(i)
    for j in range(5):
        print(i,j)
    ## End j loop
## End i loop

などのように、どこでループを閉じるのかをコメントして練習するのも良いかもしれません。
コードを編集しているうちに

for i in range(2):
    print(i)
for j in range(5):
    print(i,j)

といったように意図しないインデントになってしまって、
正しい答えを与えない(バグを作ってしまう)可能性があります。

Pythonはインデント(ブロック)が、文法的な意味を持つという特徴がありますので、よく覚えておきましょう。

練習

リスト data = [ ["Aさん", 178,66] , ["Bさん",180,70], ["Cさん", 165,55]]を用意し、
[体重を二乗して身長にかけ合わせた量]の総和を計算するコードを作ってみましょう。

ヒント1: 身長は入れ子になっているリストの[1]番目,体重は[2]番目
ヒント2: total=0.0を定義して、体重の二乗×身長をfor文を使ってどんどん足していく。

###以下は、不完全なコードです。これにあと3行ほど書き足してみましょう。 
###ちなみに答えは 2156493です.
data = [ ["Aさん", 178,66] , ["Bさん",180,70], ["Cさん", 165,55]]
total = 0.0

さて、ループ(for文)の使い方がなんとなく分かったでしょうか?
どんどん使ってみて、なれておいてください。

他にも、たとえば、 1, 10, 100, 1000, 10000,…というループを回したい時、

for i in [1,10,100,1000,10000]:
    print(i)

と書くのではなく、べき乗に対するループを回す

for p in range(5):
    print(10**p)

など、考えたい値を直接ループで扱うのではなく間接的な値(今の場合p)を扱うなどの考え方も、コードをスッキリさせる上で重要です。

2.3.3. リスト内包表記

for文の別の使い方として、リスト内包表記と呼ばれる書き方があります。
これを使うと、要素がたくさんのリストを簡単につくることができます。

# year を1900から2000まで変えていき、その値を詰めたリストyearsを作成
years = [ year for year in range(1900, 2001) ] 
print(years)

2.3.4. break ループ処理を途中で抜けたい場合

breakfor文を途中で抜け出すのに使います。用途としては目的を果たしたのでもうループを繰り返す必要がないとき予期せぬ事が起きた場合にループ処理を終わらせてプログラムを終了するときなどに使います.

以下のような状況を考えてみましょう

Aさんは、掛け金が20万円、コインの表が出たら掛け金+20万円もらえるゲームをしています。 Aさんは、所持金100万円からスタートして、一度でも負ければゲームを辞めることにしました。 ただし、ゲームへの参加回数は最大10回とします。

for文を使って、このゲームをシミュレーションするプログラムを書いてみましょう。

import random #これがなにかはとりあえず気にしない

# 最初の手持ち金と掛け金を定義
money = 1_000_000
bet = 200_000

# ゲームを開始, 最大10回まで繰り返すことをfor文で表現
for i in range(10):   
    print(str(i+1)+"回目のゲームスタート")
    money = money - bet    
    result = random.choice([True,False]) # 1/2の確率で勝ちか負けかを決める
    if result: # result==True, つまり勝った場合
        money += 400000
        print("勝った!!! 所持金は", money, "円です")
    else :
        print("負けたのでおしまい")
        break # for文を抜ける

print("最終的な所持金は..."+str(money)+"円だ")

random.choice([True,False])は1/2の確率でTrueFalseを与える処理になっている。 True/Falseをそれぞれ勝ち負けに対応させられるので、その結果に応じて処理を分岐させていることが分かる。

2.3.5. \(\clubsuit\) While文

forに似たものとしてwhileもよく用いられる。

while文は、原理的にはfor文とbreakを組み合わせたコードでも実装可能なので発展的な内容(\(\clubsuit\)マーク)として授業ではスキップするかも…

プログラムを書いていくうちに、繰り返しの数が前もってわからないケースに遭遇することがある。たとえば

  • 連続で6が5回でるまでサイコロをふる

  • 利益がある値を超えるまで株の売買をくりかえす

といったイメージ。この様な場合は、何回処理を繰り返せば良いか予め知ることはほとんど不可能となる。

サイコロの例の場合だと、たとえば5回ふっただけで6が連続で出る奇跡的な状況も有りえますし、1000回ふっても100万回降っても連続で5回は出ないかもしれません。(某漫画の地下チンチロ編のようなイカサマサイコロを使用するとグッと確率はあがります)このような処理を実装したい場合にはwhile文を使います.

さっきのAさんのギャンブルを例に

手持ち100万からゲームを開始して 「手持ちが50万以上150未満ある限り賭けを続けるコード」

を作ってみましょう

import random #ランダムに勝ち負けを決めるためrandomというライブラリを使います

money = 1000000
bet = 200000
#手元に50万以上-150万未満ある限り賭け続けることをwhile文で表現
while 500000 <= money < 1500000: 
    money -= bet
    result = random.choice([True,False])
    if result:
        money += 400000 
        print("勝った!!")
    else :
        print("負けた..")
    # whileの条件文は、ブロックの最後に評価されるので、ここで条件を満たしているかを確認
    print("現在の所持金は"+str(money)+"円", "条件のチェック", 500000 <= money < 1500000)
    # while文の条件がFalseであれば、自動的にループを抜ける
print("最終的な所持金は..."+str(money)+"円だ")

といった具合です. 使用例が思い浮かばなければ「whileなんてのがある」とだけ覚えておけば当面はOKです.
ちなみに上のコードはrandomモジュールの(疑似)乱数を用いているので、実行ごとに買ったり負けたり結果が変わります。何度か実行してみてください。

2.3.6. \(\clubsuit\) continue

continue文は、forwhile文の中で[以降の処理を無視する]のに使います.
具体的な用途としては[特定の条件を満たす場合にのみ適用する処理を書きたい]場合などがあります.再びカジノの例で考えてみましょう.

上のfor文の例で、最初の2回だけは負けてもゲームを続けるというルールを追加したいとします.

money = 1_000_000
bet = 200_000

for i in range(10):   
    print(str(i+1)+"回目のゲームスタート, 所持金は", money, "円")
    money = money - bet    
    result = random.choice([True,False]) # 1/2の確率で勝ちか負けかを決める
    if result: # result==True, つまり勝った場合
        money += 400000
        print("勝った!!! ")
    else :
        # 追加した部分, continueと同じブロックの続く処理をスキップする
        if i <= 1:
            print("負けたけど...まだまだ挽回するぞ...")
            continue 
        print("負けたのでおしまい")
        break 

print("最終的な所持金は..."+str(money)+"円だ")

2.3.7. 例外処理

以下の処理は[ある値から都度10を引いていって平方根を取った値を表示する]というコードです

import math #sqrt(square root)関数を使うための1行
s = 114
while True:
    s -= 10
    print(math.sqrt(s)) 

しかし、あるところでsの値が負になってしまい、sqrtが計算できなくなってしまいます(虚数を導入すれば定義できますが、mathsqrt()関数は非負の引数に対して定義されていますのでエラーが出ます)

エラーが出る(≒予期しないことが起こる)とプログラムはそこで止まってしまいます。
通常はそれで良い(問題があればエラーが起きないと対処しようがない)のですが、複雑な状況になると「エラーを無視してとにかくプログラムを最後まで実行させたい」「エラーが起こるときにエラーを回避するような仕組みをプログラム自体に実装したい」といった状況も起こりえます.

上の例でいうと「sの値が正なら平方根を表示して、負の場合はエラーメッセージだけを表示してエラーが起きた回数をカウントする」という作業が必要な場合は以下のように

  • try: 試行したい処理のブロック

  • except: 例外(エラー)が起こった場合の処理のブロック
    という2つの操作(try,exceptともに予約語)を駆使することで、最後までプログラムを実行させることができます.

import math
s = 124
hit = 0 
for i in range(20):
    s -= 10
    try:
        print(math.sqrt(s)) 
    except :    
        print("sの値が"+str(s)+"になったのでsqrtが計算できません")
        hit += 1
print(str(hit)+"回 sqrtの計算でエラーがありました")

このノートブックでは説明しませんがexceptの後に具体的な例外を指定して例外の種類に応じた操作を行うことも出来ます。https://docs.python.org/ja/3/library/exceptions.html

2.4. 条件分岐 (if文)

if文では、書かれた条件が成立した場合、後に続くブロックを実行します。
if文を使って命題の真偽を判定することで、条件分岐を表現することができます。

a=3.0; b =-0.3
if a > b: 
    print("aはbよりも大きい")
if b > a:
    print("bはaよりも大きい") ##これは呼び出されない

条件を満たすときはA,満たさないときはBをしたい、という場合にはelseを使う。

if a< b:
    print("aはbよりも小さい")
else:
    print("a>=b") 

elif(else ifの略)を用いれば、もう少し複雑な条件を表現することができる。

たとえば、もし条件1が満たされればAを実行、条件1は満たされないが条件2が満たされればBを、さらに1も2も満たされない場合はCを実行するという場合は下記のようなコードになる:

if a < b: #条件1
    print("a<b")
elif  a ==b: #条件2
    print("a=b")
else:
    print("a>b")

if文は入れ子構造にすることもできる。その際はインデントを使ってブロックを表現する。

たとえばaが偶数の場合は値をそのまま表示して、aが奇数の場合は3の倍数かそうでないかで処理を変える事を考えると、以下のようになる

if a % 2 == 0 :
    print(a)
else:
    if a % 3 == 0 :
        print(str(a)+"は3の倍数です")
    else:
        print(str(a)+"は3の倍数ではありません")

if文を使って条件分岐を作るときは条件分岐にモレがないか注意が必要です。例えば、変数aの値によって処理を行う場合

if a > 0:
  ## なんらかの処理1
if a < 0:
  ## なんらかの処理2

と書いてしまうと、a=0の場合、if文を2つともすり抜けてしまい、バグの原因になることがあります。はじめのうちは少々面倒でもelseを使って、意図しないすり抜けがないかチェックするのが良いでしょう。

a = 0 #aをいろんな値に変えて実行してみてください
if a > 0: #aが0より大きい場合
    print("処理1:a+2=", a+2)
elif a<0:    
    print("処理2:a*2=", a*2)    
else:
    print("ゼロだよ?なんにもしなくていいの?")