Python: üretici ifadesi ile getiri karşılaştırması


90

Python'da, bir üretici ifadesi aracılığıyla bir üretici nesnesi oluşturmakla getiri ifadesini kullanmak arasında herhangi bir fark var mı?

Verim kullanarak :

def Generator(x, y):
    for i in xrange(x):
        for j in xrange(y):
            yield(i, j)

Oluşturucu ifadesini kullanma :

def Generator(x, y):
    return ((i, j) for i in xrange(x) for j in xrange(y))

Her iki işlev de tuple üreten jeneratör nesnelerini döndürür, örneğin (0,0), (0,1) vb.

Birinin veya diğerinin herhangi bir avantajı var mı? Düşünceler?


Herkese teşekkürler! Bu cevaplarda çok sayıda harika bilgi ve daha fazla referans var!


2
En okunabilir bulduğunuzu seçin.
user238424

Yanıtlar:


74

İkisinde sadece küçük farklılıklar var. disBu tür şeyleri kendiniz incelemek için modülü kullanabilirsiniz .

Düzenle: İlk sürümüm, etkileşimli bilgi isteminde modül kapsamında oluşturulan oluşturucu ifadesini yeniden derledi. Bu, bir işlevin içinde kullanıldığı için OP'nin sürümünden biraz farklıdır. Bunu sorudaki gerçek durumla eşleşecek şekilde değiştirdim.

Aşağıda görebileceğiniz gibi, "verim" üretecinin (ilk durum) kurulumda üç ekstra talimatı vardır, ancak FOR_ITERilkinden yalnızca bir açıdan farklılık gösterirler: "verim" yaklaşımı , döngü içinde LOAD_FASTbir yerine a kullanır LOAD_DEREF. LOAD_DEREFBir "yerine daha yavaş" daha LOAD_FASTbiraz daha hızlı yeterince büyük değerler için jeneratör ifade fazla "Verim" versiyonu yapar, bu nedenle x(dış döngü) için değeri, ybiraz daha hızlı her geçişte yüklenir. Daha küçük değerler xiçin, kurulum kodunun fazladan yükü nedeniyle biraz daha yavaş olacaktır.

Ayrıca, üreteç ifadesinin genellikle kodda, onu böyle bir işlevle sarmalamak yerine satır içi olarak kullanılacağına işaret etmeye değer olabilir. Bu, kurulum ek yükünün bir kısmını ortadan kaldırır LOAD_FASTve "verim" versiyonuna başka türlü bir avantaj sağlasa bile, daha küçük döngü değerleri için jeneratör ifadesini biraz daha hızlı tutar .

Her iki durumda da performans farkı, biri veya diğeri arasında karar vermeyi haklı çıkarmaya yetmeyecektir. Okunabilirlik çok daha önemlidir, bu nedenle elinizdeki durum için en okunaklı olanı kullanın.

>>> 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

Kabul edildi - dis kullanarak farkın ayrıntılı açıklaması için. Teşekkürler!
cschol

LOAD_DEREF"Oldukça yavaş" olduğunu iddia eden bir kaynağa bağlantı eklemek için güncelledim , bu nedenle performans gerçekten önemliyse gerçek zamanlama timeitiyi olurdu. Teorik bir analiz ancak şimdiye kadar gider.
Peter Hansen

36

Bu örnekte, gerçekten değil. Ancak yielddaha karmaşık yapılar için kullanılabilir - örneğin , arayanın değerlerini de kabul edebilir ve sonuç olarak akışı değiştirebilir. Daha fazla ayrıntı için PEP 342'yi okuyun (bilmeye değer ilginç bir tekniktir).

Her neyse, en iyi tavsiye, ihtiyaçlarınız için daha net olanı kullanmaktır .

Not: Dave Beazley'den basit bir eşgüdüm örneği :

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!")

8
David Beazley'e bağlantı için +1. Onun coroutines sunumu, uzun zamandır okuduğum en akıl almaz şey. Jeneratörlerle ilgili sunumu kadar yararlı değil, belki ama yine de harika.
Robert Rossney

18

Bir üreteç ifadesine sığdırabileceğiniz basit döngü türleri arasında hiçbir fark yoktur. Ancak verim, çok daha karmaşık işlemler yapan üreticiler oluşturmak için kullanılabilir. Fibonacci dizisini oluşturmak için basit bir örnek:

>>> 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]

5
Süper havalı +1 ... Özyineleme olmadan bu kadar kısa ve tatlı bir fib uygulaması gördüğümü söyleyemem.
JudoWill

Aldatıcı derecede basit kod parçacığı - Fibonacci'nin onu görmekten mutlu olacağını düşünüyorum !!
user-asterix

10

Kullanımda, bir jeneratör nesnesi ile bir jeneratör işlevi arasındaki bir ayrıma dikkat edin.

Bir jeneratör nesnesi, yeni bir jeneratör nesnesi döndürdüğü için onu her çağırdığınızda yeniden kullanılabilen bir jeneratör işlevinin tersine yalnızca bir kez kullanılır.

Jeneratör ifadeleri pratikte genellikle "ham" olarak kullanılırlar, onları bir fonksiyona sarmadan ve bir jeneratör nesnesi döndürürler.

Örneğin:

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()))

hangi çıktılar:

[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]

Biraz farklı bir kullanımla karşılaştırın:

range_10_gen = range_10_gen_func()
print(list(range_10_gen))
print(list(range_10_gen))
print(list(range_10_gen))

hangi çıktılar:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
[]

Ve bir üreteç ifadesiyle karşılaştırın:

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))

aynı zamanda çıktı:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
[]

8

yieldİfade yalnızca iç içe döngülerden daha karmaşıksa kullanmak güzeldir. Diğer şeylerin yanı sıra, özel bir ilk veya özel son değeri döndürebilirsiniz. Düşünmek:

def Generator(x):
  for i in xrange(x):
    yield(i)
  yield(None)

5

Yineleyiciler hakkında düşünürken itertoolsmodül:

... kendi başlarına veya kombinasyon halinde yararlı olan temel bir hızlı, bellek verimli araçlar kümesini standartlaştırır. Birlikte, saf Python'da özel araçları özlü ve verimli bir şekilde oluşturmayı mümkün kılan bir "yineleyici cebir" oluştururlar.

Performans için düşünün itertools.product(*iterables[, repeat])

Girdi yinelemelerinin kartezyen çarpımı.

Oluşturucu ifadesinde yuvalanmış for-döngülere eşdeğerdir. Örneğin product(A, B), aynı şeyi döndürür ((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)]
>>> 

4

Evet bir fark var.

Jeneratör ifade için (x for var in expr), iter(expr)ifade edildiğinde denir yarattı .

Bir jeneratör oluşturmak için defve kullanırken yield, aşağıdaki gibi:

def my_generator():
    for var in expr:
        yield x

g = my_generator()

iter(expr)henüz aranmadı. Yalnızca yinelendiğinde çağrılacaktır g(ve hiç çağrılmayabilir).

Bu yineleyiciyi örnek olarak alırsak:

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

Bu kod:

g1 = (i ** 2 for i in CountDown(3))  # immediately prints "ITER"
print("Go!")
for x in g1:
    print(x)

süre:

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)

Çoğu yineleyici içinde çok fazla şey yapmadığından __iter__, bu davranışı gözden kaçırmak kolaydır. Gerçek bir dünya örneği QuerySet, verileri içeri alan__iter__ ve data = (f(x) for x in qs)çok zaman alabilen def g(): for x in qs: yield f(x)Django'dur.data=g() hemen geri dönen Django'dur.

Daha fazla bilgi ve resmi tanım için PEP 289 - Jeneratör İfadelerine bakın .


0

Henüz belirtilmeyen bazı bağlamlarda önemli olabilecek bir fark var. Kullanımı , dolaylı olarak StopIteration'ı (ve ilgili şeyleri düzeltmekten) başka bir şey için kullanmanızı yieldengeller.return .

Bu, bu kodun biçimsiz olduğu anlamına gelir (ve onu bir tercümana vermek size bir bilgi verecektir 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)

Öte yandan, bu kod bir cazibe gibi çalışır:

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)
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.