coLinux日記

coLinuxはフリーソフトを種として、よろずのシステムとぞなれりける。

SimplePrograms で Python を学ぶ その18

2024-08-02 22:41:20 | Python
SimplePrograms - Python Wiki
https://wiki.python.org/moin/SimplePrograms

の 18番目のプログラムは、 20行の素数のふるいです。

import itertools

def iter_primes():
    # an iterator of all numbers between 2 and +infinity
    numbers = itertools.count(2)

    # generate primes forever
    while True:
        # get the first number from the iterator (always a prime)
        prime = next(numbers)
        yield prime

        # this code iteratively builds up a chain of
        # filters...slightly tricky, but ponder it a bit
        numbers = filter(prime.__rmod__, numbers)

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


15 lines プログラムで使った itertools モジュールのイテレータ count() を使うのですね。
https://docs.python.org/ja/3/library/itertools.html
によれば、

count()、 引数:[start,[step]]、 結果:start,start+step,start+2*step,...

となる、無限イテレータ(Infinite iterators)だそうです。
つまり、itertools.count(2) は 2以上の 1づつ増加する数列を生成するのですね。
そして、この数列は、next()で1つづつ取り出せるわけです。

>>> n = itertools.count(2)
>>> p = next(n)
>>> print(p)
2
>>> p = next(n)
>>> print(p)
3
>>> p = next(n)
>>> print(p)
4
>>>

iter_primes()関数の動きを見てみます。
メインのwhile ループの中で、 yield を無視して動作を調べると、

>>> import itertools
>>> numbers = itertools.count(2)
>>> # ここから while ループ
>>> prime = next(numbers)
>>> numbers = filter(prime.__rmod__, numbers)
>>> print(prime)
2
>>> prime = next(numbers)
>>> numbers = filter(prime.__rmod__, numbers)
>>> print(prime)
3
....................
>>> prime = next(numbers)
>>> numbers = filter(prime.__rmod__, numbers)
>>> print(prime)
31
>>> prime = next(numbers)
>>> numbers = filter(prime.__rmod__, numbers)
>>> print(prime)
37
>>> prime = next(numbers)
>>> numbers = filter(prime.__rmod__, numbers)
>>> print(prime)
41
>>>
....................

このように、毎回素数が得られるわけですね。filter() は numbers からprime で割り切れるものを除いて、新しい numbers を生成するメソッドのようです。

その後、for 文を使って、iter_primes() を1回だけ呼び出して、生成された素数のイテレータオブジェクトから1つづつ取り出して(つまり next() のように動作する?) p に代入し、その素数が1000 を越えるまで print(p) で出力するのがこのプログラムです。

しかし、普通のプログラミング言語だと、このような関数を作り出すのは難しいように思います。それを Python では、 yield 文(式?)を使ってこれを実現するようです。

関数 iter_prime() は、中で yield prime つまり、 yield 文を使っているのでジェネレータ関数になります。

最初にこのジェネレータ関数を呼び出すと、
yield 式まで実行して prime の値を返して処理を「一時停止」し、
次にこの関数の一時停止を解除すると、yield の次を実行して while ループで繰り返して yield 式の処理をして、
また一時停止します、
のようになるらしいです。

つまり、
>>> import itertools
>>>
>>> # 1回目
>>> numbers = itertools.count(2)
>>>
>>> prime = next(numbers)
>>> print(prime)
2
>>>
>>> # 2回目
>>> numbers = filter(prime.__rmod__, numbers)
>>> prime = next(numbers)
>>> print(prime)
3
>>>
>>> # 3回目
>>> numbers = filter(prime.__rmod__, numbers)
>>> prime = next(numbers)
>>> print(prime)
5
>>>
===== 以下省略 ====

となるので、ここで生成される関数iter_primes()は、素数の無限イテレータ・オブジェクトを生成し、 for 文の in で指定され最初に1回だけこの関数が実行され、生成されたジェネレータオブジェクトから次々と素数が p に代入されるみたいです。

これは for文の復習として、以下のリファレンスを参考に説明してみました。

------------------------
for 文は、シーケンス (文字列、タプルまたはリスト) や、その他の反復可能なオブジェクト (iterable object) 内の要素に渡って反復処理を行うために使われます:

for_stmt ::= "for" target_list "in" starred_list ":" suite
            ["else" ":" suite]

starred_list 式は一度だけ評価され、評価結果として イテラブル オブジェクトを返す必要があります。そのイテラブルから イテレータ が作られます。

------------------------

つまり、
>>> a = iter_primes() # 一回しか関数を呼び出さないことを強調します。
>>> a
<generator object iter_primes at 0xf7027db8>
>>> for p in a:
...       if p > 6:
...          break
...       print(p)
...
2
3
5
>>>

です。 この動きはとても興味深いです。

ちょっと理解が怪しくなっているように感じているので、次回以降に yield や filter() を調べて見たいと思います。
コメント (1)
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする