2017-03-12 6 views
1

私は、次のPythonの関数(s)は書かれている:あなたが見ることができるように、両方の機能がreturn声明を持っていない"無限"の反復可能関数を使用してループを閉じるとどうなりますか?

import numpy 

def primes_iterable(): 
    """Iterable giving the primes""" 
    # The lowest primes 
    primes = [2,3,5] 
    for p in primes: 
     yield p 
    for n in potential_primes(): 
     m = int(numpy.sqrt(n)) 
     check = True 
     for p in primes: 
      if p > m: 
       break 
      if n%p == 0: 
       check = False 
     if check: 
      primes.append(n) 
      yield n 

def potential_primes(): 
    """Iterable starting at 7 and giving back the non-multiples of 2,3,5""" 
    yield 7 
    n = 7 
    gaps = [4,2,4,2,4,6,2,6] 
    while 1: 
     for g in gaps: 
      n += g 
      yield n 

を。私はこのような何かを書くことだったと仮定します。

for p in primes_iterable(): 
    if p > 1000: 
     break 
    print p 

break文が到達したときに、メモリのレベルでどうなりますか?私が正しく理解するならば、primes_iterable()と呼ぶと機能が起動し、次のyieldまで行き、再び必要になるまで一時停止します。 breakステートメントに達すると、関数インスタンスは閉じますか、またはバックグラウンドに存在し続けるのは完全に役に立たないのですか?

+1

はCPythonまたは別の実装:

def gen(): print("starting") try: while 1: yield "foo" except GeneratorExit: print("caught GeneratorExit") raise finally: print("cleaning up") 

ここでサンプル実行はですか? – MSeifert

+0

@MSeifert Python 2.7でSpyderを使用しています –

答えて

4

あなたの機能primes_iterableはジェネレータ関数です。あなたがそれを呼び出すと、何も起こりません(ジェネレータオブジェクトを返す以外)。 nextが呼び出されたときのみ、次のyieldに実行されます。

ジェネレータ関数を呼び出すと、反復可能なジェネレータオブジェクトが取得されます。 forループでこれを実行している場合、ループは、実行中にジェネレータオブジェクトへの参照を保持します。ループの外にbreakがある場合、その参照は解放され、ジェネレータオブジェクトはガベージコレクションされます。

しかし、ジェネレータオブジェクトがクリーンアップされると、ジェネレータ関数で実行されるコードはどうなりますか?それはyieldで中断されたGeneratorStop例外によって中断され、中断されました。必要があれば、ジェネレータ関数がこの例外をキャッチすることもできますが、リソースをクリーンアップして終了する以外には何も役に立ちません。これは多くの場合、exceptステートメントではなく、try/finallyのペアで行われます。

はここで動作を示し、いくつかのサンプルコードです:

>>> for i, s in enumerate(gen()): 
    print(s) 
    if i >= 3: 
     break 

starting 
foo 
foo 
foo 
foo 
caught GeneratorExit 
cleaning up 
2

だけ明確にするため、あなた、それは最終的にはゴミが収集されるように発電機に残された参照がないforループからbreak ...
primes_iterable()を呼び出すとジェネレータを作成します。ジェネレータにnext()をコールするとジェネレータに制御が渡され、yieldになるまで実行されます。 forは暗黙的に各ループnext()を呼び出します。

この考えてみましょう:あなたは、常に次の素数を得ることができるので、今、あなたはまだ発電への参照を持っている

prime = primes_iterable() 
print(next(prime)) # 2 
for p in prime: 
    if p > 1000: 
     break 
    print(p)   # 3, 5, 7, ... 

primeと呼ばれる:

print(next(prime)) # 1013 
+1

ジェネレータが無限大であることに注意してください。ジェネレータオブジェクトは、どのように "長い"かにかかわらず同じ方法で作成され、ガベージコレクションされます。実際に発電機が実際にそれをやらなければどれだけ長く続くかを知る方法はありません。 – BrenBarn

+0

@BrenBarn、確かに。枯渇したジェネレータは、すべての参照がなくなるまでガベージコレクションされません。引き続き 'next()'を呼び出すことができ、 'StopIterationを引き上げる' – AChampion

1

primes_iterable()イテレータを返します。これはnextを呼び出すたびに新しい値を吐き出すオブジェクトです。これは、ループの背後にあるforのループです。これを試してみてください:

it = primes_iterable() 
print(next(it)) 
print(next(it)) 

注意することが重要でitがここ舞台裏永遠に実行されていないということです、それはちょうどあなたがそれを頼むたびに新しい価値を吐き出すために十分に実行されます。データを保持しておくので、いつでもデータの再起動が可能ですが、そのデータにアクセスすることはできません。

今、あなたのコードでは、primes_iterable上記のよう

for p in primes_iterable(): 

が呼び出され、この場合には、イテレータに名前がないものの(すなわち、それが変数にバインドされていない)、イテレータを返しました。ループのすべてのステップについて、pはイテレータのnextに割り当てられます。

if p > 1000: 
     break 

今、私たちは抜け出すと、forループイテレータ上でnextの実行を停止します。これ以上イテレータを参照するものはありません(グローバル名前空間で定義されたすべてを表示するdir()を呼び出して確認できます)。

したがって、しばらくすると、反復子が占めていたメモリがPythonによって解放されます。これをガベージコレクションといいます。たとえば次のような場合にも起こります。インタプリタに[1,2,3]と入力しますが、変数名にバインドしないでください。それは作成されますが、無意味なのでスペースを解放するために効果的に削除されます。

あなたは(とすべきである)、ここでイテレータ詳細を読むことができます:

https://docs.python.org/3/tutorial/classes.html#iterators

関連する問題