Python の for 文ってなに?





ひとつ、ひとつ、順番に
オブジェクトを
取り出す時に使います。





f:id:domodomodomo:20180805030321j:plain f:id:domodomodomo:20180805030327j:plain f:id:domodomodomo:20180805143745j:plain f:id:domodomodomo:20180805143749j:plain








この記事は Python の for 文の考え方と動作の仕組みについて、説明しています。 この記事は Python の for 文の使い方については、説明していません。

1 章...for 文の書き方と考え方
2 章...リスト内包表記の書き方と考え方
3 章...range ってなに?
4 章...range は、なんで 0 からはじまるの?
5 章...for 文と while 文の違い
6 章...よくやってしまう間違い
7 章...糖衣構文
8 章...どのクラスなら for 文で使えるの?
9 章...まとめ





f:id:domodomodomo:20180805014814j:plain

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.

for
【前】
1. ~のために、~に対して
・What can I do for you?
: あなたのために私に何かできることはありますか。

2. リスト内包表記

リストの各要素に同じ処理を適用するというのは、頻繁にあることです。 頻繁にある単純な処理なのに3行もかけて書くのは、面倒です。

bucket = ['apple', 'orange', 'grape']

# 3行も必要...
lst = []
for fruit in bucket:
    lst.append('**' + fruit + '**' )

2.1. 書き方

そこでリスト内包表記という書き方があります。

[取り出したオブジェクトに対する処理 for 取り出したオブジェクト in オブジェクトの集まり]
bucket = ['apple', 'orange', 'grape']
lst = ['**' + fruit + '**' for fruit in bucket]

2.2. 考え方

for 文は繰り返す iterate。 ですがリスト内包表記は、既に順番に並んでいる要素に対して関数を適用 apply するって考えると受け入れやすいかなという気がします。個人の感想です。

bucket = ['apple', 'orange', 'grape']
lst = ['**' + fruit + '**' for fruit in bucket]

リストの各要素 に対して 処理を適用する。
Apply function f for each fruit in a bucket.

for
【前】
1. ~のために、~に対して
・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)]

3. range って、なに?

答え: range オブジェクトは、整数の集まりです。

f:id:domodomodomo:20180805143805j:plain f:id:domodomodomo:20180805143809j:plain f:id:domodomodomo:20180805143819j:plain f:id:domodomodomo:20180805143826j:plain f:id:domodomodomo:20180805143833j:plain f:id:domodomodomo:20180805030512j:plain f:id:domodomodomo:20180805030535j:plain


"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 標準ライブラリ


range オブジェクトは、整数の集まりと認識しておいた方が for 文の理解が進むかなと思います。

この次のページ「イテレータってなに?」で、実際に Python の for 文が1つずつオブジェクトを取り出すように設計されていることを、見ていただきたいと考えています。

◯ まとめ

range オブジェクトは、整数の集まりです。

4. なんで range は 0 から始まるの?

答え: list の添え字が 0 からはじまるから.. だと思います。

# スッキリ書ける
lst = [0, 1, 2]
for index in range(len(lst)):
    lst[index] = None

4.1. じゃあ、なんで list は 0 から始まるの?

答え: C 言語の配列を踏襲しているから.. だと思います。

(読み飛ばしてください)
何気ない疑問のようで結構重たい内容です。

C 言語には「配列」があります。Python の「リスト」と、よく似た機能です。 C 言語の配列の添え字は 0 からはじまります。

なぜ、そうするかについては、自分もまだ読んでいませんが、以下の記事が参考になるかなと思います。 掴みの部分だけ簡単に訳しました。

なぜ C 言語では 0 から添え字が始まるのですか?
Why does the indexing start with zero in 'C'?

C 言語では、配列 の名前は本質的には ポインタ です。 ポインタは、アドレス への参照以外の何者でもありません。 つまり array[n] という式は、アドレスを指しています。 これは最初の要素から n 番目の要素という意味です。
In C, the name of any array essentially is a pointer, which is nothing but a reference to a memory location, and so the expression array[n] points to a memory location which is n-elements away from the first element.

このことは添え字は、先頭のアドレスからの差分として使われていることを意味しています。 最初の配列の要素は、まさに配列が指し示しているアドレス(0個の要素分だけ離れている場所)に格納されています。 つまり最初の要素は array[0] として参照されなければなりません。
This means that the index is used as an offset. The first element of the array is exactly contained in the memory location that array points to (0 elements away), so it should always be referred as array[0].

さらなる詳細はこちらを参照してください:
Why The Array Index Should Start From 0
for more info:
Why The Array Index Should Start From 0

5. for 文と while 文の違いって、なに?

5.1. for 文 ... 要素をとひとつひとつ取り出して、処理を繰り返す。

5.2. while 文 ... 要素がある限り、処理を繰り返す


while 文は、単純に "条件が True である限り、処理を繰り返す" というよりも、 オブジェクトが "ある" 限り処理を繰り返すということをしています。

これについては if 文ってなに?のなかで説明させていただいています。
Python の if 文ってなに? - いっきに Python に詳しくなるサイト

6. よくやってしまいがちな失敗

6.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 の代入とは何かについては、こちらで説明させていただきました。
Python の代入ってなに?

6.2. for 文の中でリストの要素を削除できないのは、なんで?

答え: 順番が崩れてしまうから

6.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
6.2.2. 原因

実は for 文でこう書いたとき

lst = [0, 1, 2, 3, 4, 5, 6 ,7 ,8 ,9]
for e in lst:
    #
    print(e)


while 文で書き直すとこういう動作をしています。 実際に、このように動作していることを、このあと「イテレータってなに?」 で、説明させていただきます。

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)
6.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
6.2.4. 解決策

Python でリストから条件にあった要素を削除したい場合は、リスト内包表記を使うのが便利です。
Python でリストを条件をつけて複数の要素を削除したい。

7. 糖衣構文

7.1. for 文は while 文の糖衣構文です。

6 章で見ていただきましたが、for 文は while 文で書き換えることができます。 極論すれば for 文は while 文をわかりやすく書き換えたものです。 このようにわかりやすくかく書き方を 糖衣構文 と言います。

# 簡単
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)

7.2. リスト内包表記は 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 スタイルガイド
シンプルなケースの場合のみ利用する。

7.3. 三項演算子は 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

7.4. まとめ

糖衣構文は、扱える範囲が単純なものに限られます。 もし複雑になりそうなら、ためらわず低レベルな書き方をします。

8. for 文で使えるクラスは、なに?
オブジェクトの集まりって、なに?

for 文の in の中に代入して繰り返し (iterate) 処理ができるオブジェクトを iterable と言います。 例えば list, set, tuple, dict などが当たります。

では、どのようなオブジェクトであれば iterable と言えるのでしょうか? それについて、すこしずつ見ていきたいと思います。





9. まとめ

f:id:domodomodomo:20180805031152j:plain f:id:domodomodomo:20180805031205j:plain f:id:domodomodomo:20180805031216j:plain f:id:domodomodomo:20180805031436j:plain f:id:domodomodomo:20180805031258j:plain f:id:domodomodomo:20180805031307j:plain f:id:domodomodomo:20180805031316j:plain

f:id:domodomodomo:20181103191030j:plain f:id:domodomodomo:20181103191049j:plain f:id:domodomodomo:20181103191057j:plain f:id:domodomodomo:20181103191104j:plain





for 文を理解する5講座