ホームページ バックエンド開発 Python チュートリアル Python でのイテレータとジェネレータの高度な使用法

Python でのイテレータとジェネレータの高度な使用法

Mar 01, 2017 pm 02:09 PM
python ビルダー イテレーター

イテレータ

イテレータは、反復プロトコルに準拠するオブジェクトです。基本的には、呼び出されるとシーケンス内の次の項目を返す next メソッドがあることを意味します。返す項目がない場合は、StopIteration 例外を発生させます。

反復可能なオブジェクトでは 1 つのループが許可されます。単一の反復の状態 (位置) が保持されます。別の観点から見ると、シーケンスがループされるたびに反復オブジェクトが必要になります。これは、同じシーケンスを複数回反復できることを意味します。反復ロジックをシーケンスから分離すると、反復する方法がさらに増えます。

コンテナの __iter__ メソッドを呼び出してイテレータ オブジェクトを作成するのが、イテレータをマスターする最も直接的な方法です。 iter 関数を使用すると、キーストロークがいくつか節約されます。

>>> nums = [1,2,3]   # note that ... varies: these are different objects
>>> iter(nums)              
<listiterator object at ...>
>>> nums.__iter__()           
<listiterator object at ...>
>>> nums.__reversed__()         
<listreverseiterator object at ...>

>>> it = iter(nums)
>>> next(it)      # next(obj) simply calls obj.next()
1
>>> it.next()
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
StopIteration
ログイン後にコピー

ループ内で使用すると、StopIteration が受け入れられ、ループが停止します。しかし、明示的な呼び出しでは、イテレータ要素が使い果たされると、それにアクセスすると例外がスローされることがわかります。

for...in ループを使用し、__iter__ メソッドも使用します。これにより、透過的にシーケンスの反復を開始できるようになります。しかし、すでに反復子がある場合は、それらを for ループ内で同様に使用できるようにしたいと考えます。これを実現するために、イテレータには next に加えて、イテレータ自体 (self) を返すメソッド __iter__ も用意されています。

Python のイテレーターのサポートは広く普及しています。標準ライブラリ内のすべてのシーケンスおよび順序なしコンテナーがサポートされています。この概念は他のものにも拡張されています。たとえば、ファイル オブジェクトは行の反復をサポートします。

>>> f = open(&#39;/etc/fstab&#39;)
>>> f is f.__iter__()
True
ログイン後にコピー

ファイル自体は反復子であり、その __iter__ メソッドは別個のオブジェクトを作成しません。シングルスレッドの順次読み取りのみが許可されます。

式の生成
反復可能オブジェクトを作成する 2 番目の方法は、リスト理解の基礎であるジェネレーター式を使用することです。わかりやすくするために、生成された式は常に括弧または式で囲まれます。括弧を使用すると、ジェネレーター反復子が作成されます。角括弧の場合、このプロセスは「短絡」され、リストが取得されます。

>>> (i for i in nums)          
<generator object <genexpr> at 0x...>
>>> [i for i in nums]
[1, 2, 3]
>>> list(i for i in nums)
[1, 2, 3]
ログイン後にコピー

Python 2.7 および 3.x では、リスト式の構文が辞書式とセット式に拡張されました。生成された式が中かっこで囲まれると、セットが作成されます。辞書辞書は、式に key:value の形式のキーと値のペアが含まれている場合に作成されます:

>>> {i for i in range(3)}  
set([0, 1, 2])
>>> {i:i**2 for i in range(3)}  
{0: 0, 1: 1, 2: 4}
ログイン後にコピー

不幸にして古いバージョンの Python に囚われてしまった場合、この構文は少し悪いです:

>>> set(i for i in &#39;abc&#39;)
set([&#39;a&#39;, &#39;c&#39;, &#39;b&#39;])
>>> dict((i, ord(i)) for i in &#39;abc&#39;)
{&#39;a&#39;: 97, &#39;c&#39;: 99, &#39;b&#39;: 98}
ログイン後にコピー

生成 この式は非常に単純なので、これ以上の説明は必要ありません。言及する価値のある落とし穴が 1 つだけあります。それは、Python のバージョン 3 未満ではインデックス変数 (i) がリークすることです。

ジェネレーター

ジェネレーターは、単一の値ではなく結果のリストを生成する関数です。

反復可能オブジェクトを作成する 3 番目の方法は、ジェネレーター関数を呼び出すことです。ジェネレーターは、yield キーワードを含む関数です。このキーワードが存在するだけで関数の性質が完全に変わることは注目に値します。yield ステートメントを呼び出す必要はなく、アクセスする必要さえありません。ただし、関数をジェネレーターにしてみましょう。関数が呼び出されると、その関数内の命令が実行されます。そして、ジェネレーターが呼び出されると、その中の最初の命令の前で実行が停止します。ジェネレーターを呼び出すと、反復プロトコルに関連付けられたジェネレーター オブジェクトが作成されます。通常の関数と同様に、同時呼び出しと再帰呼び出しが許可されます。
next が呼び出されると、関数は最初の yield まで実行されます。 yield ステートメントが出現するたびに、next として返される値が取得され、yield ステートメントが実行された後、関数の実行が停止されます。

>>> def f():
...  yield 1
...  yield 2
>>> f()                  
<generator object f at 0x...>
>>> gen = f()
>>> gen.next()
1
>>> gen.next()
2
>>> gen.next()
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
StopIteration
ログイン後にコピー

単一のジェネレーター関数呼び出しの過程全体を見てみましょう。

>>> def f():
...  print("-- start --")
...  yield 3
...  print("-- middle --")
...  yield 4
...  print("-- finished --")
>>> gen = f()
>>> next(gen)
-- start --
3
>>> next(gen)
-- middle --
4
>>> next(gen)              
-- finished --
Traceback (most recent call last):
 ...
StopIteration
ログイン後にコピー

通常の関数で f() を実行して即座に print を実行するのに比べ、関数本体内のステートメントを実行せずに gen が代入されます。次に gen.next() が呼び出されたときのみ、最初の yield 部分までのステートメントが実行されます。 2 番目のステートメントは -- middle -- を出力し、2 番目の yield に達すると実行を停止します。 3 番目の次の print--finished-- そして関数の最後まで、yield がないため、例外がスローされます。

関数 yield の後に制御が呼び出し元に戻るとどうなりますか?各ジェネレーターの状態はジェネレーター オブジェクトに保存されます。この時点から、ジェネレーター関数は別のスレッドで実行されているように見えますが、これは単なる錯覚です。実行は厳密にシングルスレッドですが、インタープリターは次の値リクエストの間の状態を保持し、保存します。

ジェネレーターはなぜ便利ですか?イテレータのセクションで強調したように、ジェネレータ関数は反復可能なオブジェクトを作成する別の方法にすぎません。 yield ステートメントで完了できるものはすべて、次のメソッドでも完了できます。ただし、関数を使用してインタプリタが魔法のようにイテレータを作成できるようにすることには利点があります。関数は、next メソッドと __iter__ メソッドを必要とするクラス定義よりもはるかに短くすることができます。さらに重要なことは、ジェネレーターの作成者は、連続する次の呼び出しの間に反復子オブジェクトのインスタンス プロパティを渡す必要があるよりも、ローカル変数にローカライズされたステートメントを簡単に理解できることです。

还有问题是为何迭代器有用?当一个迭代器用来驱动循环,循环变得简单。迭代器代码初始化状态,决定是否循环结束,并且找到下一个被提取到不同地方的值。这凸显了循环体——最值得关注的部分。除此之外,可以在其它地方重用迭代器代码。

双向通信
每个yield语句将一个值传递给调用者。这就是为何PEP 255引入生成器(在Python2.2中实现)。但是相反方向的通信也很有用。一个明显的方式是一些外部(extern)语句,或者全局变量或共享可变对象。通过将先前无聊的yield语句变成表达式,直接通信因PEP 342成为现实(在2.5中实现)。当生成器在yield语句之后恢复执行时,调用者可以对生成器对象调用一个方法,或者传递一个值 给 生成器,然后通过yield语句返回,或者通过一个不同的方法向生成器注入异常。

第一个新方法是send(value),类似于next(),但是将value传递进作为yield表达式值的生成器中。事实上,g.next()和g.send(None)是等效的。

第二个新方法是throw(type, value=None, traceback=None),等效于在yield语句处

raise type, value, traceback
ログイン後にコピー

不像raise(从执行点立即引发异常),throw()首先恢复生成器,然后仅仅引发异常。选用单次throw就是因为它意味着把异常放到其它位置,并且在其它语言中与异常有关。

当生成器中的异常被引发时发生什么?它可以或者显式引发,当执行某些语句时可以通过throw()方法注入到yield语句中。任一情况中,异常都以标准方式传播:它可以被except和finally捕获,或者造成生成器的中止并传递给调用者。

因完整性缘故,值得提及生成器迭代器也有close()方法,该方法被用来让本可以提供更多值的生成器立即中止。它用生成器的__del__方法销毁保留生成器状态的对象。

让我们定义一个只打印出通过send和throw方法所传递东西的生成器。

>>> import itertools
>>> def g():
...   print &#39;--start--&#39;
...   for i in itertools.count():
...     print &#39;--yielding %i--&#39; % i
...     try:
...       ans = yield i
...     except GeneratorExit:
...       print &#39;--closing--&#39;
...       raise
...     except Exception as e:
...       print &#39;--yield raised %r--&#39; % e
...     else:
...       print &#39;--yield returned %s--&#39; % ans

>>> it = g()
>>> next(it)
--start--
--yielding 0--
0
>>> it.send(11)
--yield returned 11--
--yielding 1--
1
>>> it.throw(IndexError)
--yield raised IndexError()--
--yielding 2--
2
>>> it.close()
--closing--
ログイン後にコピー

注意: next还是__next__?

在Python 2.x中,接受下一个值的迭代器方法是next,它通过全局函数next显式调用,意即它应该调用__next__。就像全局函数iter调用__iter__。这种不一致在Python 3.x中被修复,it.next变成了it.__next__。对于其它生成器方法——send和throw情况更加复杂,因为它们不被解释器隐式调用。然而,有建议语法扩展让continue带一个将被传递给循环迭代器中send的参数。如果这个扩展被接受,可能gen.send会变成gen.__send__。最后一个生成器方法close显然被不正确的命名了,因为它已经被隐式调用。

链式生成器
注意: 这是PEP 380的预览(还未被实现,但已经被Python3.3接受)

比如说我们正写一个生成器,我们想要yield一个第二个生成器——一个子生成器(subgenerator)——生成的数。如果仅考虑产生(yield)的值,通过循环可以不费力的完成:

subgen = some_other_generator()
for v in subgen:
  yield v
ログイン後にコピー

然而,如果子生成器需要调用send()、throw()和close()和调用者适当交互的情况下,事情就复杂了。yield语句不得不通过类似于前一章节部分定义的try...except...finally结构来保证“调试”生成器函数。这种代码在PEP 380中提供,现在足够拿出将在Python 3.3中引入的新语法了:

yield from some_other_generator()
ログイン後にコピー

像上面的显式循环调用一样,重复从some_other_generator中产生值直到没有值可以产生,但是仍然向子生成器转发send、throw和close。

更多Python中的迭代器与生成器高级用法相关文章请关注PHP中文网!

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

PHPおよびPython:さまざまなパラダイムが説明されています PHPおよびPython:さまざまなパラダイムが説明されています Apr 18, 2025 am 12:26 AM

PHPは主に手順プログラミングですが、オブジェクト指向プログラミング(OOP)もサポートしています。 Pythonは、OOP、機能、手続き上のプログラミングなど、さまざまなパラダイムをサポートしています。 PHPはWeb開発に適しており、Pythonはデータ分析や機械学習などのさまざまなアプリケーションに適しています。

PHPとPythonの選択:ガイド PHPとPythonの選択:ガイド Apr 18, 2025 am 12:24 AM

PHPはWeb開発と迅速なプロトタイピングに適しており、Pythonはデータサイエンスと機械学習に適しています。 1.PHPは、単純な構文と迅速な開発に適した動的なWeb開発に使用されます。 2。Pythonには簡潔な構文があり、複数のフィールドに適しており、強力なライブラリエコシステムがあります。

Python vs. JavaScript:学習曲線と使いやすさ Python vs. JavaScript:学習曲線と使いやすさ Apr 16, 2025 am 12:12 AM

Pythonは、スムーズな学習曲線と簡潔な構文を備えた初心者により適しています。 JavaScriptは、急な学習曲線と柔軟な構文を備えたフロントエンド開発に適しています。 1。Python構文は直感的で、データサイエンスやバックエンド開発に適しています。 2。JavaScriptは柔軟で、フロントエンドおよびサーバー側のプログラミングで広く使用されています。

PHPとPython:彼らの歴史を深く掘り下げます PHPとPython:彼らの歴史を深く掘り下げます Apr 18, 2025 am 12:25 AM

PHPは1994年に発信され、Rasmuslerdorfによって開発されました。もともとはウェブサイトの訪問者を追跡するために使用され、サーバー側のスクリプト言語に徐々に進化し、Web開発で広く使用されていました。 Pythonは、1980年代後半にGuidovan Rossumによって開発され、1991年に最初にリリースされました。コードの読みやすさとシンプルさを強調し、科学的コンピューティング、データ分析、その他の分野に適しています。

Windows 8でコードを実行できます Windows 8でコードを実行できます Apr 15, 2025 pm 07:24 PM

VSコードはWindows 8で実行できますが、エクスペリエンスは大きくない場合があります。まず、システムが最新のパッチに更新されていることを確認してから、システムアーキテクチャに一致するVSコードインストールパッケージをダウンロードして、プロンプトとしてインストールします。インストール後、一部の拡張機能はWindows 8と互換性があり、代替拡張機能を探すか、仮想マシンで新しいWindowsシステムを使用する必要があることに注意してください。必要な拡張機能をインストールして、適切に動作するかどうかを確認します。 Windows 8ではVSコードは実行可能ですが、開発エクスペリエンスとセキュリティを向上させるために、新しいWindowsシステムにアップグレードすることをお勧めします。

Visual StudioコードはPythonで使用できますか Visual StudioコードはPythonで使用できますか Apr 15, 2025 pm 08:18 PM

VSコードはPythonの書き込みに使用でき、Pythonアプリケーションを開発するための理想的なツールになる多くの機能を提供できます。ユーザーは以下を可能にします。Python拡張機能をインストールして、コードの完了、構文の強調表示、デバッグなどの関数を取得できます。デバッガーを使用して、コードを段階的に追跡し、エラーを見つけて修正します。バージョンコントロールのためにGitを統合します。コードフォーマットツールを使用して、コードの一貫性を維持します。糸くずツールを使用して、事前に潜在的な問題を発見します。

ターミナルVSCODEでプログラムを実行する方法 ターミナルVSCODEでプログラムを実行する方法 Apr 15, 2025 pm 06:42 PM

VSコードでは、次の手順を通じて端末でプログラムを実行できます。コードを準備し、統合端子を開き、コードディレクトリが端末作業ディレクトリと一致していることを確認します。プログラミング言語(pythonのpython your_file_name.pyなど)に従って実行コマンドを選択して、それが正常に実行されるかどうかを確認し、エラーを解決します。デバッガーを使用して、デバッグ効率を向上させます。

VSCODE拡張機能は悪意がありますか? VSCODE拡張機能は悪意がありますか? Apr 15, 2025 pm 07:57 PM

VSコード拡張機能は、悪意のあるコードの隠れ、脆弱性の活用、合法的な拡張機能としての自慰行為など、悪意のあるリスクを引き起こします。悪意のある拡張機能を識別する方法には、パブリッシャーのチェック、コメントの読み取り、コードのチェック、およびインストールに注意してください。セキュリティ対策には、セキュリティ認識、良好な習慣、定期的な更新、ウイルス対策ソフトウェアも含まれます。

See all articles