Pythonにおいて、イテレータ（iterator）は、一連の要素に順序をつけて、要素を順番に処理するためのオブジェクトです。イテレータは、forループで使用することができます。
Pythonのイテレータは、イテレータプロトコルに従うオブジェクトである必要があります。イテレータプロトコルに従うとは、__iter__と__next__という2つのメソッドを実装することを指します。
__iter__メソッドは、イテレータ自身を返します。
__next__メソッドは、次の要素がある場合はその要素を返し、次の要素がない場合はStopIteration例外を発生します。
以下は、イテレータを使って0から4までの数値を順番に取り出す例です。
class MyIterator:
    def __init__(self, x):
        self.x = x
        self.i = 0
    def __iter__(self):
        return self
    def __next__(self):
        if self.i < len(self.x):
            result = self.x[self.i]
            self.i += 1
            return result
        else:
            raise StopIteration
my_iter = MyIterator([0, 1, 2, 3, 4])
for i in my_iter:
    print(i)
このプログラムは、0から4までの数値を順番に取り出して表示します。
iter()関数は、イテラブルオブジェクトを引数として受け取り、そのオブジェクトからイテレータを返す組み込み関数です。イテラブルオブジェクトとは、要素を順番に取り出すことができるオブジェクトのことで、例えばリスト、タプル、文字列、辞書などが挙げられます。
以下は、リストを引数にしてiter()関数を用いてイテレータを取得するコード例です。
lst = [1, 2, 3, 4]
it = iter(lst)
iter()関数によって返されたイテレータは、next()関数を用いて要素を一つずつ順番に取り出すことができます。以下は、イテレータitから要素を取り出して表示するコード例です。
print(next(it))  # 1
print(next(it))  # 2
print(next(it))  # 3
print(next(it))  # 4
イテレータは、最後の要素まで取り出した後にもnext()関数を呼び出すとStopIteration例外が発生します。
また、next()関数は第二引数にデフォルト値を指定することができます。
引数を取らないnext()関数でイテレータから要素を取り出すと、要素がなくなった場合に例外が発生しますが、第二引数で指定した値が返されます。
以下は、イテレータから要素を全て取り出した後にStopIteration例外が発生する代わりに、Noneを返すように指定した例です。
it = iter([1, 2, 3])
print(next(it, None))  # 1
print(next(it, None))  # 2
print(next(it, None))  # 3
print(next(it, None))  # None
iter()関数は、for文でのイテレーションを始める前に暗黙的に呼び出されます。つまり、以下のようなコードがイテレータを取得するコード例と等価です。
lst = [1, 2, 3, 4]
for value in lst:
    print(value)
Pythonのジェネレータは、イテレータを作成するための強力なツールです。ジェネレータは、リスト型の配列などのコレクションとは異なり、動的にアイテムを生成し、それらを逐次処理します。ジェネレータは、yield文で明示的に中断され、再開されることがあります。
以下は、Pythonのジェネレータ関数の例です。
def count_up_to(n):
    i = 1
    while i <= n:
        yield i
        i += 1
このジェネレータ関数は、1からnまでの整数を生成します。例えば、以下のようにして呼び出すことができます。
for num in count_up_to(5):
    print(num)
このコードは、1から5の整数を出力します。ジェネレータは、yieldで中断され、その後、再開されます。 また、ジェネレータ式を使用することもできます。
squares = (num * num for num in count_up_to(10))
これにより、0から9までの整数の2乗を取得することができます。また、ジェネレータは、メモリの使用量がリスト型の配列などの静的なコレクションよりもはるかに少なく、省エネルギーであることが多いため、大きなデータセットの処理に最適です。
素数を求めるアルゴリズムというとエラトステネスの篩が有名ですが、エラトステネスの篩はある値までの素数を高速に求めることが出来ますが、素数は本来は上限がありません。
このことから、エラトステネスの篩とは違いますが、辞書で篩法を実装した上限がないアルゴリズムでジェネレータを書いてみました。
def primegen():
    n = 2
    sieve = {}
    while True:
        print([f"{i}:{sieve[i]}" for i in sorted(sieve.keys())]) # 説明のための sieve のダンプ
        if sieve.get(n, 0) == 0:
            yield n
            sieve[n * n] = n
        else:
            factor = sieve[n]
            sieve[n + factor] = factor
            sieve[n] = 0
        n += 1
for p in primegen():
    print(p, end=' ')
    if p > 10:
        break
[]
2 ['4:2']
3 ['4:2', '9:3']
['4:0', '6:2', '9:3']
5 ['4:0', '6:2', '9:3', '25:5']
['4:0', '6:0', '8:2', '9:3', '25:5']
7 ['4:0', '6:0', '8:2', '9:3', '25:5', '49:7']
['4:0', '6:0', '8:0', '9:3', '10:2', '25:5', '49:7']
['4:0', '6:0', '8:0', '9:0', '10:2', '12:3', '25:5', '49:7']
['4:0', '6:0', '8:0', '9:0', '10:0', '12:2', '25:5', '49:7'] 
11 
if sieve[n] == 0:とせずif sieve.get(n, 0) == 0:としているのはsieve[n]ではnがsieveにないとKeyError例外が上がってしまうためです。