Yanıtlar:
Jeneratörler size tembel bir değerlendirme yapar. Bunları üzerlerinde yineleyerek, açıkça 'for' ile veya dolaylı olarak yineleyen herhangi bir işleve veya yapıya geçirerek kullanırsınız. Üreteçleri, bir liste döndürüyormuş gibi birden çok öğeyi döndürmek olarak düşünebilirsiniz, ancak hepsini bir kerede döndürmek yerine, bunları teker teker döndürmek yerine bir sonraki öğe talep edilene kadar jeneratör işlevi duraklatılır.
Jeneratörler, tüm sonuçlara ihtiyacınız olup olmayacağını bilmediğiniz veya aynı anda tüm sonuçlar için belleği ayırmak istemediğiniz büyük sonuçları (özellikle döngüler içeren hesaplamaları) hesaplamak için iyidir . Veya jeneratörün başka bir jeneratör kullandığı veya başka bir kaynak tükettiği durumlar için ve bu mümkün olduğunca geç gerçekleşmişse daha uygundur.
Jeneratörler için başka bir kullanım (gerçekten aynıdır) geri çağrıları yinelemeyle değiştirmektir. Bazı durumlarda bir işlevin çok fazla iş yapmasını ve arayana geri bildirmesini istersiniz. Geleneksel olarak bunun için bir geri arama işlevi kullanırsınız. Bu geri aramayı çalışma işlevine iletirsiniz ve düzenli olarak bu geri aramayı çağırır. Jeneratör yaklaşımı, çalışma fonksiyonunun (şimdi bir jeneratör) geri arama hakkında hiçbir şey bilmemesi ve yalnızca bir şey raporlamak istediğinde verim vermesidir. Arayan, ayrı bir geri arama yazmak ve bunu çalışma fonksiyonuna aktarmak yerine, tüm raporlama jeneratörün etrafında küçük bir 'for' döngüsünde çalışır.
Örneğin, bir 'dosya sistemi arama' programı yazdığınızı varsayalım. Aramayı tamamen gerçekleştirebilir, sonuçları toplayabilir ve ardından birer birer görüntüleyebilirsiniz. İlk sonuçları göstermeden önce tüm sonuçların toplanması gerekir ve tüm sonuçlar aynı anda hafızada olur. Ya da sonuçları bulduğunuzda görüntüleyebilirsiniz, bu da bellekte daha verimli ve kullanıcıya karşı daha dostça olacaktır. İkincisi, sonuç yazdırma işlevini dosya sistemi arama işlevine geçirerek yapılabilir veya yalnızca arama işlevini bir jeneratör yaparak ve sonuç üzerinde yineleyerek yapılabilir.
Son iki yaklaşımın bir örneğini görmek istiyorsanız, bkz. Os.path.walk () (geri çağrı ile eski dosya sistemi yürüme işlevi) ve os.walk () (yeni dosya sistemi yürüme jeneratörü.) gerçekten tüm sonuçları bir listede toplamak istediniz, jeneratör yaklaşımı büyük liste yaklaşımına dönüştürmek önemsizdir:
big_list = list(the_generator)
yield
ve join
sonra el ile başlatma sorununa gitmedikçe , paralel olarak çalışmaz (ve hiçbir standart kütüphane oluşturucusu bunu yapmaz; gizlice başlatma iş parçacıkları kaşlarını çatmaz). Bir yield
sonraki değer talep edilene kadar jeneratör her birinde duraklar . Jeneratör G / Ç'yi sarıyorsa, işletim sistemi kısa süre içinde talep edileceği varsayımıyla dosyadan proaktif olarak önbellek veriyor olabilir, ancak bu işletim sistemi, Python dahil değildir.
Jeneratörü kullanmanın nedenlerinden biri, çözümü bir tür çözüm için daha net hale getirmektir.
Diğeri, sonuçları birer birer işlemek, yine de ayrılmış olarak işleyeceğiniz büyük sonuç listeleri oluşturmaktan kaçınmaktır.
Böyle bir fibonacci-up-to-n fonksiyonunuz varsa:
# function version
def fibon(n):
a = b = 1
result = []
for i in xrange(n):
result.append(a)
a, b = b, a + b
return result
Fonksiyonu şu şekilde daha kolay yazabilirsiniz:
# generator version
def fibon(n):
a = b = 1
for i in xrange(n):
yield a
a, b = b, a + b
İşlev daha nettir. Ve eğer işlevi böyle kullanırsanız:
for x in fibon(1000000):
print x,
bu örnekte, jeneratör sürümü kullanılıyorsa, 1000000 öğe listesinin tamamı oluşturulmaz, her seferinde sadece bir değer. İlk olarak bir listenin oluşturulacağı liste sürümü kullanılırken durum böyle olmaz.
list(fibon(5))
PEP 255'teki "Motivasyon" bölümüne bakın .
Üreticilerin aşikar olmayan bir kullanımı, iş parçacığı kullanmadan güncelleme kullanıcı arayüzü veya birkaç işi "aynı anda" (aslında araya eklenmiş gibi) çalıştırmanıza izin veren kesilebilir işlevler oluşturuyor.
Şüphemi temizleyen bu açıklamayı buluyorum. Çünkü bilmeyen bir kişinin Generators
de bilmediği bir olasılık varyield
Dönüş
Return ifadesi, tüm yerel değişkenlerin yok edildiği ve ortaya çıkan değerin arayana geri verildiği (döndürüldüğü) yerdir. Aynı fonksiyon bir süre sonra çağrılırsa, fonksiyon yeni bir değişken seti alacaktır.
Yol ver
Fakat bir fonksiyondan çıktığımızda yerel değişkenler atılmazsa ne olur? Bu, kaldığımız yeri yapabileceğimizi gösterir resume the function
. Burası kavramın generators
tanıtıldığı ve yield
ifadenin function
kaldığı yerden devam ettiği yerdir .
def generate_integers(N):
for i in xrange(N):
yield i
In [1]: gen = generate_integers(3)
In [2]: gen
<generator object at 0x8117f90>
In [3]: gen.next()
0
In [4]: gen.next()
1
In [5]: gen.next()
Python'daki return
ve yield
ifadeler arasındaki fark budur .
Verim deyimi, bir işlevi bir jeneratör işlevi yapan şeydir.
Yani jeneratörler yineleyiciler oluşturmak için basit ve güçlü bir araçtır. Normal işlevler gibi yazılırlar, ancak yield
veri döndürmek istediklerinde ifadeyi kullanırlar . Next () her çağrıldığında, jeneratör kaldığı yerden devam eder (tüm veri değerlerini ve en son hangi cümleyi çalıştırdığını hatırlar).
Diyelim ki MySQL tablonuzda 100 milyon alanınız var ve her alan için Alexa derecesini güncellemek istiyorsunuz.
İhtiyacınız olan ilk şey, alan adlarınızı veritabanından seçmektir.
Diyelim ki tablo adınız domains
ve sütun adınız domain
.
Eğer kullanırsanız SELECT domain FROM domains
, çok fazla bellek tüketecek 100 milyon satır döndürecek. Böylece sunucunuz çökebilir.
Böylece programı toplu olarak çalıştırmaya karar verdiniz. Parti boyutumuzun 1000 olduğunu varsayalım.
İlk partimizde ilk 1000 satırı sorgulayacağız, her alan için Alexa derecesini kontrol edeceğiz ve veritabanı satırını güncelleyeceğiz.
İkinci partimizde önümüzdeki 1000 satır üzerinde çalışacağız. Üçüncü partimizde 2001'den 3000'e kadar olacak.
Şimdi yığınlarımızı üreten bir jeneratör fonksiyonuna ihtiyacımız var.
İşte jeneratör fonksiyonumuz:
def ResultGenerator(cursor, batchsize=1000):
while True:
results = cursor.fetchmany(batchsize)
if not results:
break
for result in results:
yield result
Gördüğünüz gibi, fonksiyonumuz yield
sonuçlara devam ediyor . return
Bunun yerine anahtar kelimeyi kullandıysanız yield
, tüm işlev, dönüşe ulaştığında sona erer.
return - returns only once
yield - returns multiple times
Eğer bir fonksiyon anahtar kelimeyi kullanıyorsa, yield
o zaman bu bir jeneratör olur.
Şimdi şöyle tekrarlayabilirsiniz:
db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="domains")
cursor = db.cursor()
cursor.execute("SELECT domain FROM domains")
for result in ResultGenerator(cursor):
doSomethingWith(result)
db.close()
Tamponlama. Büyük parçalar halinde veri almak verimli, ancak küçük parçalar halinde işlemek verimli olduğunda, bir jeneratör yardımcı olabilir:
def bufferedFetch():
while True:
buffer = getBigChunkOfData()
# insert some code to break on 'end of data'
for i in buffer:
yield i
Yukarıdakiler, tamponlamayı işlemden kolayca ayırmanızı sağlar. Tüketici işlevi artık değerleri arabelleğe alma konusunda endişelenmeden tek tek alabilir.
Jeneratörlerin kodunuzu temizlemede ve kodu kapsüllemek ve modüle etmek için benzersiz bir yol vererek çok yardımcı olduğunu gördüm. Bir şeylerin ihtiyaçları Kodunuzdaki her yerde aradı (ve sadece bir döngü veya örneğin bir blok içinde) vakti gelince sürekli kendi iç işlemeye ve esaslı değerleri tükürmek bir şey lazım bir durumda, jeneratörleri için özellik kullanın.
Soyut bir örnek, bir döngü içinde yaşamayan bir Fibonacci sayı üreteci olabilir ve herhangi bir yerden çağrıldığında her zaman dizideki bir sonraki sayıyı döndürür:
def fib():
first = 0
second = 1
yield first
yield second
while 1:
next = first + second
yield next
first = second
second = next
fibgen1 = fib()
fibgen2 = fib()
Artık kodunuzun herhangi bir yerinden çağırabileceğiniz iki Fibonacci sayı üreteci nesneniz var ve her zaman daha büyük Fibonacci sayılarını aşağıdaki gibi sırayla döndürecekler:
>>> fibgen1.next(); fibgen1.next(); fibgen1.next(); fibgen1.next()
0
1
1
2
>>> fibgen2.next(); fibgen2.next()
0
1
>>> fibgen1.next(); fibgen1.next()
3
5
Jeneratörlerle ilgili en güzel şey, nesneleri yaratma çemberinden geçmek zorunda kalmadan durumu kapsüllemeleridir. Onlar hakkında düşünmenin bir yolu, içsel durumlarını hatırlayan "işlevler" dir.
Python Jeneratörlerinden Fibonacci örneğini aldım - Nedir bunlar? ve küçük bir hayal gücü ile, jeneratörlerin for
döngülere ve diğer geleneksel yineleme yapılarına mükemmel bir alternatif oluşturduğu birçok başka durumla karşılaşabilirsiniz .
Basit açıklama: Bir for
ifade düşünün
for item in iterable:
do_stuff()
Çoğu zaman, tüm öğelerin iterable
başlangıçtan itibaren orada olması gerekmez, ancak gerektiğinde anında oluşturulabilir. Bu her ikisinde de çok daha verimli olabilir
Diğer zamanlarda, tüm öğeleri önceden bilmiyorsunuz bile. Örneğin:
for command in user_input():
do_stuff_with(command)
Kullanıcının tüm komutlarını önceden bilmenin bir yolu yoktur, ancak size komutları teslim eden bir jeneratörünüz varsa böyle güzel bir döngü kullanabilirsiniz:
def user_input():
while True:
wait_for_command()
cmd = get_command()
yield cmd
Jeneratörler ile, elbette kaplar üzerinde yineleme yaparken mümkün olmayan sonsuz diziler üzerinde yineleme yapabilirsiniz.
Favori kullanımlarım "filtre" ve "azaltma" işlemleridir.
Diyelim ki bir dosya okuyoruz ve yalnızca "##" ile başlayan satırları istiyoruz.
def filter2sharps( aSequence ):
for l in aSequence:
if l.startswith("##"):
yield l
Daha sonra jeneratör fonksiyonunu uygun bir döngüde kullanabiliriz
source= file( ... )
for line in filter2sharps( source.readlines() ):
print line
source.close()
İndirgeme örneği benzerdir. Diyelim ki <Location>...</Location>
satır bloklarını bulmamız gereken bir dosya var . [HTML etiketleri değil, etiket benzeri görünen satırlar.]
def reduceLocation( aSequence ):
keep= False
block= None
for line in aSequence:
if line.startswith("</Location"):
block.append( line )
yield block
block= None
keep= False
elif line.startsWith("<Location"):
block= [ line ]
keep= True
elif keep:
block.append( line )
else:
pass
if block is not None:
yield block # A partial block, icky
Yine, bu jeneratörü döngü için uygun bir şekilde kullanabiliriz.
source = file( ... )
for b in reduceLocation( source.readlines() ):
print b
source.close()
Fikir, bir jeneratör fonksiyonunun bir sekansı filtrelememize veya azaltmamıza izin vererek, her seferinde bir değer olan başka bir sekans üretmesidir.
fileobj.readlines()
jeneratörleri kullanma amacını yenerek tüm dosyayı hafızadaki bir listeye okurdu. Dosya nesneleri zaten yinelenebilir olduğundan, for b in your_generator(fileobject):
bunun yerine kullanabilirsiniz . Bu şekilde, tüm dosyayı okumaktan kaçınmak için dosyanız her seferinde bir satır okunacaktır.
Bir jeneratörü kullanabileceğiniz pratik bir örnek, bir çeşit şekle sahipseniz ve köşeleri, kenarları veya her şeyi tekrarlamak istiyorsanız. Kendi projem için ( burada kaynak kodu ) bir dikdörtgen vardı:
class Rect():
def __init__(self, x, y, width, height):
self.l_top = (x, y)
self.r_top = (x+width, y)
self.r_bot = (x+width, y+height)
self.l_bot = (x, y+height)
def __iter__(self):
yield self.l_top
yield self.r_top
yield self.r_bot
yield self.l_bot
Şimdi bir dikdörtgen oluşturabilir ve köşelerinde döngü yapabilirim:
myrect=Rect(50, 50, 100, 100)
for corner in myrect:
print(corner)
Bunun yerine __iter__
bir yönteminiz olabilir iter_corners
ve bunu ile çağırabilirsiniz for corner in myrect.iter_corners()
. O __iter__
zamandan beri kullanmak daha zarif , sınıf örneği adını doğrudan for
ifadede kullanabiliriz.
Bununla birlikte, bazı iyi cevaplar, ayrıca , jeneratörlerin daha güçlü kullanım durumlarından bazılarının açıklanmasına yardımcı olan Python Fonksiyonel Programlama öğreticisinin tamamen okunmasını da tavsiye ederim .
Bir jeneratörün gönderme yönteminden bahsedilmediğinden, işte bir örnek:
def test():
for i in xrange(5):
val = yield
print(val)
t = test()
# Proceed to 'yield' statement
next(t)
# Send value to yield
t.send(1)
t.send('2')
t.send([3])
Çalışan bir jeneratöre bir değer gönderme olasılığını gösterir. Aşağıdaki videodaki jeneratörler hakkında daha gelişmiş bir kurs (açıklama yield
, paralel işleme için jeneratörler, özyineleme sınırından kaçınma vb. Dahil )
Malzeme yığınları. Bir öğe dizisi oluşturmak istediğinizde, ancak hepsini bir seferde bir listeye 'gerçekleştirmek' istemiyorsanız. Örneğin, asal sayıları döndüren basit bir oluşturucunuz olabilir:
def primes():
primes_found = set()
primes_found.add(2)
yield 2
for i in itertools.count(1):
candidate = i * 2 + 1
if not all(candidate % prime for prime in primes_found):
primes_found.add(candidate)
yield candidate
Daha sonra bunu sonraki primerlerin ürünlerini üretmek için kullanabilirsiniz:
def prime_products():
primeiter = primes()
prev = primeiter.next()
for prime in primeiter:
yield prime * prev
prev = prime
Bunlar oldukça önemsiz örneklerdir, ancak büyük (potansiyel olarak sonsuz!) Veri kümelerini önceden oluşturmadan nasıl işleyebileceğini görebilirsiniz; bu, daha açık kullanımlardan sadece biridir.
Ayrıca n'ye kadar asal sayıları yazdırmak için de iyidir:
def genprime(n=10):
for num in range(3, n+1):
for factor in range(2, num):
if num%factor == 0:
break
else:
yield(num)
for prime_num in genprime(100):
print(prime_num)