Jeneratör dili olanağına sahip olmak yield
iyi bir fikir mi?
Buna Python perspektifinden vurgulu bir evet ile cevap vermek istiyorum , bu harika bir fikir .
Öncelikle sorunuzdaki bazı soruları ve varsayımları ele alarak başlayacağım, sonra jeneratörlerin yaygınlığını ve daha sonra Python'daki makul olmayan yararlılıklarını göstereceğim.
Normal bir jeneratör olmayan fonksiyon ile onu çağırabilirsiniz ve aynı giriş verilirse aynı çıkışı döndürür. Verim ile, dahili durumuna bağlı olarak farklı çıktılar döndürür.
Bu yanlış. Nesneler üzerindeki yöntemler, kendi iç durumlarıyla birlikte işlevler olarak düşünülebilir. Python'da, her şey bir nesne olduğundan, aslında bir nesneden bir yöntem alabilir ve bu yöntemi (bu nesnenin geldiği nesneye bağlı olduğu için) iletebilirsiniz, böylece durumunu hatırlar).
Diğer örnekler arasında kasıtlı olarak rastgele işlevlerin yanı sıra ağ, dosya sistemi ve terminal gibi giriş yöntemleri yer alır.
Böyle bir işlev dil paradigmasına nasıl uyar?
Dil paradigması birinci sınıf işlevler gibi şeyleri destekliyorsa ve jeneratörler Yinelenebilir protokol gibi diğer dil özelliklerini destekliyorsa, sorunsuz bir şekilde uyuyorlar.
Gerçekten herhangi bir sözleşmeyi bozuyor mu?
Hayır. Dilde pişirildiğinden, sözleşmeler etrafında inşa edilir ve jeneratör kullanımını içerir (veya gerektirir!).
Programlama dili derleyicileri / tercümanları böyle bir özelliği uygulamak için herhangi bir kuraldan kurtulmak zorunda mıdır?
Diğer özelliklerde olduğu gibi, derleyicinin de özelliği destekleyecek şekilde tasarlanması gerekir. Python durumunda, işlevler halihazırda durumu olan nesnelerdir (varsayılan argümanlar ve işlev ek açıklamaları gibi).
bir dilin bu özelliğin çalışması için çoklu iş parçacığı uygulaması gerekiyor mu, yoksa diş teknolojisi olmadan yapılabilir mi?
İlginç gerçek: Varsayılan Python uygulaması diş açmayı hiç desteklemez. Global Tercüman Kilidi (GIL) özelliğine sahiptir, bu nedenle Python'un farklı bir örneğini çalıştırmak için ikinci bir işlem başlatmadıysanız aslında hiçbir şey eşzamanlı olarak çalışmaz.
not: örnekler Python 3'te
Verimin Ötesinde
İken yield
anahtar bir jeneratör haline getirmek için herhangi bir işlevde kullanılabilir, bu biri yapmak için tek yol değildir. Python, bir jeneratörü başka bir yinelenebilir (diğer jeneratörler dahil) açısından açıkça ifade etmenin güçlü bir yolu olan Generator Expressions'a sahiptir.
>>> pairs = ((x,y) for x in range(10) for y in range(10) if y >= x)
>>> pairs
<generator object <genexpr> at 0x0311DC90>
>>> sum(x*y for x,y in pairs)
1155
Gördüğünüz gibi, sadece sözdizimi temiz ve okunabilir değil, aynı zamanda sum
kabul üreteçleri gibi yerleşik işlevler de vardır .
İle
İle ifadesi için Python Geliştirme Önerisine bakın . Diğer dillerde bir With ifadesinden beklediğinizden çok farklı. Standart kütüphaneden biraz yardım alarak, Python'un jeneratörleri onlar için bağlam yöneticisi olarak güzel çalışır.
>>> from contextlib import contextmanager
>>> @contextmanager
def debugWith(arg):
print("preprocessing", arg)
yield arg
print("postprocessing", arg)
>>> with debugWith("foobar") as s:
print(s[::-1])
preprocessing foobar
raboof
postprocessing foobar
Tabii ki, şeyleri yazdırmak burada yapabileceğiniz en sıkıcı şeyle ilgilidir, ancak görünür sonuçlar gösterir. Daha ilginç seçenekler, kaynakların otomatik yönetimi (dosyaları açma / kapatma / akışlar / ağ bağlantıları), eşzamanlılık için kilitleme, bir işlevi geçici olarak sarma veya değiştirme ve verileri açma ve yeniden sıkıştırma işlemlerini içerir. Arama işlevleri kodunuza kod enjekte etmeye benziyorsa, ifadelerle birlikte kodunuzun bazı bölümlerini diğer koda sarmak gibidir. Ne kadar kullanırsanız kullanın, bu bir dil yapısına kolay bir kancanın sağlam bir örneğidir. Verim tabanlı üreticiler, içerik yöneticileri oluşturmanın tek yolu değildir, ancak kesinlikle uygun olanlardır.
Kısmi Tükenme
Python'daki döngüler ilginç bir şekilde çalışır. Aşağıdaki biçime sahiptirler:
for <name> in <iterable>:
...
İlk olarak, çağırdığım ifade <iterable>
tekrarlanabilir bir nesne elde etmek için değerlendirilir. İkincisi, yinelenebilir onu __iter__
çağırdı ve ortaya çıkan yineleyici perde arkasında saklandı. Onaltılık, __next__
koyduğunuz isme bağlanacak bir değer elde etmek için yineleyiciye çağrılır <name>
. Çağrısı kadar bu adımı tekrar __next__
bir atar StopIteration
. İstisna, for döngüsü tarafından yutulur ve yürütme oradan devam eder.
Jeneratörlere geri dönme: __iter__
bir jeneratörü çağırdığınızda , sadece kendini döndürür.
>>> x = (a for a in "boring generator")
>>> id(x)
51502272
>>> id(x.__iter__())
51502272
Bunun anlamı, bir şeyi yinelemeyi onunla yapmak istediğiniz şeyden ayırabilir ve bu davranışı ortada değiştirebilirsiniz. Aşağıda, aynı jeneratörün iki döngüde nasıl kullanıldığına dikkat edin ve ikincisinde ilkinden kaldığı yerden çalışmaya başlar.
>>> generator = (x for x in 'more boring stuff')
>>> for letter in generator:
print(ord(letter))
if letter > 'p':
break
109
111
114
>>> for letter in generator:
print(letter)
e
b
o
r
i
n
g
s
t
u
f
f
Tembel Değerlendirme
Jeneratörlere göre listelerle karşılaştırıldığında aşağı taraflardan biri, bir jeneratörde erişebileceğiniz tek şey, çıkan bir sonraki şeydir. Önceki sonuçlarda olduğu gibi geri gidemez veya ara sonuçlardan geçmeden bir sonraki sonuca atlayamazsınız. Bunun üst tarafı, bir jeneratörün eşdeğer listesine kıyasla neredeyse hiç bellek alamayacağıdır.
>>> import sys
>>> sys.getsizeof([x for x in range(10000)])
43816
>>> sys.getsizeof(range(10000000000))
24
>>> sys.getsizeof([x for x in range(10000000000)])
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
sys.getsizeof([x for x in range(10000000000)])
File "<pyshell#10>", line 1, in <listcomp>
sys.getsizeof([x for x in range(10000000000)])
MemoryError
Jeneratörler de tembel olarak zincirlenebilir.
logfile = open("logs.txt")
lastcolumn = (line.split()[-1] for line in logfile)
numericcolumn = (float(x) for x in lastcolumn)
print(sum(numericcolumn))
Birinci, ikinci ve üçüncü satırlar her biri bir jeneratör tanımlar, ancak gerçek bir iş yapmayın. Son satır çağrıldığında, sum sayısal sütunu bir değer için sorar, sayısal sütun sütununun lastcolumn değerine ihtiyacı vardır, lastcolumn logfile'dan bir değer ister, bu da gerçekte dosyadan bir satır okur. Bu yığın, toplam ilk tamsayısını alana kadar gevşer. Ardından, işlem ikinci satır için tekrarlanır. Bu noktada, toplamın iki tamsayısı vardır ve bunları bir araya getirir. Üçüncü satırın henüz dosyadan okunmadığını unutmayın. Sum daha sonra sayısal kolondan (tamamen zincirin geri kalanından habersiz) değer istemeye ve sayısal sütun tükenene kadar bunları eklemeye devam eder.
Buradaki gerçekten ilginç kısım, satırların ayrı ayrı okunması, tüketilmesi ve atılmasıdır. Hiçbir zaman bellekteki tüm dosya bir kerede yok. Bu günlük dosyası bir terabaytsa ne olur? Sadece çalışır, çünkü her seferinde sadece bir satır okur.
Sonuç
Bu, Python'daki jeneratörlerin tüm kullanımlarının tam bir incelemesi değildir. Özellikle, sonsuz jeneratörleri, devlet makinelerini, değerleri geri aktarmayı ve koroutinlerle ilişkilerini atladım.
Temiz bir şekilde entegre edilmiş, kullanışlı bir dil özelliği olarak jeneratörlere sahip olabileceğinizi kanıtlamanın yeterli olduğuna inanıyorum.
yield
aslında bir devlet motorudur. Her seferinde aynı sonucu döndürmek anlamına gelmez. Ne olacak mutlak bir kesinlikle yapmak bir enumerable içinde onu çağıran her zaman bir sonraki öğeyi döndürmek olduğunu. İplikler gerekli değildir; mevcut durumu korumak için bir kapatma işlemine (az ya da çok) ihtiyacınız vardır.