# for 文ってなに?
オブジェクトを
取り出す時に使います。
この記事は Python の for 文の「考え方」と「動作の仕組み」について、説明しています。 この記事は Python の for 文にある break 文や continue 文などの「使い方」については、 説明していません。
# 1. for 文
# 1.1. 書き方
in の中にはオブジェクトの集まりがはいります。
for 取り出したオブジェクト in オブジェクトの集まり:
取り出したオブジェクトに対する処理
Python では for 文を使って1つずつオブジェクトを取り出すことができます。
# range
for integer in range(3):
2 * integer
# 0
# 2
# 4
# list
basket = ['apple', 'orange', 'grape']
for fruit in basket:
print('**' + fruit + '**')
# **apple**
# **orange**
# **grape**
# 1.2. 考え方
もし Python の for 文を日本語に訳すなら、こんな感じになるかなと思います。
basket = ['apple', 'orange', 'grape']
for fruit in basket:
print('**' + fruit + '**')
# **apple**
# **orange**
# **grape**
バケットの中にある1つ1つのフルーツに 対して 順番に繰り返す。
Iterate for each fruit in a basket sequentially.
- ~のために、~に対して
・What can I do for you?
: あなたのために私に何かできることはありますか。
# 2. range って、なに?
答え: range オブジェクトは、整数の集まりです。
"in にはオブジェクトの集まりはいります" って言ったけど、 range は別にオブジェクトの集まりではなくない?という疑問があります。
ここでは数字の集まりだと考えておいてください。 range(10) なら 0 ~ 9 までの数字です。 range(0, 10, 2) なら 0 ~ 9 までの数のうちの偶数となります。
# range(10) は
r = range(10)
# 0 ~ 9 というオブジェクトの集まりです。
r[0] # 0
r[1] # 1
r[2] # 2
r[3] # 3
r[4] # 4
r[5] # 5
r[6] # 6
r[7] # 7
r[8] # 8
r[9] # 9
# range(0, 10, 2) なら
r = range(0, 10, 2)
# 0 ~ 9 というオブジェクトの集まりのうち
# 偶数のオブジェクトになります。
r[0] # 0
r[1] # 2
r[2] # 4
r[3] # 6
r[4] # 8
もちろん range は、何回か繰り返す処理をする時に使うと覚えておいても全く差し支えはないのですが
range 型は、... 一般に for ループにおいて特定の回数のループに使われます。
4.4.6. range - Python 標準ライブラリ (opens new window)
range オブジェクトは、整数の集まりと認識しておいた方が for 文の理解が進むかなと思います。
で、実際に Python の for 文が1つずつオブジェクトを取り出すように設計されていることを、 見ていただきたいと考えています。
range オブジェクトは、整数の集まりです。
# ◯ なんで range は 0 から始まるの?
答え: 完全に推測ですが まず第一に、 おそらく範囲を指定するときに数学的にそちらの方が綺麗な表現になるから。 また第二に、配列を表現するときに 0 からはじめた方が計算機科学的にそちらの方が綺麗な表現になるから。
すこし話を脱線しないといけなくなります。 主題とは関係なく読まなくても先に進めます。
# 3. よくやってしまいがちな失敗
# 3.1. for 文の中でリストに代入できないのは、なんで?
for 文の中でリストに代入しようとして、昔よく失敗しました。
lst = [0, 1, 2, 3]
for element in lst:
element = None
lst
# [0, 1, 2, 3]
# あれ?
何をしているかというと、単純に変数 element に代入し続けてるだけだからです。
lst = [0, 1, 2, 3]
# for element in lst:
element = None
element = None
element = None
element = None
正しくは、次のようにします。
for i in range(len(lst)):
lst[i] = None
lst
# [None, None, None, None]
# うまく行った
何気ない失敗のようですが、Python の代入とはそもそも一体何かについて考える良いきっかけかなと思います。 Python の代入とは何かについては、こちらで説明させていただきました。
# 3.2. for 文の中でリストの要素を削除できないのは、なんで?
答え: 順番が崩れてしまうから
# 3.2.1. 問題
例えば for 文の中でリストの要素を全て削除しようとして pop, append, del を使うと...
lst = [0, 1, 2, 3, 4, 5, 6 ,7 ,8 ,9]
for e in lst:
#
tmp = lst.pop(0)
print(e)
2 個ずつ for 文が進んでしまい、意図した動作ができません。
...
0
2
4
6
8
# 3.2.2. 原因
実は for 文でこう書いたとき
lst = [0, 1, 2, 3, 4, 5, 6 ,7 ,8 ,9]
for e in lst:
#
print(e)
while 文で書き直すとこういう動作をしています。 実際に、このように動作していることを、このあと「イテレータってなに?」 (opens new window) で、説明させていただきます。
lst = [0, 1, 2, 3, 4, 5, 6 ,7 ,8 ,9]
i = -1
while i < len(lst) - 1:
i += 1
e = lst[i]
#
print(e)
# 3.2.3. 原因を問題に当てはめて考えてみる
なので、このように書いたとき...
lst = [0, 1, 2, 3, 4, 5, 6 ,7 ,8 ,9]
for e in lst:
#
tmp = lst.pop(0)
print(e)
次のように処理されてしまっているわけです。
lst = [0, 1, 2, 3, 4, 5, 6 ,7 ,8 ,9]
i = -1
while i < len(lst) - 1:
i += 1
e = lst[i]
#
tmp = lst.pop(0)
print(e)
# 0
# 2
# 4
# 6
# 8
# 3.2.4. 解決策
Python でリストから条件にあった要素を削除したい場合は、リスト内包表記を使うのが便利です。
# 4. for 文と while 文の違い
- for 文 ... 要素をとひとつひとつ取り出して、処理を繰り返す。
- while 文 ... 要素がある限り、処理を繰り返す
while 文は、単純に "条件が True である限り、処理を繰り返す" というよりも、 オブジェクトが "ある" 限り処理を繰り返すということをしています。 これについては if 文ってなに?のなかで説明させていただいています。
# 4.1. for 文は while 文の糖衣構文です。
6 章で見ていただきましたが、for 文は while 文で書き換えることができます。 極論すれば for 文は while 文をわかりやすく書き換えたものです。 このようにわかりやすくかく書き方を 糖衣構文 (opens new window) と言います。
# 簡単
lst = [0, 1, 2, 3, 4, 5, 6 ,7 ,8 ,9]
for e in lst:
#
print(e)
# 複雑
lst = [0, 1, 2, 3, 4, 5, 6 ,7 ,8 ,9]
i = -1
while i < len(lst) - 1:
i += 1
e = lst[i]
#
print(e)
途中で break するようなケースでは for 文よりも while 文の方が早かったりします。 これは for 文がループするたびに条件判定を実施しているからです。
# 5. リスト内包表記
リストの各要素に同じ処理を適用するというのは、頻繁にあることです。 頻繁にある単純な処理なのに3行もかけて書くのは、面倒です。
bucket = ['apple', 'orange', 'grape']
# 3行も必要...
lst = []
for fruit in bucket:
lst.append('**' + fruit + '**' )
# 5.1. 書き方
そこで「リスト内包表記」という簡単にした書き方があります。
[取り出したオブジェクトに対する処理 for 取り出したオブジェクト in オブジェクトの集まり]
bucket = ['apple', 'orange', 'grape']
lst = ['**' + fruit + '**' for fruit in bucket]
このような簡単にした書き方のことを難しい言葉で 「糖衣構文」 (opens new window) と言います。
# 5.2. 考え方
for 文は「繰り返す」、「iterate する」と考えるといいかなと思います。 リスト内包表記では、既に順番に並んでいる要素に対して処理を「適用する」、「apply する」と 考えると受け入れやすいかなという気がします(※あくまで個人の感想です)。
bucket = ['apple', 'orange', 'grape']
lst = ['**' + fruit + '**' for fruit in bucket]
リストの各要素 に対して 処理を適用する。
Apply function f for each fruit in a bucket.
- ~のために、~に対して
・What can I do for you?
: あなたのために私に何かできることはありますか。
以下の例ではリスト内の 0, 1, 2 という整数にそれぞれ関数 f を適用しています。
def f(x):
return x**2
lst = [f(x) for x in [0, 1, 2]]
lst = [f(0), f(1), f(2)]
# 5.3. リスト内包表記は for 文の糖衣構文です。
# 簡単
basket = ['apple', 'orange', 'grape']
lst = ['**' + fruit + '**' for fruit in basket]
# 複雑
basket = ['apple', 'orange', 'grape']
lst = []
for fruit in basket:
lst.append('**' + fruit + '**' )
一般に糖衣構文は使用できる範囲は限定されます。複雑になものを無理やり糖衣構文で書くと可読性を下げてしまいます。
リストの内包 - Google Python スタイルガイド (opens new window)
シンプルなケースの場合のみ利用する。
# 6. 条件式
# 6.1. 条件式は if 文の糖衣構文です。
すこし脱線しますが。 下記の gcd は最大公約数を求める関数です。
# 簡単
def gcd(a, b):
return gcd(b, (a % b)) if a % b else b
# 複雑
def gcd(a, b):
if a:
return gcd(b, (a % b))
else:
return b
三項演算子についても可読性について議論した記事がありました。
複雑な式で三項演算子を使うと、途端にわかりにくくなる。
三項演算子?:は悪である。- Qiita (opens new window)
ちなみに使いどきはいかのような具合になります。 JavaScript ですが、とても参考になるので引用させていただきました。
IMO
— 松田信介@ごみっとくんリリースしました! (@xhackjp1) May 16, 2020
三項演算子を使った方がいいとき#JavaScript #xhack勉強会 pic.twitter.com/zooUaPH3rF
# 6.2. まとめ
糖衣構文は、扱える範囲が単純なものに限られます。 もし複雑になりそうなら、ためらわず低レベルな書き方をします。
# 7. まとめ
# ◯ for 文で使えるクラスは、なに?
オブジェクトの集まりって、なに?
for 文の in の中に代入して繰り返し (iterate) 処理ができるオブジェクトを iterable と言います。 例えば list, set, tuple, dict などが当たります。
では、どのようなオブジェクトであれば iterable と言えるのでしょうか? それについて、すこしずつ見ていきたいと思います。