Pythonの「for」ループ(確定反復)

Pythonの「for」ループ(確定反復)

このチュートリアルでは、Pythonのforループを使用してdefinite iterationを実行する方法を示します。

この入門シリーズのprevious tutorialで、次のことを学びました。

  • 同じコードブロックを何度も繰り返し実行することをiterationと呼びます。

  • 反復には2つのタイプがあります。

    • Definite反復。反復回数は、事前に明示的に指定されます。

    • Indefiniteの反復。この反復では、何らかの条件が満たされるまでコードブロックが実行されます。

  • Pythonでは、whileループを使用して無期限の反復が実行されます。

このチュートリアルで取り上げる内容は次のとおりです。

  • プログラミング言語で使用され、明確な反復を実装するいくつかの異なるパラダイムの比較から始めます。

  • 次に、iterablesiteratorsについて学習します。これらは、Pythonでの明確な反復の基礎を形成する2つの概念です。

  • 最後に、すべてを結び付けて、Pythonのforループについて学習します。

Free Bonus:Click here to get access to a chapter from Python Tricks: The Bookは、Pythonのベストプラクティスと、より美しい+ Pythonicコードをすぐに適用できる簡単な例を示しています。

プログラミングにおける確定反復の調査

明確な反復ループは、forがPythonを含むほぼすべてのプログラミング言語で導入するために使用されるキーワードであるため、しばしばforループと呼ばれます。

歴史的に、プログラミング言語は、forループのいくつかのさまざまなフレーバーを提供してきました。 これらについては、次のセクションで簡単に説明します。

数値範囲ループ

最も基本的なforループは、開始値と終了値を持つ単純な数値範囲ステートメントです。 正確な形式は言語によって異なりますが、通常は次のようになります。

for i = 1 to 10
    

ここでは、ループの本体が10回実行されます。 変数iは、最初の反復で値1、2番目の反復で値2、というように仮定します。 この種のforループは、BASIC、Algol、およびPascalの言語で使用されます。

3つの式のループ

Cプログラミング言語で普及しているforループの別の形式には、次の3つの部分があります。

  • 初期化

  • 終了条件を指定する式

  • 各反復の終わりに実行されるアクション。

このタイプの形式は次のとおりです。

for (i = 1; i <= 10; i++)
    

Technical Note: Cプログラミング言語では、i++は変数iをインクリメントします。 Pythonのi += 1とほぼ同等です。

このループは次のように解釈されます。

  • i1に初期化します。

  • i <= 10までループを続けます。

  • 各ループの反復後にi1ずつインクリメントします。

3つの部分に指定された式はほぼすべてであるため、3つの式のforループが一般的です。したがって、これは、上記の単純な数値範囲形式よりもかなり柔軟性があります。 これらのforループは、C ++、Java、PHP、およびPerl言語でも機能します。

コレクションベースまたはイテレータベースのループ

このタイプのループは、数値や条件を指定するのではなく、オブジェクトのコレクションを反復処理します。

for i in 
    

ループを通過するたびに、変数i<collection>の次のオブジェクトの値を取ります。 このタイプのforループは、間違いなく最も一般的で抽象的なものです。 PerlとPHPもこのタイプのループをサポートしていますが、forではなくキーワードforeachによって導入されています。

Further Reading:プログラミング言語間での明確な反復の実装の詳細については、For loopWikipediaページを参照してください。

Pythonforループ

上記のループタイプのうち、Pythonが実装するのは最後のコレクションベースの反復のみです。 一見したところ、それは生の取引のように思えるかもしれませんが、Pythonの明確な反復の実装は非常に用途が広いため、だまされてしまうことはありません。

間もなく、Pythonのforループの内臓を詳しく掘り下げます。 しかし今は、簡単なプロトタイプと例から始めましょう。

Pythonのforループは次のようになります。

for  in :
    

<iterable>は、リストやタプルなどのオブジェクトのコレクションです。 ループ本体の<statement(s)>は、すべてのPython制御構造と同様にインデントで示され、<iterable>の項目ごとに1回実行されます。 ループ変数<var>は、ループを通過するたびに<iterable>の次の要素の値を取ります。

これが代表的な例です。

>>>

>>> a = ['foo', 'bar', 'baz']
>>> for i in a:
...     print(i)
...
foo
bar
baz

この例では、<iterable>はリストaであり、<var>は変数iです。 ループを通過するたびに、iaの連続する項目を引き受けるため、print()は値'foo''bar'、および'baz'を表示します。 、それぞれ。 このようなforループは、反復可能でアイテムを処理するPythonの方法です。

しかし、反復可能なものとは正確には何ですか? forループをさらに調べる前に、Pythonのイテラブルが何であるかをより深く掘り下げることが有益です。

イテラブル

Pythonでは、iterableは、オブジェクトを反復で使用できることを意味します。 この用語は次のように使用されます。

  • An adjective:オブジェクトは反復可能として記述される場合があります。

  • A noun:オブジェクトは反復可能として特徴付けられる場合があります。

オブジェクトが反復可能である場合、それを組み込みのPython関数iter()に渡すことができます。この関数は、iteratorと呼ばれるものを返します。 はい、用語は少し繰り返されます。 そこにたむろしてください。 最終的にはすべてうまくいきます。

次の例の各オブジェクトは反復可能であり、iter()に渡されると何らかのタイプの反復子を返します。

>>>

>>> iter('foobar')                             # String


>>> iter(['foo', 'bar', 'baz'])                # List


>>> iter(('foo', 'bar', 'baz'))                # Tuple


>>> iter({'foo', 'bar', 'baz'})                # Set


>>> iter({'foo': 1, 'bar': 2, 'baz': 3})       # Dict

一方、これらのオブジェクトタイプは反復不可能です。

>>>

>>> iter(42)                                   # Integer
Traceback (most recent call last):
  File "", line 1, in 
    iter(42)
TypeError: 'int' object is not iterable

>>> iter(3.1)                                  # Float
Traceback (most recent call last):
  File "", line 1, in 
    iter(3.1)
TypeError: 'float' object is not iterable

>>> iter(len)                                  # Built-in function
Traceback (most recent call last):
  File "", line 1, in 
    iter(len)
TypeError: 'builtin_function_or_method' object is not iterable

これまでに遭遇したコレクション型またはコンテナ型のすべてのデータ型は反復可能です。 これらには、stringlisttupledictset、およびfrozensetタイプが含まれます。

しかし、これらは反復できる唯一のタイプではありません。 Pythonに組み込まれている、またはモジュールで定義されている多くのオブジェクトは、反復可能になるように設計されています。 たとえば、Pythonで開いているファイルは反復可能です。 ファイルI / Oのチュートリアルですぐにわかるように、開いているファイルオブジェクトを反復処理すると、ファイルからデータが読み取られます。

実際、Pythonのほとんどすべてのオブジェクトを反復可能にすることができます。 ユーザー定義のオブジェクトでさえ、繰り返し処理できるように設計できます。 (これがどのように行われるかは、今後のオブジェクト指向プログラミングに関する記事で確認できます。)

イテレータ

これで、オブジェクトが反復可能であるとはどういう意味かがわかり、iter()を使用してオブジェクトから反復子を取得する方法がわかりました。 イテレータを取得したら、それで何ができますか?

反復子は、本質的に、関連付けられた反復可能なオブジェクトから連続した値を生成する値プロデューサーです。 組み込み関数next()は、イテレータから次の値を取得するために使用されます。

上記と同じリストを使用した例を次に示します。

>>>

>>> a = ['foo', 'bar', 'baz']

>>> itr = iter(a)
>>> itr


>>> next(itr)
'foo'
>>> next(itr)
'bar'
>>> next(itr)
'baz'

この例では、aは反復可能なリストであり、itrは、iter()で取得された関連する反復子です。 各next(itr)呼び出しは、itrから次の値を取得します。

イテレータが内部的に状態を保持する方法に注目してください。 どの値がすでに取得されているかを知っているので、next()を呼び出すと、次に返す値がわかります。

イテレータの値がなくなるとどうなりますか? 上記のイテレータでもう1つnext()呼び出しを行いましょう。

>>>

>>> next(itr)
Traceback (most recent call last):
  File "", line 1, in 
    next(itr)
StopIteration

イテレータからのすべての値がすでに返されている場合、後続のnext()呼び出しでStopIteration例外が発生します。 イテレータから値を取得しようとすると失敗します。

イテレータから値を取得できるのは一方向のみです。 後戻りできません。 prev()関数はありません。 ただし、同じ反復可能オブジェクトで2つの独立した反復子を定義できます。

>>>

>>> a
['foo', 'bar', 'baz']

>>> itr1 = iter(a)
>>> itr2 = iter(a)

>>> next(itr1)
'foo'
>>> next(itr1)
'bar'
>>> next(itr1)
'baz'

>>> next(itr2)
'foo'

イテレータitr1がすでにリストの最後にある場合でも、itr2はまだ最初にあります。 各イテレーターは、他のイテレーターから独立した独自の内部状態を維持します。

イテレータからすべての値を一度に取得する場合は、組み込みのlist()関数を使用できます。 他の可能な使用法の中で、list()はイテレータを引数として取り、イテレータが生成したすべての値で構成されるリストを返します。

>>>

>>> a = ['foo', 'bar', 'baz']
>>> itr = iter(a)
>>> list(itr)
['foo', 'bar', 'baz']

同様に、組み込みのtuple()関数とset()関数は、イテレータが生成するすべての値から、それぞれタプルとセットを返します。

>>>

>>> a = ['foo', 'bar', 'baz']

>>> itr = iter(a)
>>> tuple(itr)
('foo', 'bar', 'baz')

>>> itr = iter(a)
>>> set(itr)
{'baz', 'foo', 'bar'}

これを習慣にすることは必ずしも推奨されません。 イテレーターの優雅さの一部は、イテレーターが「遅延」していることです。つまり、イテレータを作成するときに、その時点で生成できるすべてのアイテムが生成されるわけではありません。 next()で要求するまで待機します。 アイテムは、要求されるまで作成されません。

list()tuple()などを使用する場合、イテレータにすべての値を一度に生成させるため、すべての値を返すことができます。 イテレータが返すオブジェクトの総数が非常に大きい場合、時間がかかる場合があります。

実際、Pythonで無限のオブジェクトのシリーズを返すイテレータを作成することは可能です。 (これを行う方法は、ジェネレーター関数とitertoolsに関する今後のチュートリアルで学習します。)エンドレスイテレーターからすべての値を一度に取得しようとすると、プログラムはhangになります。

Pythonforループの根性

これで、Pythonのforループがどのように機能するかを完全に理解するために必要なすべての概念が紹介されました。 先に進む前に、関連する用語を確認しましょう。

Term 意味

繰り返し

コレクション内のオブジェクトまたはアイテムをループするプロセス

反復可能

反復できるオブジェクト(またはオブジェクトを説明するために使用される形容詞)

イテレータ

関連する反復可能オブジェクトから連続するアイテムまたは値を生成するオブジェクト

iter()

イテレータからイテレータを取得するために使用される組み込み関数

ここで、このチュートリアルの最初に示した単純なforループについてもう一度考えてみましょう。

>>>

>>> a = ['foo', 'bar', 'baz']
>>> for i in a:
...     print(i)
...
foo
bar
baz

このループは、今学んだ概念の観点から完全に説明できます。 このforループが記述する反復を実行するために、Pythonは次のことを行います。

  • iter()を呼び出して、aのイテレータを取得します

  • next()を繰り返し呼び出して、イテレータから各アイテムを順番に取得します

  • next()StopIteration例外を発生させたときに、ループを終了します

ループ本体は、アイテムnext()が返されるたびに1回実行され、ループ変数iは反復ごとに指定されたアイテムに設定されます。

この一連のイベントを次の図にまとめます。

おそらくこれは多くの不必要なモンキービジネスのように思えますが、そのメリットは相当なものです。 Pythonは、すべての反復可能要素に対するループをこの方法で処理します。Pythonでは、反復可能要素と反復子が多数あります。

  • 多くの組み込みオブジェクトおよびライブラリオブジェクトは反復可能です。

  • 反復可能オブジェクトを返す多くの関数を含むitertoolsと呼ばれる標準ライブラリモジュールがあります。

  • Pythonのオブジェクト指向機能で作成されたユーザー定義のオブジェクトは、反復可能にすることができます。

  • Pythonには、ジェネレータと呼ばれる構造があり、これを使用すると、単純で簡単な方法で独自のイテレータを作成できます。

このシリーズ全体を通して、上記のすべてについて詳しく知ることができます。 それらはすべてforループのターゲットになる可能性があり、構文は全体的に同じです。 シンプルでエレガントで非常に多用途です。

辞書を反復処理する

イテレータはiter()のディクショナリから取得できることを以前に見たので、ディクショナリはイテレータでなければならないことがわかります。 辞書をループするとどうなりますか? どれどれ:

>>>

>>> d = {'foo': 1, 'bar': 2, 'baz': 3}
>>> for k in d:
...     print(k)
...
foo
bar
baz

ご覧のとおり、forループがディクショナリを反復処理すると、ループ変数がディクショナリのキーに割り当てられます。

ループ内で辞書の値にアクセスするには、通常どおりキーを使用して辞書参照を作成できます。

>>>

>>> for k in d:
...     print(d[k])
...
1
2
3

.values()を使用して、辞書の値を直接反復することもできます。

>>>

>>> for v in d.values():
...     print(v)
...
1
2
3

実際、辞書のキーと値の両方を同時に反復処理できます。 これは、forループのループ変数が単一の変数だけに限定されていないためです。 また、タプルにすることもできます。この場合、割り当てステートメントの場合と同様に、パッキングとアンパックを使用してiterableのアイテムから割り当てが行われます。

>>>

>>> i, j = (1, 2)
>>> print(i, j)
1 2

>>> for i, j in [(1, 2), (3, 4), (5, 6)]:
...     print(i, j)
...
1 2
3 4
5 6

Pythondictionariesのチュートリアルで説明したように、ディクショナリメソッド.items()は、キーと値のペアのリストをタプルとして効果的に返します。

>>>

>>> d = {'foo': 1, 'bar': 2, 'baz': 3}

>>> d.items()
dict_items([('foo', 1), ('bar', 2), ('baz', 3)])

したがって、キーと値の両方にアクセスする辞書を反復処理するPythonの方法は次のようになります。

>>>

>>> d = {'foo': 1, 'bar': 2, 'baz': 3}
>>> for k, v in d.items():
...     print('k =', k, ', v =', v)
...
k = foo , v = 1
k = bar , v = 2
k = baz , v = 3

range()関数

このチュートリアルの最初のセクションでは、numeric range loopと呼ばれるタイプのforループを確認しました。このループでは、開始数値と終了数値が指定されています。 この形式のforループはPythonに直接組み込まれていませんが、簡単にアクセスできます。

たとえば、0から4までの値を反復処理する場合は、次のようにするだけです。

>>>

>>> for n in (0, 1, 2, 3, 4):
...     print(n)
...
0
1
2
3
4

このソリューションは、数が少ない場合でもそれほど悪くありません。 しかし、番号の範囲がはるかに大きい場合、すぐに退屈になります。

幸いなことに、Pythonには、より優れたオプションである組み込みのrange()関数が用意されています。この関数は、整数のシーケンスを生成する反復可能オブジェクトを返します。

range(<end>)は、0で始まり、<end>までの整数を生成するイテラブルを返します。

>>>

>>> x = range(5)
>>> x
range(0, 5)
>>> type(x)

range()は、値のリストまたはタプルではなく、クラスrangeのオブジェクトを返すことに注意してください。 rangeオブジェクトは反復可能であるため、forループを使用して値を反復処理することにより、値を取得できます。

>>>

>>> for n in x:
...     print(n)
...
0
1
2
3
4

list()またはtuple()を使用して、すべての値を一度に取得することもできます。 REPLセッションでは、値が何であるかをすばやく表示する便利な方法になります。

>>>

>>> list(x)
[0, 1, 2, 3, 4]

>>> tuple(x)
(0, 1, 2, 3, 4)

ただし、大規模なアプリケーションの一部であるコードでrange()を使用する場合、通常、この方法でlist()またはtuple()を使用することは不適切な方法と見なされます。 イテレータと同様に、rangeオブジェクトはレイジーです。指定された範囲の値は、要求されるまで生成されません。 rangeオブジェクトでlist()またはtuple()を使用すると、すべての値が一度に返されます。 これが必要になることはめったにありません。リストが長い場合、時間とメモリを浪費する可能性があります。

range(<begin>, <end>, <stride>)は、<begin>で始まり、<end>までの整数を生成するiterableを返します。 指定した場合、<stride>は、値の間でスキップする量を示します(文字列とリストのスライスに使用されるストライド値に類似しています)。

>>>

>>> list(range(5, 20, 3))
[5, 8, 11, 14, 17]

<stride>を省略すると、デフォルトで1になります。

>>>

>>> list(range(5, 10, 1))
[5, 6, 7, 8, 9]
>>> list(range(5, 10))
[5, 6, 7, 8, 9]

range()に指定するすべてのパラメーターは整数である必要がありますが、いずれも負の値にすることができます。 当然、<begin><end>より大きい場合、<stride>は負である必要があります(結果が必要な場合)。

>>>

>>> list(range(-5, 5))
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4]

>>> list(range(5, -5))
[]
>>> list(range(5, -5, -1))
[5, 4, 3, 2, 1, 0, -1, -2, -3, -4]

Technical Note:厳密に言えば、range()は厳密には組み込み関数ではありません。 不変のシーケンス型を作成する呼び出し可能なクラスとして実装されます。 しかし、実際的な目的では、組み込み関数のように動作します。

range()の詳細については、Real Pythonの記事Python’s range() Function (Guide)を参照してください。

forのループ動作の変更

この入門シリーズの前のチュートリアルで、whileループの実行をbreak and continue statementsで中断し、else clauseで変更する方法を説明しました。 これらの機能は、forループでも使用できます。

breakおよびcontinueステートメント

breakおよびcontinueは、whileループと同じようにforループで機能します。 breakはループを完全に終了し、ループに続く最初のステートメントに進みます。

>>>

>>> for i in ['foo', 'bar', 'baz', 'qux']:
...     if 'b' in i:
...         break
...     print(i)
...
foo

continueは現在の反復を終了し、次の反復に進みます。

>>>

>>> for i in ['foo', 'bar', 'baz', 'qux']:
...     if 'b' in i:
...         continue
...     print(i)
...
foo
qux

else

forループには、else句を含めることもできます。 解釈は、whileループの解釈に類似しています。 else句は、反復可能オブジェクトの枯渇によってループが終了した場合に実行されます。

>>>

>>> for i in ['foo', 'bar', 'baz', 'qux']:
...     print(i)
... else:
...     print('Done.')  # Will execute
...
foo
bar
baz
qux
Done.

リストがbreakステートメントで分割されている場合、else句は実行されません。

>>>

>>> for i in ['foo', 'bar', 'baz', 'qux']:
...     if i == 'bar':
...         break
...     print(i)
... else:
...     print('Done.')  # Will not execute
...
foo

結論

このチュートリアルでは、Pythonのdefinite iterationの主力製品であるforループを紹介しました。

また、iterablesiteratorsの内部動作についても学びました。これは、明確な反復の根底にある2つの重要なオブジェクトタイプですが、他のさまざまなPythonコードでも顕著に表れています。

この入門シリーズの次の2つのチュートリアルでは、ギアを少しシフトし、Pythonプログラムがキーボードからinputおよびコンソールへのoutputを介してユーザーと対話する方法を探ります。