Pythonでは、generator expressionを使用してジェネレーターオブジェクトを作成する場合とyieldステートメントを使用する場合の違いはありますか?
yieldを使用:
def Generator(x, y):
for i in xrange(x):
for j in xrange(y):
yield(i, j)
generator expressionの使用:
def Generator(x, y):
return ((i, j) for i in xrange(x) for j in xrange(y))
両方の関数は、タプルを生成するジェネレーターオブジェクトを返します。 (0,0)、(0,1)など。
どちらか一方の利点はありますか?考え?
みんなありがとう!これらの回答には多くの素晴らしい情報とさらなる参考文献があります!
2つにはわずかな違いしかありません。 dis
モジュールを使用して、この種のことを自分で調べることができます。
Edit:私の最初のバージョンは、対話型プロンプトのmodule-scopeで作成されたジェネレーター式を逆コンパイルしました。これは、関数内で使用されるOPのバージョンとはわずかに異なります。質問の実際のケースに合わせてこれを変更しました。
以下に示すように、「yield」ジェネレータ(最初のケース)には、セットアップに3つの追加の命令がありますが、最初のFOR_ITER
これらは1つの点でのみ異なります。「歩留まり」アプローチではLOAD_FAST
の代わりにLOAD_DEREF
ループ内。 LOAD_DEREF
は "やや遅い"LOAD_FAST
。したがって、x
の値は各パスでわずかに速くロードされるため、y
(外側のループ)の十分に大きな値に対して、ジェネレーター式よりも「yield」バージョンがわずかに速くなります。 。 x
の値が小さい場合、セットアップコードの余分なオーバーヘッドのために、わずかに遅くなります。
ジェネレーター式は、通常、そのような関数でラップするのではなく、コード内でインラインで使用されることを指摘する価値があります。これにより、セットアップのオーバーヘッドが少し削除され、LOAD_FAST
それ以外の場合、「yield」バージョンに利点がありました。
どちらの場合も、パフォーマンスの違いは、どちらか一方を決定することを正当化するのに十分ではありません。読みやすさははるかに重要であるため、目前の状況で最も読みやすいと思われる方を使用してください。
>>> def Generator(x, y):
... for i in xrange(x):
... for j in xrange(y):
... yield(i, j)
...
>>> dis.dis(Generator)
2 0 SETUP_LOOP 54 (to 57)
3 LOAD_GLOBAL 0 (xrange)
6 LOAD_FAST 0 (x)
9 CALL_FUNCTION 1
12 GET_ITER
>> 13 FOR_ITER 40 (to 56)
16 STORE_FAST 2 (i)
3 19 SETUP_LOOP 31 (to 53)
22 LOAD_GLOBAL 0 (xrange)
25 LOAD_FAST 1 (y)
28 CALL_FUNCTION 1
31 GET_ITER
>> 32 FOR_ITER 17 (to 52)
35 STORE_FAST 3 (j)
4 38 LOAD_FAST 2 (i)
41 LOAD_FAST 3 (j)
44 BUILD_Tuple 2
47 YIELD_VALUE
48 POP_TOP
49 JUMP_ABSOLUTE 32
>> 52 POP_BLOCK
>> 53 JUMP_ABSOLUTE 13
>> 56 POP_BLOCK
>> 57 LOAD_CONST 0 (None)
60 RETURN_VALUE
>>> def Generator_expr(x, y):
... return ((i, j) for i in xrange(x) for j in xrange(y))
...
>>> dis.dis(Generator_expr.func_code.co_consts[1])
2 0 SETUP_LOOP 47 (to 50)
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 40 (to 49)
9 STORE_FAST 1 (i)
12 SETUP_LOOP 31 (to 46)
15 LOAD_GLOBAL 0 (xrange)
18 LOAD_DEREF 0 (y)
21 CALL_FUNCTION 1
24 GET_ITER
>> 25 FOR_ITER 17 (to 45)
28 STORE_FAST 2 (j)
31 LOAD_FAST 1 (i)
34 LOAD_FAST 2 (j)
37 BUILD_Tuple 2
40 YIELD_VALUE
41 POP_TOP
42 JUMP_ABSOLUTE 25
>> 45 POP_BLOCK
>> 46 JUMP_ABSOLUTE 6
>> 49 POP_BLOCK
>> 50 LOAD_CONST 0 (None)
53 RETURN_VALUE
この例では、実際にはそうではありません。しかし、yield
は、より複雑な構造に使用できます- たとえば 呼び出し元からの値も受け入れ、結果としてフローを変更できます。詳細については、 PEP 342 をお読みください(知っておく価値のある興味深いテクニックです)。
とにかく、最善のアドバイスは、ニーズに合ったものを使用することです。
追伸以下は、 Dave Beazley の簡単なコルーチンの例です。
def grep(pattern):
print "Looking for %s" % pattern
while True:
line = (yield)
if pattern in line:
print line,
# Example use
if __== '__main__':
g = grep("python")
g.next()
g.send("Yeah, but no, but yeah, but no")
g.send("A series of tubes")
g.send("python generators rock!")
ジェネレータ式に適合することができる単純なループの種類に違いはありません。ただし、yieldを使用すると、はるかに複雑な処理を行うジェネレーターを作成できます。フィボナッチ数列を生成する簡単な例を次に示します。
>>> def fibgen():
... a = b = 1
... while 1:
... yield a
... a, b = b, a+b
>>> list(itertools.takewhile((lambda x: x<100), fibgen()))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
使用時には、ジェネレーターオブジェクトとジェネレーター関数の違いに注意してください。
ジェネレーターオブジェクトは、新しいジェネレーターオブジェクトを返すため、再度呼び出すたびに再利用できるジェネレーター関数とは対照的に、一度だけ使用できます。
ジェネレーター式は、実際には関数でラップせずに通常「生」で使用され、ジェネレーターオブジェクトを返します。
例えば。:
def range_10_gen_func():
x = 0
while x < 10:
yield x
x = x + 1
print(list(range_10_gen_func()))
print(list(range_10_gen_func()))
print(list(range_10_gen_func()))
どの出力:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
わずかに異なる使用法と比較してください:
range_10_gen = range_10_gen_func()
print(list(range_10_gen))
print(list(range_10_gen))
print(list(range_10_gen))
どの出力:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
[]
ジェネレーター式と比較してください:
range_10_gen_expr = (x for x in range(10))
print(list(range_10_gen_expr))
print(list(range_10_gen_expr))
print(list(range_10_gen_expr))
次も出力します。
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
[]
式がネストされたループよりも複雑な場合は、yield
を使用すると便利です。とりわけ、特別な最初の値または特別な最後の値を返すことができます。考慮してください:
def Generator(x):
for i in xrange(x):
yield(i)
yield(None)
イテレータについて考えるとき、itertools
モジュールは:
...単独でまたは組み合わせて使用できる、高速でメモリ効率の高いツールのコアセットを標準化します。一緒になって、「反復代数」を形成し、純粋なPythonで特殊なツールを簡潔かつ効率的に構築できるようにします。
パフォーマンスについては、 itertools.product(*iterables[, repeat])
を考慮してください
入力イテラブルのデカルト積。
ジェネレーター式のネストされたforループと同等です。たとえば、
product(A, B)
は((x,y) for x in A for y in B)
と同じを返します。
>>> import itertools
>>> def gen(x,y):
... return itertools.product(xrange(x),xrange(y))
...
>>> [t for t in gen(3,2)]
[(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]
>>>
はい、違いがあります。
ジェネレーター式_(x for var in expr)
_では、式がcreatedのときにiter(expr)
が呼び出されます。
def
およびyield
を使用してジェネレーターを作成する場合、次のようになります。
_def my_generator():
for var in expr:
yield x
g = my_generator()
_
iter(expr)
はまだ呼び出されていません。 g
を反復する場合にのみ呼び出されます(まったく呼び出されない場合があります)。
このイテレータを例にとります:
_from __future__ import print_function
class CountDown(object):
def __init__(self, n):
self.n = n
def __iter__(self):
print("ITER")
return self
def __next__(self):
if self.n == 0:
raise StopIteration()
self.n -= 1
return self.n
next = __next__ # for python2
_
このコード:
_g1 = (i ** 2 for i in CountDown(3)) # immediately prints "ITER"
print("Go!")
for x in g1:
print(x)
_
一方:
_def my_generator():
for i in CountDown(3):
yield i ** 2
g2 = my_generator()
print("Go!")
for x in g2: # "ITER" is only printed here
print(x)
_
ほとんどのイテレータは___iter__
_で多くの処理を行わないため、この動作を見逃すことは簡単です。実際の例としては、DjangoのQuerySet
があります。これは ___iter__
_ およびdata = (f(x) for x in qs)
のデータを取得するのに時間がかかり、def g(): for x in qs: yield f(x)
にdata=g()
が続くと、すぐに戻ります。
詳細と正式な定義については、 PEP 289-ジェネレータ式 を参照してください。
まだ指摘されていないコンテキストでは重要な違いがあります。 yield
を使用すると、 暗黙的にStopIteration(およびコルーチン関連のもの)を上げる 以外のものにreturn
を使用できなくなります。
これは、このコードの形式が正しくないことを意味します(インタープリターに渡すとAttributeError
が返されます)。
class Tea:
"""With a cloud of milk, please"""
def __init__(self, temperature):
self.temperature = temperature
def mary_poppins_purse(tea_time=False):
"""I would like to make one thing clear: I never explain anything."""
if tea_time:
return Tea(355)
else:
for item in ['lamp', 'mirror', 'coat rack', 'tape measure', 'ficus']:
yield item
print(mary_poppins_purse(True).temperature)
一方、このコードは魅力のように機能します。
class Tea:
"""With a cloud of milk, please"""
def __init__(self, temperature):
self.temperature = temperature
def mary_poppins_purse(tea_time=False):
"""I would like to make one thing clear: I never explain anything."""
if tea_time:
return Tea(355)
else:
return (item for item in ['lamp', 'mirror', 'coat rack',
'tape measure', 'ficus'])
print(mary_poppins_purse(True).temperature)