Jeneratör İfadeleri ve Liste Anlama


411

Jeneratör ifadelerini ne zaman ve Python'da liste kavrayışlarını ne zaman kullanmalısınız?

# Generator expression
(x*2 for x in range(256))

# List comprehension
[x*2 for x in range(256)]

27
[exp for x in iter]sadece şeker olabilir list((exp for x in iter))mi? ya da bir yürütme farkı var mı?
b0fh

1
ilgili bir sorum olduğunu düşünüyorum, bu yüzden verim kullanırken bir fonksiyondan sadece jeneratör ifadesini kullanabilir miyiz veya jeneratör nesnesini döndürmek için bir fonksiyon için verim mi kullanmalıyız?

28
@ b0fh Yorumunuza çok geç cevap: Python2'de küçük bir fark var, bir döngü ifadesi bir liste kavramasından dışarı çıkarken bir jeneratör ifadesi sızmayacak. Karşılaştırma X = [x**2 for x in range(5)]; print xile Y = list(y**2 for y in range(5)); print y, ikinci bir hata verecektir. Python3'te, bir liste kavraması gerçekten list()beklediğiniz gibi beslenen bir jeneratör ifadesi için sözdizimsel şekerdir , bu nedenle döngü değişkeni artık dışarı çıkmaz .
Bas Swinckels

12
PEP 0289'u okumanızı öneririm . Tarafından özetlenebilir "yüksek performans gibi bu PEP tanıtır jeneratör ifadeleri, liste comprehensions ve jeneratörlerinin bellek verimli genelleme" . Bunların ne zaman kullanılacağına dair faydalı örnekleri de vardır.
icc97

5
@ icc97 Ben de partiye sekiz yıl geç kaldım ve PEP bağlantısı mükemmeldi. Bunu bulmayı kolaylaştırdığın için teşekkürler!
eenblam

Yanıtlar:


283

John'un cevabı iyidir (bir şeyi birden çok kez yinelemek istediğinizde liste kavrayışları daha iyidir). Ancak, liste yöntemlerinden herhangi birini kullanmak isterseniz bir liste kullanmanız gerektiğini de belirtmek gerekir. Örneğin, aşağıdaki kod çalışmaz:

def gen():
    return (something for something in get_some_stuff())

print gen()[:2]     # generators don't support indexing or slicing
print [5,6] + gen() # generators can't be added to lists

Temel olarak, yaptığınız tek şey bir kez yineleniyorsa bir jeneratör ifadesi kullanın. Oluşturulan sonuçları saklamak ve kullanmak istiyorsanız, muhtemelen bir liste kavrayışıyla daha iyi durumdasınızdır.

Performans birini diğerine tercih etmenin en yaygın nedeni olduğundan, tavsiyem bunun için endişelenmemek ve sadece birini seçmek; Programınızın çok yavaş çalıştığını görürseniz, ancak o zaman geri dönüp kodunuzu ayarlama konusunda endişelenmeniz gerekir.


70
Bazen jeneratörleri kullanmanız gerekir - örneğin, verim kullanarak işbirliği programlaması ile birlikte programlar yazıyorsanız. Ama bunu yapıyorsanız, muhtemelen bu soruyu sormuyorsunuz;)
ephemient

12
Bunun eski olduğunu biliyorum, ancak jeneratörlerin (ve herhangi bir yinelenebilir) listelerle genişletilebileceğini belirtmeye değer: a = [1, 2, 3] b = [4, 5, 6] a.extend(b)- şimdi [1, 2, 3, 4, 5, 6] olacak. (Yorumlara satırsonu ekleyebilir misiniz ??)
jarvisteve

12
@jarvisteve örneğiniz söylediğiniz kelimelere inanıyor. Burada da güzel bir nokta var. Listeler jeneratörlerle genişletilebilir, ancak daha sonra bir jeneratör oluşturmanın bir anlamı yoktu. Jeneratörler listelerle genişletilemez ve jeneratörler tam olarak tekrarlanamaz. a = (x for x in range(0,10)), b = [1,2,3]Örneğin. a.extend(b)bir istisna atar. b.extend(a)a'nın tümünü değerlendirecek, bu durumda ilk etapta bir jeneratör yapmanın bir anlamı yok.
Slater Victoroff

4
@SlaterTyranus% 100 haklısınız ve doğruluk için sizi oyladım. Yine de, yorumunun OP'nin sorusuna yararlı bir cevap olmadığını düşünüyorum, çünkü kendilerini burada bulanlara yardımcı olacak çünkü bir arama motoruna 'jeneratörü liste kavrama ile birleştirin' gibi bir şey yazdılar.
rbp

1
Bir defada yineleme yapmak için bir jeneratör kullanmanın nedeni (örneğin , bellek eksikliğiyle ilgili endişem, "değerleri birer birer getirme" konusundaki endişemi geçersiz kılar ), muhtemelen birden çok kez yineleme yaparken hala geçerli olmaz mı? Bir listeyi daha kullanışlı hale getirebileceğini söyleyebilirim, ancak bunun bellek endişelerini aşmak için yeterli olup olmadığı başka bir şey.
Rob Grant

181

Jeneratör ifadesi veya liste kavrayışı üzerinde yineleme yapmak da aynı şeyi yapacaktır. Bununla birlikte, liste kavrama ilk olarak tüm listeyi hafızada yaratırken, jeneratör ifadesi anında öğeleri yaratır, böylece onu çok büyük (ve aynı zamanda sonsuz!) Diziler için kullanabilirsiniz.


39
Sonsuz için +1. Performansı ne kadar az önemsediğinize bakılmaksızın bunu bir listeyle yapamazsınız.
Paul Draper

Anlama yöntemini kullanarak sonsuz jeneratörler oluşturabilir misiniz?
AnnanFay

5
@Annan Yalnızca başka bir sonsuz jeneratöre erişiminiz varsa. Örneğin, itertools.count(n)n den başlayarak tam sonsuz bir dizi, yani (2 ** item for item in itertools.count(n))yetkilerinin sonsuz bir dizi olacaktır 2başlayan 2 ** n.
Kevin

2
Bir jeneratör, yinelendikten sonra öğeleri bellekten siler. Örneğin, görüntülemek istediğiniz büyük verileriniz varsa hızlıdır. Bu bir bellek domuzu değil. jeneratörler ile öğeler 'gerektiği gibi' işlenir. listeye asmak veya tekrarlamak isterseniz (öğeleri saklayın) sonra liste kavrayışı kullanın.
j2emanue

102

Sonucun birden çok kez yinelenmesi gerektiğinde veya hızın çok önemli olduğu durumlarda liste kavramalarını kullanın. Aralık geniş veya sonsuz olduğunda jeneratör ifadeleri kullanın.

Daha fazla bilgi için Jeneratör ifadeleri ve liste kavramalarına bakın.


2
Bu muhtemelen biraz konu dışı olacak, ancak maalesef "geri alınamaz" ... Bu bağlamda "en önemli" ne anlama gelir? Ben anadili İngilizce değilim ... :)
Guillermo Ares

6
@GuillermoAres bu, en önemli
Sнаđошƒаӽ

1
Peki ifadelerden listsdaha hızlı generatormı? DF'nin cevabını okurken, bunun tam tersi olduğu ortaya çıktı.
Hassan Baig

1
Menzil küçükken liste kavrayışlarının daha hızlı olduğunu söylemek daha iyidir, ancak ölçek arttıkça, değerleri kullanım sırasında tam zamanında hesaplamak daha değerli hale gelir. Bir jeneratör ifadesi bunu yapar.
Kyle

59

Önemli olan, liste kavrayışının yeni bir liste oluşturmasıdır. Jeneratör, bitleri tüketirken kaynak materyali anında "filtreleyecek" yinelenebilir bir nesne oluşturur.

"Hugefile.txt" adlı bir 2TB günlük dosyanız olduğunu ve "ENTRY" kelimesiyle başlayan tüm satırların içerik ve uzunluğunu istediğinizi düşünün.

Yani bir liste kavrayışı yazarak başlamayı deneyin:

logfile = open("hugefile.txt","r")
entry_lines = [(line,len(line)) for line in logfile if line.startswith("ENTRY")]

Bu, tüm dosyayı karıştırır, her satırı işler ve eşleşen satırları dizinizde saklar. Bu nedenle bu dizi en fazla 2 TB içerik içerebilir. Bu çok fazla RAM ve muhtemelen amaçlarınız için pratik değil.

Bunun yerine içeriğimize "filtre" uygulamak için bir jeneratör kullanabiliriz. Sonuç üzerinde yineleme yapmaya başlayana kadar hiçbir veri okunmaz.

logfile = open("hugefile.txt","r")
entry_lines = ((line,len(line)) for line in logfile if line.startswith("ENTRY"))

Dosyamızdan henüz tek bir satır okunmadı. Aslında, sonucumuzu daha da fazla filtrelemek istediğimizi düşünelim:

long_entries = ((line,length) for (line,length) in entry_lines if length > 80)

Hala hiçbir şey okunmadı, ancak şimdi verilerimize istediğimiz gibi davranacak iki jeneratör belirledik.

Filtrelenmiş satırlarımızı başka bir dosyaya yazalım:

outfile = open("filtered.txt","a")
for entry,length in long_entries:
    outfile.write(entry)

Şimdi girdi dosyasını okuyoruz. Bizim gibi fordöngü ek satırlar talep devam ediyor long_entriesjeneratör gelen satırları talep entry_linesuzunluğu 80 karakterden daha büyük olduğu sadece bu dönen, jeneratör. Ve sırayla, entry_linesjeneratör logfileyineleyiciden satırlar (belirtildiği gibi filtrelenir) ister ve bu da dosyayı okur.

Dolayısıyla, verileri çıktı işlevinize tam olarak doldurulmuş bir liste biçiminde "göndermek" yerine, çıktı işlevine verileri yalnızca gerektiğinde "çekmek" için bir yol sağlarsınız. Bu bizim durumumuzda çok daha verimli, ama o kadar esnek değil. Jeneratörler tek yönlü, tek geçişli; okuduğumuz günlük dosyasındaki veriler hemen atılır, bu nedenle önceki satıra geri dönemeyiz. Öte yandan, işimiz bittiğinde verileri saklamak konusunda endişelenmemiz gerekmiyor.


46

Bir jeneratör ifadesinin yararı, tüm listeyi aynı anda oluşturmadığı için daha az bellek kullanmasıdır. Oluşturucu ifadeleri en iyi şekilde, sonuçları toplamak veya sonuçlardan bir diksiyon oluşturmak gibi bir aracı olduğunda kullanılır.

Örneğin:

sum(x*2 for x in xrange(256))

dict( (k, some_func(k)) for k in some_list_of_keys )

Avantajı, listenin tamamen oluşturulmamış olması ve bu nedenle çok az bellek kullanılması (ve ayrıca daha hızlı olması)

Bununla birlikte, istenen nihai ürün bir liste olduğunda liste kavrayışlarını kullanmalısınız. Oluşturulan listeyi istediğiniz için oluşturucu ifadelerini kullanarak herhangi bir not kaydetmeyeceksiniz. Ayrıca, sıralanmış veya ters çevrilmiş gibi liste işlevlerinden herhangi birini kullanabilme avantajından da yararlanabilirsiniz.

Örneğin:

reversed( [x*2 for x in xrange(256)] )

9
Sizin için dilde, jeneratör ifadelerinin bu şekilde kullanılması gerektiğine dair bir ipucu var. Parantezleri gevşetin! sum(x*2 for x in xrange(256))
u0b34a0f6ae

8
sortedve reverseddahil edilebilir herhangi bir yinelenebilir, jeneratör ifadeleri üzerinde iyi çalışır.
marr75

1
2.7 ve üzeri bir sürüm kullanabiliyorsanız, bu dict () örneği bir dict kavraması olarak daha iyi görünür (bunun için PEP, jeneratör ifadelerinden PEP'den daha eski, ancak inmesi daha uzun sürdü)
Jürgen A. Erhard

14

Değişken bir nesneden (liste gibi) bir jeneratör oluştururken jeneratörün, jeneratörün oluşturulması sırasında değil jeneratörü kullanırken listenin durumu hakkında değerlendirileceğini unutmayın:

>>> mylist = ["a", "b", "c"]
>>> gen = (elem + "1" for elem in mylist)
>>> mylist.clear()
>>> for x in gen: print (x)
# nothing

Listenizin değiştirilme şansı varsa (veya bu liste içinde değiştirilebilir bir nesne), ancak jeneratörün oluşturulmasında duruma ihtiyacınız varsa, bunun yerine bir liste kavrama kullanmanız gerekir.


1
Ve bu kabul edilen cevap olmalı. Verileriniz kullanılabilir bellekten daha büyükse, bellekteki liste üzerinde döngü yapmak daha hızlı olsa da, her zaman jeneratörleri kullanmalısınız (ancak bunu yapmak için yeterli belleğiniz yoktur).
Marek Marczak

4

Bazen tee işlevi ile itertools'tan uzaklaşabilirsiniz , aynı jeneratör için bağımsız olarak kullanılabilecek birden fazla yineleyici döndürür.


4

Ben kullanıyorum Hadoop kıymalı modülü . Bu not almak için harika bir örnek olduğunu düşünüyorum:

import mincemeat

def mapfn(k,v):
    for w in v:
        yield 'sum',w
        #yield 'count',1


def reducefn(k,v): 
    r1=sum(v)
    r2=len(v)
    print r2
    m=r1/r2
    std=0
    for i in range(r2):
       std+=pow(abs(v[i]-m),2)  
    res=pow((std/r2),0.5)
    return r1,r2,res

Burada jeneratör bir metin dosyasından sayıları alır (15 GB kadar büyük) ve Hadoop'un harita küçültme özelliğini kullanarak bu sayılara basit matematik uygular. Eğer verim fonksiyonunu kullanmamış olsaydım, bir liste kavrayışını kullansaydım, toplamları ve ortalamayı (uzay karmaşıklığından bahsetmemek) hesaplamak çok daha uzun zaman alacaktı.

Hadoop, Jeneratörlerin tüm avantajlarını kullanmak için harika bir örnektir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.