Di Python, apakah ada perbedaan antara membuat objek generator melalui ekspresi generator versus menggunakan pernyataan yield ?
Menggunakan hasil :
def Generator(x, y):
for i in xrange(x):
for j in xrange(y):
yield(i, j)
Menggunakan ekspresi generator :
def Generator(x, y):
return ((i, j) for i in xrange(x) for j in xrange(y))
Kedua fungsi tersebut mengembalikan objek generator, yang menghasilkan tupel, misalnya (0,0), (0,1) dll.
Adakah keuntungan dari satu atau lainnya? Pikiran?
Terimakasih semuanya! Ada banyak informasi bagus dan referensi lebih lanjut dalam jawaban ini!
python
python-3.x
generator
yield
cschol.dll
sumber
sumber
Jawaban:
Hanya ada sedikit perbedaan di antara keduanya. Anda dapat menggunakan
dis
modul untuk memeriksa sendiri hal semacam ini.Edit: Versi pertama saya mendekompilasi ekspresi generator yang dibuat di module-scope di prompt interaktif. Itu sedikit berbeda dari versi OP yang digunakan di dalam fungsi. Saya telah mengubah ini agar sesuai dengan kasus sebenarnya dalam pertanyaan.
Seperti yang Anda lihat di bawah, generator "yield" (kasus pertama) memiliki tiga instruksi tambahan dalam penyiapan, tetapi dari yang pertama
FOR_ITER
mereka hanya berbeda dalam satu hal: pendekatan "yield" menggunakan aLOAD_FAST
sebagai penggantiLOAD_DEREF
di dalam loop. IniLOAD_DEREF
adalah "agak lebih lambat" daripadaLOAD_FAST
, sehingga membuat versi "hasil" sedikit lebih cepat daripada ekspresi generator untuk nilai yang cukup besar darix
(loop luar) karena nilai dariy
dimuat sedikit lebih cepat pada setiap lintasan. Untuk nilai yang lebih kecilx
akan sedikit lebih lambat karena biaya tambahan dari kode pengaturan.Mungkin juga penting untuk menunjukkan bahwa ekspresi generator biasanya akan digunakan sebaris dalam kode, daripada membungkusnya dengan fungsi seperti itu. Hal itu akan menghapus sedikit overhead penyiapan dan menjaga ekspresi generator sedikit lebih cepat untuk nilai loop yang lebih kecil bahkan jika
LOAD_FAST
versi "yield" memberikan keuntungan sebaliknya.Dalam kedua kasus, perbedaan kinerja tidak akan cukup untuk membenarkan keputusan antara satu atau lainnya. Keterbacaan jauh lebih penting, jadi gunakan mana saja yang dirasa paling mudah dibaca untuk situasi yang dihadapi.
>>> 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
sumber
LOAD_DEREF
"agak lambat", jadi jika kinerja benar-benar penting, beberapa waktu nyata dengantimeit
akan bagus. Analisis teoretis hanya berlaku sejauh ini.Dalam contoh ini, tidak juga. Tetapi
yield
dapat digunakan untuk konstruksi yang lebih kompleks - misalnya ia dapat menerima nilai dari pemanggil juga dan sebagai hasilnya mengubah aliran. Bacalah PEP 342 untuk lebih jelasnya (ini adalah teknik menarik yang perlu diketahui).Bagaimanapun, saran terbaik adalah menggunakan apa pun yang lebih jelas untuk kebutuhan Anda .
PS Berikut contoh coroutine sederhana dari Dave Beazley :
def grep(pattern): print "Looking for %s" % pattern while True: line = (yield) if pattern in line: print line, # Example use if __name__ == '__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!")
sumber
Tidak ada perbedaan untuk jenis loop sederhana yang dapat Anda masukkan ke dalam ekspresi generator. Namun hasil dapat digunakan untuk membuat generator yang melakukan pemrosesan yang jauh lebih kompleks. Berikut adalah contoh sederhana untuk menghasilkan urutan fibonacci:
>>> def fibgen(): ... a = b = 1 ... while True: ... 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]
sumber
Dalam penggunaannya, perhatikan perbedaan antara objek generator vs fungsi generator.
Objek generator hanya digunakan sekali, berbeda dengan fungsi generator, yang bisa digunakan kembali setiap kali Anda memanggilnya lagi, karena mengembalikan objek generator baru.
Ekspresi generator dalam praktiknya biasanya menggunakan "mentah", tanpa membungkusnya dalam sebuah fungsi, dan mereka mengembalikan objek generator.
Misalnya:
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()))
keluaran yang mana:
[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]
Bandingkan dengan penggunaan yang sedikit berbeda:
keluaran yang mana:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [] []
Dan bandingkan dengan ekspresi generator:
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))
yang juga menghasilkan:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [] []
sumber
Penggunaannya
yield
bagus jika ekspresi lebih rumit daripada sekadar loop bersarang. Antara lain Anda dapat mengembalikan nilai pertama khusus atau nilai terakhir khusus. Mempertimbangkan:def Generator(x): for i in xrange(x): yield(i) yield(None)
sumber
Saat memikirkan iterator,
itertools
modul:Untuk performa, pertimbangkan
itertools.product(*iterables[, repeat])
>>> 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)] >>>
sumber
Ya ada bedanya.
Untuk ekspresi generator
(x for var in expr)
,iter(expr)
dipanggil saat ekspresi dibuat .Saat menggunakan
def
danyield
membuat generator, seperti dalam:def my_generator(): for var in expr: yield x g = my_generator()
iter(expr)
belum dipanggil. Ini akan dipanggil hanya saat iterasig
(dan mungkin tidak dipanggil sama sekali).Mengambil iterator ini sebagai contoh:
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
Kode ini:
g1 = (i ** 2 for i in CountDown(3)) # immediately prints "ITER" print("Go!") for x in g1: print(x)
sementara:
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)
Karena sebagian besar iterator tidak melakukan banyak hal
__iter__
, mudah untuk melewatkan perilaku ini. Contoh dunia nyata adalah DjangoQuerySet
, yang mengambil data__iter__
dandata = (f(x) for x in qs)
mungkin membutuhkan banyak waktu, sementaradef g(): for x in qs: yield f(x)
diikuti olehdata=g()
akan segera kembali.Untuk info lebih lanjut dan definisi formal merujuk ke PEP 289 - Ekspresi Generator .
sumber
Ada perbedaan yang mungkin penting dalam beberapa konteks yang belum dikemukakan. Menggunakan
yield
mencegah Anda menggunakanreturn
untuk hal lain selain secara implisit memunculkan StopIteration (dan hal-hal terkait coroutine) .Ini berarti kode ini salah format (dan memberikannya kepada penerjemah akan memberi Anda
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)
Di sisi lain, kode ini berfungsi seperti pesona:
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)
sumber