2015-10-05 5 views
5

zipをエミュレートする2つの関数をPython 2.xと3.xで組み込みました。最初のものは、(Pythonの2.xでのような)リストを返し、もう一つは(Pythonの3.xのように)時間に設定された結果の一枚を返すジェネレータ関数である:ジェネレータ式を使用するとPythonがハングする

def myzip_2x(*seqs): 
    its = [iter(seq) for seq in seqs] 
    res = [] 
    while True: 
     try: 
      res.append(tuple([next(it) for it in its])) # Or use generator expression? 
      # res.append(tuple(next(it) for it in its)) 
     except StopIteration: 
      break 
    return res 

def myzip_3x(*seqs): 
    its = [iter(seq) for seq in seqs] 
    while True: 
     try: 
      yield tuple([next(it) for it in its])   # Or use generator expression? 
      # yield tuple(next(it) for it in its) 
     except StopIteration: 
      return 

print(myzip_2x('abc', 'xyz123'))     
print(list(myzip_3x([1, 2, 3, 4, 5], [7, 8, 9]))) 

このうまく機能と内蔵のzipの予想出力が得られます。

[('a', 'x'), ('b', 'y'), ('c', 'z')] 
[(1, 7), (2, 8), (3, 9)] 

それから私は(なぜ角括弧[]を削除することによって、その(ほぼ)同等のジェネレータ式でtuple()通話中にリストの内包を交換することについて考えジェネレータが反復可能なexpのためにうまくいくべきであるときに、理解を使って一時的なリストを作成するtuple()、右か?)

しかし、これはPythonをハングさせます。 CtrlC(WindowsのIDLEの場合)を使用して実行が終了しない場合、最終的に(予期される)MemoryErrorの例外が数分後に停止します。

(PyScripterを使用して)コードをデバッグすると、ジェネレータ式を使用すると例外が発生しないことが明らかになりました。 myzip_3x()の第2の実施の呼び出しはタプル(1, 7)(2, 8)(3, 9)(4,)(5,)()()()...を生成しながら、myzip_2x()に上記第1の実施の呼び出しは、resに空の組を追加し続けます。

何か不足していますか?

最後の注意:itsが、各機能の最初の行(tuple()呼び出しで使用されている場合)のジェネレータ(its = (iter(seq) for seq in seqs)を使用)になる場合、同じハング動作が表示されます。

編集:説明のための

おかげ@Blckknghtを、あなたは正しかったです。 This messageは、上記のジェネレータ関数と同様の例を使用して、何が起こっているかについてより詳細を示します。結論として、生成式を使用すると、Python 3.5+でしか動作せず、ファイルの先頭にfrom __future__ import generator_stop文が必要で、StopIterationRuntimeErrorに変更する必要があります(リスト内包表記の代わりにジェネレータ式を使用する場合)。

編集2:上記の最後の注意として

itsは(its = (iter(seq) for seq in seqs)を使用して)発電機になった場合、それは、単に1つの反復をサポートする - ジェネレータはワンショットイテレータからです。したがって、whileループが最初に実行されたときには使い果たされ、その後のループでは空のタプルだけが得られます。

答えて

2

あなたが見ている動作はバグです。これは、ジェネレータからの例外バブリングが正常に終了するジェネレータと区別できないという事実に由来します。これは、tryexceptのジェネレータにループをラップすることはできず、ループロジックが例外を消費するため、ループを中断させるためにStopIterationを探すことができないことを意味します。

PEP 479はバブルアップの前にRuntimeErrorに発電機のターンの内側にキャッチされていないStopIterationを作るために言語を変更することで、問題の修正を提案しています。これはあなたのコードを(あなたがキャッチする例外の種類を少し微調整して)動作させることができます。

PEPはPython 3.5で実装されていますが、下位互換性を維持するために、変更された動作は、ファイルの先頭にfrom __future__ import generator_stopを入れて要求した場合にのみ利用可能です。新しい動作は、Python 3.7ではデフォルトで有効になります(Python 3.6ではデフォルトの動作に戻りますが、状況が発生した場合は警告が表示されることがあります)。

0

あなたは:

tuple([next(it) for it in its]) 

あなたが最初tuple()にそれを渡した後、リストを作成しています。 StopIterationが発生してリストを作成できない場合、リストは作成されず、例外が伝播されます。

しかし、あなたが行うとき:あなたは発電機を構築し、tuple()に直接渡している

tuple(next(it) for it in its) 

。タプルコンストラクタは、ジェネレータをイテレータとして使用します。つまり、StopIterationが呼び出されるまでアイテムを覗き見します。

つまり、StopIterationtuple()にキャッチされ、伝播されません。

直ちにStopIterationを生成するジェネレータは空のタプルに変換されます。

0

私は本当にそれについてはわかりませんが、内部で生成されたネストされたジェネレータと外側のキャッチがあるようです。StopIteration

def gen(its): 
    for it in its: 
     yield next(it) # raises StopIteration 

tuple(gen(its)) # doesn't raises StopIteration 

それはあなたのバージョンが何に等しい何かを行います。

は、この例で考えてみましょう。

2

以下は、Python言語リファレンスやリファレンス実装ではなく、これらのコードの実行時の動作に基づく推測です。

tuple(next(it) for it in its)は、tuple(generator)の場合、generator = (next(it) for it in its)に相当します。発電機がStopIterationを上げるときfor文は、疲労の兆候として、任意のStopIterationをキャッチするのでnext(it)がそれを発生させているため、for文は単にそれをキャッチします

def __init__(self, generator): 
    for element in generator: 
     self.__internal_array.append(element) 

tupleコンストラクタは以下のコードと同等概念的です発電機が枯渇したと考えています。これは、ループが終了せず、空のタプルが追加される理由です。例外はコンストラクタtupleを決して泡立てません。

リスト内包、[next(it) for it in its]が、一方で、だから、StopIterationfor文によって捕捉されない

result = [] 
for it in its: 
    result.append(next(it)) 

と概念的に同等されています。

この例は、リテラルな理解とジェネレータ式によるコンストラクタコールの面白い違いを示しています。 list(next(it) for it in its[next(it) for it in its]を使用すると同じことが起こります。

+0

ループや関数の外でコードを実行して推測を確認できました。 –

+0

このような良い概念の説明をありがとう。 – John

関連する問題