“Getiri” anahtar kelimesi ne işe yarar?


10193

Kullanımı nedir yieldPython'da anahtar kelimenin ve ne işe ?

Örneğin, bu kod 1 anlamaya çalışıyorum :

def _get_child_candidates(self, distance, min_dist, max_dist):
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild  

Ve bu arayan:

result, candidates = [], [self]
while candidates:
    node = candidates.pop()
    distance = node._get_dist(obj)
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

Yöntem _get_child_candidatesçağrıldığında ne olur ? Liste iade edildi mi? Tek bir unsur mu? Tekrar arandı mı? Sonraki aramalar ne zaman duracak?


1. Bu kod parçası metrik alanlar için harika bir Python kütüphanesi yapan Jochen Schulz (jrschulz) tarafından yazılmıştır. Bu kaynağın tamamı için link: Module mspace .

Yanıtlar:


14644

Ne yieldyaptığını anlamak için jeneratörlerin ne olduğunu anlamalısınız . Ve jeneratörleri anlayabilmeniz için, tekrarlanabilirleri anlamalısınız .

Iterables

Bir liste oluşturduğunuzda, öğelerini tek tek okuyabilirsiniz. Öğelerini tek tek okumaya yineleme denir:

>>> mylist = [1, 2, 3]
>>> for i in mylist:
...    print(i)
1
2
3

mylistbir yinelenebilir . Bir liste kavrayışı kullandığınızda, bir liste oluşturur ve böylece yinelenebilir:

>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
...    print(i)
0
1
4

" for... in..." Üzerinde kullanabileceğiniz her şey yinelenebilir; lists,, stringsdosyalar ...

Bu tekrarlamalar kullanışlıdır, çünkü onları istediğiniz kadar okuyabilirsiniz, ancak tüm değerleri hafızada saklarsınız ve çok fazla değeriniz olduğunda bu her zaman istediğiniz şey değildir.

Jeneratörler

Jeneratörler yineleyicilerdir, sadece bir kez yineleyebileceğiniz bir çeşit yinelenebilir . Jeneratörler tüm değerleri hafızada depolamaz, değerleri anında üretir :

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...    print(i)
0
1
4

Bunun ()yerine kullandığınız dışında sadece aynı []. ANCAK, sen olamaz gerçekleştirmekfor i in mygenerator onlar 0 hesaplamak, sonra bunu unutur ve 1 hesaplamak ve teker 4, tek hesaplama sona: jeneratörler sadece kullanılabilir beri kez ikinci kez.

Yol ver

yieldreturnişlevi bir jeneratör döndürecek hariç, gibi kullanılan bir anahtar kelimedir .

>>> def createGenerator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

Burada işe yaramaz bir örnek, ancak işlevinizin yalnızca bir kez okumanız gereken büyük bir değer kümesi döndüreceğini bildiğinizde kullanışlıdır.

Master olarak yield, işlevi çağırdığınızda, işlev gövdesinde yazdığınız kodun çalışmadığını anlamalısınız . Fonksiyon sadece jeneratör nesnesini döndürür, bu biraz zor :-)

Ardından, kodunuz her seferinde kaldığı yerden devam eder for jeneratörü kullandığında .

Şimdi zor kısmı:

forFonksiyonunuzdan oluşturulan jeneratör nesnesini ilk kez çağırdığında, fonksiyonunuzdaki kodu baştan sona kadar çalıştıracak yield, daha sonra döngünün ilk değerini döndürecektir. Ardından, sonraki her çağrı, işlevde yazdığınız döngünün başka bir yinelemesini çalıştırır ve bir sonraki değeri döndürür. Bu, jeneratör boş olarak kabul edilene kadar devam eder, bu da işlev çarpmadan çalışır yield. Bunun nedeni, döngünün sona ermesi veya artık bir "if/else".


Kodunuz açıklandı

Jeneratör:

# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):

    # Here is the code that will be called each time you use the generator object:

    # If there is still a child of the node object on its left
    # AND if the distance is ok, return the next child
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild

    # If there is still a child of the node object on its right
    # AND if the distance is ok, return the next child
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild

    # If the function arrives here, the generator will be considered empty
    # there is no more than two values: the left and the right children

Arayan:

# Create an empty list and a list with the current object reference
result, candidates = list(), [self]

# Loop on candidates (they contain only one element at the beginning)
while candidates:

    # Get the last candidate and remove it from the list
    node = candidates.pop()

    # Get the distance between obj and the candidate
    distance = node._get_dist(obj)

    # If distance is ok, then you can fill the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    # Add the children of the candidate in the candidate's list
    # so the loop will keep running until it will have looked
    # at all the children of the children of the children, etc. of the candidate
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

return result

Bu kod birkaç akıllı parça içerir:

  • Döngü bir liste üzerinde yinelenir, ancak döngü yinelenirken liste genişler :-) Sonsuz bir döngü ile sonuçlanabileceğiniz için biraz tehlikeli olsa bile tüm bu iç içe geçmiş verileri gözden geçirmek için özlü bir yoldur. Bu durumda, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))jeneratörün tüm değerlerini boşaltın, ancak whileaynı düğüme uygulanmadığı için öncekilerden farklı değerler üretecek yeni jeneratör nesneleri oluşturmaya devam ediyor.

  • extend()Yöntem, bir iterable bekler ve listeye değerlerini ekleyen bir liste nesnesinin bir yöntemdir.

Genellikle bir liste geçiririz:

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

Ancak kodunuzda, bir jeneratör alır, bu iyidir:

  1. Değerleri iki kez okumak zorunda değilsiniz.
  2. Çok fazla çocuğunuz olabilir ve hepsinin hafızada saklanmasını istemezsiniz.

Ve çalışır çünkü Python bir yöntemin argümanının bir liste olup olmadığını umursamaz. Python yinelemeleri bekler, böylece dizeler, listeler, tuples ve jeneratörlerle çalışır! Buna ördek yazma denir ve Python'un bu kadar havalı olmasının nedenlerinden biridir. Ama bu başka bir hikaye, başka bir soru için ...

Burada durdurabilir veya bir jeneratörün gelişmiş kullanımını görmek için biraz okuyabilirsiniz:

Jeneratör tükenmesini kontrol etme

>>> class Bank(): # Let's create a bank, building ATMs
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
...    print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

Not: Python 3 için print(corner_street_atm.__next__())veyaprint(next(corner_street_atm))

Bir kaynağa erişimi kontrol etmek gibi çeşitli şeyler için yararlı olabilir.

Itertools, en iyi arkadaşın

İtertools modülü, yinelenebilirleri işlemek için özel işlevler içerir. Hiç bir jeneratörü çoğaltmak istediniz mi? İki jeneratör zinciri mi? Yuvalanmış bir listede değerleri bir astarla gruplandır? Map / Zipbaşka bir liste oluşturmadan?

O zaman sadece import itertools.

Bir örnek? Dört atlı bir yarış için olası varış emirlerini görelim:

>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
 (1, 2, 4, 3),
 (1, 3, 2, 4),
 (1, 3, 4, 2),
 (1, 4, 2, 3),
 (1, 4, 3, 2),
 (2, 1, 3, 4),
 (2, 1, 4, 3),
 (2, 3, 1, 4),
 (2, 3, 4, 1),
 (2, 4, 1, 3),
 (2, 4, 3, 1),
 (3, 1, 2, 4),
 (3, 1, 4, 2),
 (3, 2, 1, 4),
 (3, 2, 4, 1),
 (3, 4, 1, 2),
 (3, 4, 2, 1),
 (4, 1, 2, 3),
 (4, 1, 3, 2),
 (4, 2, 1, 3),
 (4, 2, 3, 1),
 (4, 3, 1, 2),
 (4, 3, 2, 1)]

İterasyonun iç mekanizmalarını anlama

Yineleme, yinelemeleri ( __iter__()yöntemi uygulama ) ve yinelemeleri ( yöntemi uygulama ) ima eden bir süreçtir __next__(). Yinelemeler, yineleyici alabileceğiniz herhangi bir nesnedir. Yineleyiciler yinelemeleri tekrarlamanıza izin veren nesnelerdir.

Bu makalede döngülerin nasıl forçalıştığı hakkında daha fazla bilgi var .


355
yieldbu cevabın önerdiği kadar büyülü değil. Herhangi bir yieldyerde ifade içeren bir işlevi çağırdığınızda , bir jeneratör nesnesi alırsınız, ancak kod çalışmaz . Daha sonra bir nesneyi jeneratörden her çıkardığınızda, Python bir yielddeyime gelinceye kadar işlevdeki kodu yürütür , sonra nesneyi duraklatır ve teslim eder. Başka bir nesneyi çıkardığınızda, Python nesneden hemen sonra devam eder yieldve başka bir nesneye ulaşana kadar devam eder yield(genellikle aynı, ancak bir yineleme daha sonra). Bu, fonksiyon bitene kadar devam eder, bu noktada jeneratör tükenmiş kabul edilir.
Matthias Fripp

29
"Bu yinelemeler kullanışlıdır ... ancak tüm değerleri hafızada saklarsınız ve bu her zaman istediğiniz şey değildir", yanlış veya kafa karıştırıcıdır. İterable üzerinde ITER () çağrı üzerine bir yineleyici ve bir yineleyici her uygulanmasına bağlı olarak, bellekteki değerlerini saklamak zorunda değildir bir iterable döner iter yöntemi, aynı zamanda isteğe bağlı olarak sırayla değerleri üretir.
picmate 涅

Eklemek iyi olurdu harika neden cevap Size kullanılan dışında sadece aynıdır ()yerine[] özellikle ne ()olduğunu (Tuple ile karışıklık olabilir).
WoJ

Yanlış olabilirim, ama bir jeneratör yineleyici değil, "jeneratör" denilen bir yineleyici.
aderchox

2006

Anlamanın kısayolu yield

yieldİfadeleri olan bir işlev gördüğünüzde , ne olacağını anlamak için bu kolay hileyi uygulayın:

  1. result = []İşlevin başına bir satır ekleyin .
  2. Her değiştirin yield exprile result.append(expr).
  3. return resultİşlevin altına bir satır ekleyin .
  4. Yay - başka yieldifade yok ! Kodu okuyun ve anlayın.
  5. İşlevi orijinal tanımla karşılaştırın.

Bu hile size işlevin arkasındaki mantık hakkında bir fikir verebilir, ancak gerçekte olan şey yieldlisteye dayalı yaklaşımda olanlardan önemli ölçüde farklıdır. Çoğu durumda, verim yaklaşımı bellekte çok daha verimli ve daha hızlı olacaktır. Diğer durumlarda, orijinal işlev gayet iyi çalışıyor olsa da, bu hile sizi sonsuz bir döngüde sıkıştıracaktır. Daha fazla bilgi için okumaya devam edin ...

Yinelemelerinizi, Yineleyicilerinizi ve Jeneratörlerinizi karıştırmayın

İlk olarak, yineleyici protokolü - yazdığınızda

for x in mylist:
    ...loop body...

Python aşağıdaki iki adımı gerçekleştirir:

  1. Aşağıdakiler için bir yineleyici alır mylist:

    Çağrı iter(mylist)-> bu next()yöntemle (veya __next__()Python 3'te) bir nesne döndürür .

    [Bu, çoğu insanın size anlatmayı unuttuğu adımdır]

  2. Öğeler arasında döngü yapmak için yineleyiciyi kullanır:

    next()1. adımdan döndürülen yineleyicideki yöntemi çağırmaya devam edin .next() atanır xve döngü gövdesi yürütülür. Bir istisna StopIterationiçeriden yükseltilirse next(), yineleyicide artık değer olmadığı ve döngüden çıkıldığı anlamına gelir.

Gerçek şu ki, Python istediği zaman yukarıdaki iki adımı gerçekleştirir bir nesnenin içeriği üzerinde döngü - bu nedenle bir for döngüsü olabilir, ancak kod gibi de olabilir otherlist.extend(mylist)( otherlistPython listesinin nerede olduğu).

Burada mylistbir yinelenebilir çünkü yineleyici protokolünü uygular. Kullanıcı tanımlı bir sınıfta, sınıfınızın __iter__()örneklerini yinelenebilir hale getirmek için yöntemi uygulayabilirsiniz. Bu yöntem bir yineleyici döndürmelidir . Yineleyici,next() yöntemi . Hem uygulamak mümkündür __iter__()ve next()aynı sınıfına ve sahip __iter__()dönüşü self. Bu, basit durumlar için işe yarar, ancak iki yineleyicinin aynı nesne üzerinde aynı anda döngü yapmasını istediğinizde çalışmaz.

Bu yineleyici protokolü, birçok nesne bu protokolü uyguluyor:

  1. Yerleşik listeler, sözlükler, tuples, kümeler, dosyalar.
  2. Uygulayan kullanıcı tanımlı sınıflar __iter__() .
  3. Jeneratörler.

Bir fordöngünün ne tür bir nesne ile uğraştığını bilmediğini unutmayın - sadece yineleyici protokolünü izler ve öğeden sonra öğeyi çağırırken mutlu olur next(). Dahili listeleri iade onların ürün birini teker, sözlükler dönmek tuşları teker teker dosyaları iade hatları en iyi nerede olduğunu ... vs teker teker Ve jeneratörler dönmek yieldgelir:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

yieldDeyimler yerine, yalnızca ilkinde üç returndeyim f123()olsaydı çalıştırılır ve işlevden çıkar. Ancak f123()sıradan bir işlev değildir. Arandığında f123(), getiri ifadelerindeki değerlerin hiçbirini döndürmez! Bir jeneratör nesnesi döndürür. Ayrıca, işlev gerçekten çıkmaz - askıya alınmış bir duruma geçer. Zaman fordöngü jeneratör nesne üzerine döngü çalışır, fonksiyon sonra ertesi çizgisinde asılı konumundan itibaren devam eder yield, daha önce dönen, bu durumda, a, sonraki kod satır yürütür yieldaçıklamada ve döner bir sonraki olarak öğe. Bu, fonksiyon çıkana kadar üreteç yükselir StopIterationve döngü çıkana kadar olur .

Yani jeneratör nesne sıralama bir adaptör gibi biridir - bu teşhir ederek, yineleyici protokolünü sergileyen bir ucunda __iter__()venext()for döngüyü mutlu etmek için yöntemleri yöntemleri göstererek . Bununla birlikte, diğer uçta, işlevi bir sonraki değeri ondan alacak kadar çalışır ve askıya alınmış moda geri döndürür.

Jeneratörler Neden Kullanılmalı?

Genellikle, jeneratörleri kullanmayan ancak aynı mantığı uygulayan bir kod yazabilirsiniz. Seçeneklerden biri, daha önce bahsettiğim geçici 'hile' listesini kullanmaktır. Bu, her durumda işe yaramaz, örneğin sonsuz döngüleriniz varsa veya gerçekten uzun bir listeniz olduğunda hafızayı verimsiz kullanabilir. Diğer yaklaşım, durumu örneğin üyelerde tutan ve onun next()(veya __next__()Python 3) yönteminde bir sonraki mantıksal adımı gerçekleştiren yeni bir yinelenebilir sınıf SomethingIter uygulamaktır . Mantığa bağlı olarak, next()yöntemin içindeki kod çok karmaşık görünebilir ve hatalara eğilimli olabilir. Burada jeneratörler temiz ve kolay bir çözüm sunar.


20
"Verim ifadeleriyle bir işlev gördüğünüzde, ne olacağını anlamak için bu kolay hileyi uygulayın" Bu send, jeneratörlerin büyük bir parçası olan bir jeneratöre girebileceğiniz gerçeğini tamamen görmezden gelmiyor mu ?
DanielSank

10
"bir for döngüsü olabilir, ama aynı zamanda kod da olabilir otherlist.extend(mylist)" -> Bu yanlış. extend()listeyi yerinde değiştirir ve yinelenebilir bir değer döndürmez. Üzerinde döngü yapmaya çalışmak örtük olarak döndürülür çünkü otherlist.extend(mylist)başarısız olur ve döngü yapamazsınız . TypeErrorextend()NoneNone
Pedro

4
@pedro Bu cümleyi yanlış anlamışsınızdır. Bu, python'un yürütme sırasında belirtilen iki adımı mylist(açık değil otherlist) gerçekleştirdiği anlamına gelir otherlist.extend(mylist).
bugün

555

Bu şekilde düşün:

Yineleyici, next()yöntemi olan bir nesne için sadece süslü bir sondaj terimidir . Böylece, verim fonksiyonu şöyle olur:

Orijinal versiyon:

def some_function():
    for i in xrange(4):
        yield i

for i in some_function():
    print i

Temel olarak Python yorumlayıcısının yukarıdaki kodla yaptığı şey budur:

class it:
    def __init__(self):
        # Start at -1 so that we get 0 when we add 1 below.
        self.count = -1

    # The __iter__ method will be called once by the 'for' loop.
    # The rest of the magic happens on the object returned by this method.
    # In this case it is the object itself.
    def __iter__(self):
        return self

    # The next method will be called repeatedly by the 'for' loop
    # until it raises StopIteration.
    def next(self):
        self.count += 1
        if self.count < 4:
            return self.count
        else:
            # A StopIteration exception is raised
            # to signal that the iterator is done.
            # This is caught implicitly by the 'for' loop.
            raise StopIteration

def some_func():
    return it()

for i in some_func():
    print i

Sahne arkasında neler olduğu hakkında daha fazla bilgi için fordöngü buna yeniden yazılabilir:

iterator = some_func()
try:
    while 1:
        print iterator.next()
except StopIteration:
    pass

Bu daha mantıklı mı yoksa sadece sizi daha fazla karıştırıyor mu? :)

Bu unutmamalıdır olan gösterim amaçlı bir basitleştirme. :)


1
__getitem__yerine tanımlanabilir __iter__. Örneğin:, class it: pass; it.__getitem__ = lambda self, i: i*10 if i < 10 else [][0]; for i in it(): print(i)
Yazdıracak

17
Bu örneği Python 3.6'da denedim ve oluşturursam iterator = some_function(), değişkenin artık iteratordenilen bir işlevi next()yok, sadece bir __next__()işlevi var. Bahseteceğimi düşündüm.
Peter

forYazdığınız döngü uygulaması , örneği olan __iter__yöntemi nereden çağırıyor ? iteratorit
SystematicDisintegration

455

yieldAnahtar kelime iki basit gerçekleri indirgenir:

  1. Derleyici, bir işlevin içindeki herhangi bir yerdeyield anahtar kelimeyi algılarsa , bu işlev artık ifade yoluyla geri gelmez . Bunun yerine , derhal üreteç adı verilen tembel bir "bekleyen liste" nesnesi döndürürreturn
  2. Bir jeneratör yinelenebilir. Bir nedir iterable ? Bir Sanki şey listya setya rangeya dict görünümü, bir dahili belirli bir sırada her eleman ziyaret için protokol .

Özetle: bir jeneratör tembel, aşamalı olarak bekleyen bir listedir ve yieldifadeler jeneratörün kademeli olarak tükürmesi gereken liste değerlerini programlamak için fonksiyon gösterimini kullanmanızı sağlar .

generator = myYieldingFunction(...)
x = list(generator)

   generator
       v
[x[0], ..., ???]

         generator
             v
[x[0], x[1], ..., ???]

               generator
                   v
[x[0], x[1], x[2], ..., ???]

                       StopIteration exception
[x[0], x[1], x[2]]     done

list==[x[0], x[1], x[2]]

Misal

makeRangePython'unki gibi bir fonksiyon tanımlayalım range. Arayan makeRange(n)bir JENERATÖR döndürür:

def makeRange(n):
    # return 0,1,2,...,n-1
    i = 0
    while i < n:
        yield i
        i += 1

>>> makeRange(5)
<generator object makeRange at 0x19e4aa0>

Jeneratörü beklemedeki değerlerini hemen geri döndürmeye zorlamak için, onu geçirebilirsiniz list()(tıpkı herhangi bir yinelenebilir gibi):

>>> list(makeRange(5))
[0, 1, 2, 3, 4]

Örneği "sadece bir liste döndürmek" ile karşılaştırma

Yukarıdaki örnek, yalnızca eklediğiniz ve döndürdüğünüz bir liste oluşturmak olarak düşünülebilir:

# list-version                   #  # generator-version
def makeRange(n):                #  def makeRange(n):
    """return [0,1,2,...,n-1]""" #~     """return 0,1,2,...,n-1"""
    TO_RETURN = []               #>
    i = 0                        #      i = 0
    while i < n:                 #      while i < n:
        TO_RETURN += [i]         #~         yield i
        i += 1                   #          i += 1  ## indented
    return TO_RETURN             #>

>>> makeRange(5)
[0, 1, 2, 3, 4]

Yine de büyük bir fark var; son bölüme bakınız.


Jeneratörleri nasıl kullanabilirsiniz?

Yinelenebilir bir liste kavrayışının son kısmıdır ve tüm jeneratörler yinelenebilir, bu nedenle genellikle şu şekilde kullanılırlar:

#                   _ITERABLE_
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]

Jeneratörler için daha iyi bir his elde etmek için itertoolsmodülle oynayabilirsiniz ( garanti edildiğinde chain.from_iterabledeğil , kullandığınızdan emin olun chain). Örneğin, jeneratörler gibi sonsuz uzun tembel listeler uygulamak için bile kullanabilirsiniz itertools.count(). Kendinizinkini uygulayabilir def enumerate(iterable): zip(count(), iterable)veya alternatif olarakyield bir while döngüsünde anahtar kelimeyle .

Lütfen dikkat: jeneratörler aslında, couteinler uygulamak veya deterministik olmayan programlama veya diğer zarif şeyler gibi daha birçok şey için kullanılabilir . Ancak, burada sunduğum "tembel listeler" bakış açısı bulabileceğiniz en yaygın kullanımdır.


Kamera ARKASI

"Python yineleme protokolü" bu şekilde çalışır. Yani, yaptığınız zaman neler oluyor list(makeRange(5)). Daha önce "tembel, artımlı bir liste" olarak tanımladığım budur.

>>> x=iter(range(5))
>>> next(x)
0
>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> next(x)
4
>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Yerleşik işlev next()yalnızca .next()"yineleme protokolünün" bir parçası olan ve tüm yineleyicilerde bulunan nesneler işlevini çağırır . next()Fonksiyonu (ve yineleme protokolünün diğer kısımlarını), genellikle okunabilirlik pahasına süslü şeyler uygulamak için manuel olarak kullanabilirsiniz , bu yüzden bunu yapmaktan kaçının ...


Önemsiz ayrıntılar

Normalde, çoğu insan aşağıdaki ayrımları önemsemez ve muhtemelen burada okumayı bırakmak ister.

Python- speak'de , yinelenebilir bir liste gibi "for-loop kavramını anlayan" herhangi bir nesnedir [1,2,3]ve yineleyici istenen for- loop'un özel bir örneğidir [1,2,3].__iter__(). Bir jeneratör , yazılma şekli (işlev sözdizimi ile) dışında herhangi bir yineleyici ile tamamen aynıdır.

Listeden bir yineleyici talep ettiğinizde, yeni bir yineleyici oluşturur. Ancak, bir yineleyiciden bir yineleyici talep ettiğinizde (nadiren yaparsınız), size sadece bir kopyasını verir.

Böylece, böyle bir şey yapamamanız muhtemel olayda ...

> x = myRange(5)
> list(x)
[0, 1, 2, 3, 4]
> list(x)
[]

... o zaman bir jeneratörün bir yineleyici olduğunu unutmayın ; yani, tek kullanımlıktır. Tekrar kullanmak isterseniz, tekrar aramalısınız myRange(...). Sonucu iki kez kullanmanız gerekiyorsa, sonucu bir listeye dönüştürün ve bir değişkende saklayın x = list(myRange(5)). Kesinlikle bir jeneratörü klonlaması gerekenler (örneğin, korkunç bir şekilde meta metagramlama yapan) itertools.teekesinlikle gerekliyse kullanabilirler , çünkü kopyalanabilir yineleyici Python PEP standartları önerisi ertelendi.


377

Ne gelmez yieldanahtar kelime Python yapmak?

Cevap Anahat / Özet

  • İle bir işlev yieldolarak adlandırılan, bir döner Generator .
  • Jeneratörler yineleyicilerdir, çünkü yineleyici protokolünü uygularlar , böylece bunları yineleyebilirsiniz.
  • Bir jeneratöre bilgi gönderilebilir , bu da onu kavramsal olarak bir program yapar .
  • Python 3'te, bir jeneratörden diğerine her iki yönde de temsilci atayabilirsinizyield from .
  • (Ek, üstteki cevap da dahil olmak üzere birkaç cevabı eleştirir ve returnbir jeneratörde kullanımını tartışır .)

Jeneratörler:

yieldyalnızca bir işlev tanımının içinde yasaldır ve bir işlev tanımına dahil edilmesi yieldonu bir üreteç döndürür.

Jeneratör fikri, farklı uygulamalara sahip diğer dillerden (bkz. Dipnot 1) gelir. Python'un Jeneratörlerinde, kodun yürütülmesi verim noktasında dondurulur . Jeneratör çağrıldığında (yöntemler aşağıda tartışılmaktadır) yürütme devam eder ve bir sonraki verimde donar.

yieldaşağıdaki iki yöntemle tanımlanan yineleyici protokolünü uygulamanın kolay bir yolunu sunar : __iter__ve next(Python 2) veya __next__(Python 3). Bu yöntemlerin her ikisi de bir nesneyi modülden IteratorAbstract Base Sınıfı ile yazabileceğiniz bir yineleyici yapar collections.

>>> def func():
...     yield 'I am'
...     yield 'a generator!'
... 
>>> type(func)                 # A function with yield is still a function
<type 'function'>
>>> gen = func()
>>> type(gen)                  # but it returns a generator
<type 'generator'>
>>> hasattr(gen, '__iter__')   # that's an iterable
True
>>> hasattr(gen, 'next')       # and with .next (.__next__ in Python 3)
True                           # implements the iterator protocol.

Jeneratör tipi, yineleyicinin bir alt türüdür:

>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

Ve gerekirse, aşağıdaki gibi yazabiliriz:

>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True

A'nın bir özelliği, Iterator bittikten sonra tekrar kullanamaz veya sıfırlayamazsınız:

>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]

İşlevselliğini tekrar kullanmak istiyorsanız başka bir tane yapmanız gerekir (bkz. Dipnot 2):

>>> list(func())
['I am', 'a generator!']

Veri programlı olarak verilebilir, örneğin:

def func(an_iterable):
    for item in an_iterable:
        yield item

Yukarıdaki basit jeneratör de aşağıdakine eşdeğerdir - Python 3.3'ten itibaren (ve Python 2'de mevcut değildir), şunları kullanabilirsiniz yield from:

def func(an_iterable):
    yield from an_iterable

Ancak, yield from alt koroutinlerle kooperatif delegasyonu ile ilgili aşağıdaki bölümde açıklanacak olan alt jeneratörlere yetki verilmesine de izin vermektedir.

Eşyordamlar:

yield verilerin jeneratöre gönderilmesine izin veren bir ifade oluşturur (bkz. dipnot 3)

İşte bir örnek, receivedjeneratöre gönderilen verilere işaret eden değişkeni not edin :

def bank_account(deposited, interest_rate):
    while True:
        calculated_interest = interest_rate * deposited 
        received = yield calculated_interest
        if received:
            deposited += received


>>> my_account = bank_account(1000, .05)

İlk olarak, jeneratörü yerleşik fonksiyonla sıraya almalıyız next. Kullandığınız Python sürümüne bağlı olarak uygun nextveya __next__yöntemi çağıracaktır :

>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0

Ve şimdi jeneratöre veri gönderebiliriz. ( Gönderme None, çağrı ile aynıdırnext .):

>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5

Alt Koroutine Kooperatif Delegasyonu yield from

Şimdi, yield fromPython 3'te mevcut olduğunu hatırlayın . Bu, coroutines'i bir alt koroine devredebilmemizi sağlar:

def money_manager(expected_rate):
    under_management = yield     # must receive deposited value
    while True:
        try:
            additional_investment = yield expected_rate * under_management 
            if additional_investment:
                under_management += additional_investment
        except GeneratorExit:
            '''TODO: write function to send unclaimed funds to state'''
        finally:
            '''TODO: write function to mail tax info to client'''


def investment_account(deposited, manager):
    '''very simple model of an investment account that delegates to a manager'''
    next(manager) # must queue up manager
    manager.send(deposited)
    while True:
        try:
            yield from manager
        except GeneratorExit:
            return manager.close()

Ve şimdi işlevselliği bir alt jeneratöre devredebiliriz ve yukarıdaki gibi bir jeneratör tarafından kullanılabilir:

>>> my_manager = money_manager(.06)
>>> my_account = investment_account(1000, my_manager)
>>> first_year_return = next(my_account)
>>> first_year_return
60.0
>>> next_year_return = my_account.send(first_year_return + 1000)
>>> next_year_return
123.6

Sen kesin semantik hakkında daha fazla bilgi bulabilirsiniz yield fromiçinde PEP 380.

Diğer Yöntemler: kapatın ve atın

closeYöntem yükseltir GeneratorExitfonksiyonu yürütme dondurulmuş noktada. Bunu da __del__çağıracaksınız GeneratorExit;

>>> my_account.close()

Jeneratörde işlenebilen veya kullanıcıya geri gönderilebilen bir istisna da atayabilirsiniz:

>>> import sys
>>> try:
...     raise ValueError
... except:
...     my_manager.throw(*sys.exc_info())
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 2, in <module>
ValueError

Sonuç

Aşağıdaki sorunun tüm yönlerini ele aldığımı düşünüyorum:

Ne gelmez yieldanahtar kelime Python yapmak?

Çok yieldşey yapıyor. Eminim buna daha ayrıntılı örnekler ekleyebilirim. Daha fazlasını istiyorsanız veya yapıcı bir eleştiriniz varsa, aşağıdaki yorum yaparak bana bildirin.


Ek:

Üst / Kabul Edilen Cevabın Eleştirisi **

  • Bir listeyi örnek olarak kullanarak, tekrarlanabilir kılan şeylerin kafası karışır . Yukarıdaki referanslarıma bakın, ancak özet olarak: yinelenebilir __iter__bir yineleyici döndüren bir yöntem var . Bir yineleyici , yükselinceye kadar döngüler tarafından örtülü olarak çağrılan bir .next(Python 2 veya .__next__(Python 3) yöntemi sağlarforStopIteration ve bir kez yapıldığında, bunu yapmaya devam eder.
  • Daha sonra bir jeneratörün ne olduğunu tanımlamak için bir jeneratör ifadesi kullanır. Bir jeneratör sadece bir yineleyici oluşturmak için uygun bir yol olduğundan , sadece konuyu karıştırır ve henüz yieldparçaya gelmedik .
  • Gelen bir jeneratör yorgunluk Kontrol o aradığında .nextyerine o yerleşik işlevi kullanmak gerekirken, yöntem,next . Kod Python 3'te çalışmadığından uygun bir dolaylı katman olacaktır.
  • Itertools? Bu hiç bir şeyle ilgili değildi yield.
  • Python 3'teki yieldyeni işlevsellik ile birlikte sağlanan yöntemler hakkında tartışma yok . Üst / kabul edilen cevap çok eksik bir cevaptır.yield from

yieldJeneratör ifadesi veya kavrayışında önermenin cevabının eleştirisi .

Dilbilgisi şu anda liste kavrayışında herhangi bir ifadeye izin vermektedir.

expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
                     ('=' (yield_expr|testlist_star_expr))*)
...
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist

Verim bir ifade olduğu için, özellikle iyi bir kullanım durumu olmamasına rağmen, anlama veya üreteç ifadesinde kullanmak kadar ilginçtir.

CPython çekirdek geliştiricileri , ödeneklerini reddetmeyi tartışıyorlar . Posta listesinden alakalı bir gönderi:

30 Ocak 2017, 19:05, Brett Cannon şunu yazdı:

Paz, 29 Ocak 2017, 16:39 Craig Rodrigues şunu yazdı:

Her iki yaklaşımda da iyiyim. İşleri Python 3'teki gibi bırakmak iyi değil, IMHO.

Benim oyum, sözdiziminden beklediğiniz şeyi almadığınız için bir SyntaxError olması.

Mevcut davranışa dayanan herhangi bir kod gerçekten sürdürülemeyecek kadar akıllı olduğu için, bunun bizim için son derece mantıklı bir yer olduğunu kabul ediyorum.

Oraya varma açısından, muhtemelen şunları isteyeceğiz:

  • SözdizimiUyarı veya Kullanımdan Kaldırma - 3.7
  • 2.7.x sürümünde Py3k uyarısı
  • 3.8'de Sözdizimi Hatası

Şerefe, Nick.

- Nick Coghlan | gmail.com şirketinde ncoghlan | Brisbane, Avustralya

Ayrıca, bunun asla iyi bir fikir olmayacağı yönünde işaret eden olağanüstü bir sorun (10544) var (Python'da yazılmış bir Python uygulaması olan PyPy, zaten sözdizimi uyarılarını artırıyor.)

Sonuç olarak, CPython geliştiricileri bize aksini söyleyene kadar: Bir jeneratör ifadesi veya kavrayışı koymayın yield.

returnBir jeneratörü ifadesi

In Python 2 :

Bir üreteç işlevinde, returnifadenin bir içermesine izin verilmez expression_list. Bu bağlamda, bir çıplak returnjeneratörün yapıldığını ve StopIterationyükseltilmesine neden olacağını gösterir.

A expression_listtemel olarak virgülle ayrılmış herhangi bir sayıda ifadedir - temel olarak Python 2'de jeneratörü durdurabilirsiniz return, ancak bir değer döndüremezsiniz.

In Python 3 :

Bir jeneratör fonksiyonunda, returnifade jeneratörün yapıldığını ve StopIterationyükselmesine neden olacağını gösterir. Döndürülen değer (varsa), yapı oluşturmak için bağımsız değişken olarak kullanılır StopIterationve StopIteration.valueöznitelik haline gelir .

Dipnotlar

  1. Jeneratör kavramını Python'a tanıtmak için teklifte CLU, Sather ve Icon dillerine atıfta bulunuldu. Genel fikir, bir fonksiyonun dahili durumu koruyabilmesi ve kullanıcının talep etmesi halinde ara veri noktaları verebilmesidir. Bu , bazı sistemlerde bile bulunmayan Python iş parçacığı da dahil olmak üzere diğer yaklaşımlara göre performansta üstün olacağına söz verdi .

  2. Bu, örneğin, xrangenesnelerin ( rangePython 3'te) Iteratortekrarlanabilir olmalarına rağmen s olmadığı anlamına gelir, çünkü tekrar kullanılabilirler. Listeler gibi, __iter__yöntemleri de yineleyici nesnelerini döndürür.

  3. yieldbaşlangıçta bir ifade olarak tanıtıldı, yani yalnızca kod bloğundaki bir satırın başında görünebilir. Şimdi yieldbir verim ifadesi yaratır. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt Bu değişiklik, kullanıcının jeneratöre veri alabileceği gibi veri göndermesine izin vermek için önerildi . Veri göndermek için, kişinin bir şeye ataması gerekir ve bunun için bir ifade işe yaramaz.


328

yieldaynen return- söylediğiniz her şeyi (jeneratör olarak) döndürür. Fark, jeneratörü bir sonraki çağırışınızda yürütmenin son çağrıdan yieldifadeye başlamasıdır . Geri dönüşün aksine, verim oluştuğunda yığın çerçevesi temizlenmez, ancak kontrol arayana geri aktarılır, böylece işlev bir sonraki çağrıldığında durumu kaldığı yerden devam eder.

Kodunuzda, işlev get_child_candidatesbir yineleyici gibi davranır, böylece listenizi genişlettiğinizde, yeni listeye her seferinde bir öğe ekler.

list.extendbitene kadar bir yineleyici çağırır. Gönderdiğiniz kod örneğinde, sadece bir demet döndürmek ve bunu listeye eklemek çok daha açık olur.


107
Bu yakın, ama doğru değil. İçinde verim ifadesi bulunan bir işlevi her çağırdığınızda, yepyeni bir jeneratör nesnesi döndürür. Yalnızca bu üreticinin .next () yöntemini çağırdığınızda, yürütme son verimden sonra devam eder.
Kurosch

239

Bahsetmek için ekstra bir şey var: veren bir işlev aslında sona ermek zorunda değildir. Ben böyle bir kod yazdım:

def fib():
    last, cur = 0, 1
    while True: 
        yield cur
        last, cur = cur, last + cur

Sonra böyle başka bir kodda kullanabilirsiniz:

for f in fib():
    if some_condition: break
    coolfuncs(f);

Gerçekten bazı problemleri basitleştirmeye yardımcı olur ve bazı şeylerle çalışmayı kolaylaştırır.


233

Minimum çalışma örneğini tercih edenler için, bu etkileşimli Python oturumuna meditasyon yapın:

>>> def f():
...   yield 1
...   yield 2
...   yield 3
... 
>>> g = f()
>>> for i in g:
...   print(i)
... 
1
2
3
>>> for i in g:
...   print(i)
... 
>>> # Note that this time nothing was printed

209

TL; DR

Bunun yerine:

def square_list(n):
    the_list = []                         # Replace
    for x in range(n):
        y = x * x
        the_list.append(y)                # these
    return the_list                       # lines

Bunu yap:

def square_yield(n):
    for x in range(n):
        y = x * x
        yield y                           # with this one.

Kendinizi sıfırdan bir liste oluştururken bulduğunuzda, yieldbunun yerine her parça.

Bu benim ilk "aha" anımdı.


yieldBir olan şekerli söylemek yolu

bir dizi şey inşa et

Aynı davranış:

>>> for square in square_list(4):
...     print(square)
...
0
1
4
9
>>> for square in square_yield(4):
...     print(square)
...
0
1
4
9

Farklı davranışlar:

Verim tek geçişli : yalnızca bir kez yineleyebilirsiniz. Bir fonksiyonun içinde bir verim olduğunda ona bir jeneratör fonksiyonu diyoruz . Ve bir yineleyici geri döner. Bu terimler açığa çıkıyor. Bir kabın rahatlığını kaybederiz, ancak gerektiği gibi hesaplanan ve keyfi olarak uzun bir serinin gücünü kazanırız.

Verim tembel , hesaplamayı erteliyor. İçinde verimi olan bir işlev, onu çağırdığınızda aslında yürütülmez. Kaldığı yeri hatırlayan bir yineleyici nesnesi döndürür . next()Yineleyiciyi her çağırdığınızda (bu bir for-loop'ta gerçekleşir) yürütme bir sonraki verime ilerler. returnStopIteration'ı yükseltir ve diziyi sonlandırır (bu bir for-döngüsünün doğal sonu).

Verim çok yönlüdür . Verilerin birlikte depolanması gerekmez, her seferinde bir tane kullanılabilir. Sonsuz olabilir.

>>> def squares_all_of_them():
...     x = 0
...     while True:
...         yield x * x
...         x += 1
...
>>> squares = squares_all_of_them()
>>> for _ in range(4):
...     print(next(squares))
...
0
1
4
9

Birden fazla geçişe ihtiyacınız varsa ve seri çok uzun değilse, sadece arayın list():

>>> list(square_yield(4))
[0, 1, 4, 9]

Kelimenin parlak seçimi, yieldçünkü her iki anlam da geçerlidir:

verim - üretim veya tedarik (tarımda olduğu gibi)

... serideki sonraki verileri sağlayın.

verim - yol ver ya da vazgeç (siyasi güçte olduğu gibi)

... yineleyici ilerleyene kadar CPU yürütmesinden vazgeç.


194

Verim size bir jeneratör verir.

def get_odd_numbers(i):
    return range(1, i, 2)
def yield_odd_numbers(i):
    for x in range(1, i, 2):
       yield x
foo = get_odd_numbers(10)
bar = yield_odd_numbers(10)
foo
[1, 3, 5, 7, 9]
bar
<generator object yield_odd_numbers at 0x1029c6f50>
bar.next()
1
bar.next()
3
bar.next()
5

Gördüğünüz gibi, ilk durumda footüm listeyi bir kerede bellekte tutar. 5 elementli bir liste için önemli değil, ama 5 milyonluk bir liste istersen? Bu sadece büyük bir bellek yiyen değil, aynı zamanda fonksiyonun çağrıldığı zamanda inşa etmek için çok zamana mal olur.

İkinci durumda, barsadece bir jeneratör verir. Bir jeneratör yinelenebilir - yani bir fordöngüde vb. Kullanabileceğiniz anlamına gelir , ancak her bir değere yalnızca bir kez erişilebilir. Tüm değerler aynı zamanda bellekte saklanmaz; jeneratör nesnesi, son çağırdığınızda döngüde olduğu yeri "hatırlar" - bu şekilde, 50 milyara kadar sayılabilir bir sayım kullanıyorsanız, 50 milyara kadar saymanız gerekmez. aynı anda 50 milyar sayıyı saklayın.

Yine, oldukça hoş bir örnek, gerçekten 50 milyarı saymak istersen muhtemelen itertools'u kullanırdın. :)

Bu, jeneratörlerin en basit kullanım durumudur. Söylediğiniz gibi, bir çeşit yığın değişkeni kullanmak yerine, işleri çağrı yığınından yukarı itmek için verim kullanarak verimli permütasyonlar yazmak için kullanılabilir. Jeneratörler ayrıca özel ağaç geçişi ve diğer her türlü şey için kullanılabilir.


Sadece bir not - Python 3'te, rangeayrıca dışında benzer bir düşünce görürdük yüzden de, yerine listenin bir jeneratör döner __repr__/ __str__bu durumda, daha güzel bir sonuç göstermek için geçersiz kılınır range(1, 10, 2).
Olmaz.

189

Bir jeneratör geri dönüyor. Python'a özellikle aşina değilim, ancak bunlara aşina iseniz , C # 'ın yineleyici blokları ile aynı şey olduğuna inanıyorum .

Ana fikir, derleyici / yorumlayıcı / bazı hile yapan ne olursa olsun, arayanla ilgili olarak, next () öğesini çağırmaya devam edebilir ve değerleri döndürmeye devam eder - jeneratör yöntemi duraklatılmış gibi . Şimdi açıkça bir yöntemi gerçekten "duraklatamaz", bu yüzden derleyici şu anda nerede olduğunu ve yerel değişkenler vb neye benzediğini hatırlamak için bir durum makinesi oluşturur. Bu bir yineleyici kendiniz yazmaktan çok daha kolaydır.


167

Jeneratörlerin nasıl kullanılacağını açıklayan birçok harika cevap arasında henüz verilmediğini hissetmediğim bir cevap türü var. İşte programlama dili teorisi cevabı:

yieldPython deyimi bir jeneratör döner. Python'daki bir jeneratör, süreklilikleri döndüren bir işlevdir (ve özellikle bir tür coroutine'dir, ancak süreklilikler neler olup bittiğini anlamak için daha genel bir mekanizmayı temsil eder).

Programlama dilleri teorisindeki devamlar çok daha temel bir hesaplama türüdür, ancak sıklıkla kullanılmazlar, çünkü akıl yürütmeleri son derece zordur ve ayrıca uygulanması çok zordur. Ancak, devamın ne olduğu fikri açıktır: henüz bitmemiş bir hesaplama durumudur. Bu durumda, değişkenlerin mevcut değerleri, henüz gerçekleştirilmemiş işlemler vb. Kaydedilir. Daha sonra programın bir noktasında devam, programın değişkenleri bu duruma sıfırlanacak ve kaydedilen işlemler gerçekleştirilecek şekilde devam edilebilir.

Sürdürmeler, bu daha genel biçimde, iki şekilde uygulanabilir. Bu call/ccşekilde, programın yığını kelimenin tam anlamıyla kaydedilir ve devam çağrıldığında yığın geri yüklenir.

Devam geçiş stilinde (CPS), devamlar programcının açıkça yönettiği ve alt rutinlere aktardığı normal işlevlerdir (yalnızca işlevlerin birinci sınıf olduğu dillerde). Bu tarzda, program durumu, yığında bir yerde bulunan değişkenler yerine kapanışlarla (ve bunlarda kodlanan değişkenlerle) temsil edilir. Kontrol akışını yöneten işlevler, sürekliliği bağımsız değişkenler olarak kabul eder (bazı CPS varyasyonlarında, işlevler birden çok sürekliliği kabul edebilir) ve kontrol akışını yalnızca çağırarak ve sonra geri dönerek çağırarak manipüle eder. Devam geçiş stilinin çok basit bir örneği aşağıdaki gibidir:

def save_file(filename):
  def write_file_continuation():
    write_stuff_to_file(filename)

  check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)

Bu (çok basit) örnekte, programcı dosyayı gerçekten bir sürüme yazma işlemini kaydeder (bu, yazmak için birçok ayrıntıyla potansiyel olarak çok karmaşık bir işlem olabilir) ve sonra bu sürekliliği (yani, bir sınıf kapatma) başka bir operatöre gönderir ve bu da biraz daha fazla işlem yapar ve ardından gerekirse onu çağırır. (Bu tasarım desenini gerçek GUI programlamasında çok fazla kullanıyorum, çünkü bana kod satırlarını kaydediyor veya daha da önemlisi GUI olayları tetiklendikten sonra kontrol akışını yönetmek için.)

Bu yazının geri kalanı, genelliği kaybetmeden süreleri CPS olarak kavramsallaştırır, çünkü anlaşılması ve okunması çok daha kolay bir cehennemdir.


Şimdi Python'daki jeneratörler hakkında konuşalım. Jeneratörler devamın özel bir alt tipidir. Devamlar genel olarak bir hesaplama durumunu (yani, programın çağrı yığını) kaydedebilirken , üreteçler yalnızca yineleme durumunu bir yineleyici üzerinden kaydedebilir . Bununla birlikte, bu tanım jeneratörlerin belirli kullanım durumları için biraz yanıltıcıdır. Örneğin:

def f():
  while True:
    yield 4

Bu, davranışı iyi tanımlanmış makul bir yinelemedir - jeneratör her tekrarladığında 4 döndürür (ve sonsuza kadar yapar). Ancak yineleyicileri düşünürken akla gelen muhtemelen prototipik tekrarlanabilir tip değildir (yani for x in collection: do_something(x)). Bu örnek, jeneratörlerin gücünü gösterir: bir şey yineleyici ise, jeneratör yinelemenin durumunu kaydedebilir.

Tekrarlamak için: Devamlar bir program yığınının durumunu kaydedebilir ve jeneratörler yineleme durumunu kaydedebilir. Bu, devamların jeneratörlerden çok daha güçlü olduğu, aynı zamanda jeneratörlerin çok daha kolay olduğu anlamına gelir. Dil tasarımcısının uygulaması daha kolaydır ve programcının kullanması daha kolaydır (yazmak için biraz zamanınız varsa, devamlar ve çağrı / cc hakkında bu sayfayı okumaya ve anlamaya çalışın ).

Ancak, jeneratörleri basit, özel bir devam geçiş tarzı örneği olarak kolayca uygulayabilir (ve kavramsallaştırabilirsiniz):

Ne zaman yieldçağrılırsa, işleve bir süreyi geri döndürmesini söyler. İşlev tekrar çağrıldığında, kaldığı yerden başlar. Yani, sözde-sözde kodunda (yani, sözde kod değil, kod değil) jeneratörün nextyöntemi temel olarak şu şekildedir:

class Generator():
  def __init__(self,iterable,generatorfun):
    self.next_continuation = lambda:generatorfun(iterable)

  def next(self):
    value, next_continuation = self.next_continuation()
    self.next_continuation = next_continuation
    return value

nerede yieldanahtar kelime aslında gerçek jeneratör işlevi için sözdizimsel şeker gibi temelde bir şeydir:

def generatorfun(iterable):
  if len(iterable) == 0:
    raise StopIteration
  else:
    return (iterable[0], lambda:generatorfun(iterable[1:]))

Bunun sadece sahte kod olduğunu ve Python'daki jeneratörlerin gerçek uygulamasının daha karmaşık olduğunu unutmayın. Ancak neler olup bittiğini anlamak için bir alıştırma olarak, yieldanahtar kelime kullanmadan jeneratör nesnelerini uygulamak için devam geçiş stilini kullanmaya çalışın .


152

İşte düz dilde bir örnek. Yüksek seviyeli insan kavramları ile düşük seviyeli Python kavramları arasında bir yazışma sağlayacağım.

Bir sayı dizisi üzerinde işlem yapmak istiyorum, ama bu dizinin yaratılmasıyla kendimi rahatsız etmek istemiyorum, sadece yapmak istediğim işleme odaklanmak istiyorum. Bu yüzden aşağıdakileri yapıyorum:

  • Sizi arıyorum ve size belirli bir şekilde üretilen bir dizi sayı istediğimi söylüyorum ve algoritmanın ne olduğunu size bildiriyorum.
    Bu adım def, jeneratör fonksiyonunun, yani a yield.
  • Bir süre sonra size "Tamam, bana sayı dizisini anlatmaya hazır olun" diyorum.
    Bu adım, bir jeneratör nesnesi döndüren jeneratör işlevinin çağrılmasına karşılık gelir. Henüz bana rakam söylemediğinizi unutmayın; sadece kağıt ve kalemini alıyorsun.
  • Sana soruyorum, "bana bir sonraki numarayı söyle" ve sen bana ilk numarayı söyle; ondan sonra sizden bir sonraki numarayı istememi bekliyorsunuz. Nerede olduğunuzu, daha önce söylediğiniz sayıları ve bir sonraki rakamı hatırlamak sizin işiniz. Detaylar umrumda değil.
    Bu adım .next()jeneratör nesnesinin çağrılmasına karşılık gelir .
  • … Önceki adımı tekrarlayın, şu tarihe kadar…
  • eninde sonunda sona erebilirsin. Bana bir numara söylemiyorsun; sadece bağır, "atlarını tut! Bitirdim! Artık sayı yok!"
    Bu adım, işini sonlandıran ve bir StopIterationistisna oluşturan jeneratör nesnesine karşılık gelir . Jeneratör işlevinin istisnayı yükseltmesi gerekmez. İşlev sona erdiğinde veya verildiğinde otomatik olarak yükseltilir a return.

Jeneratörün yaptığı budur (a içeren bir fonksiyon yield); yürütmeye başlar, ne zaman olursa duraklar yieldve bir .next()değer istendiğinde en son olduğu yerden devam eder. Python'un değerlerin nasıl sıralanacağını açıklayan yineleyici protokolü ile tasarıma mükemmel uyum sağlar.

Yineleyici protokolünün en ünlü kullanıcısı forPython'daki komuttur. Yani, ne zaman yaparsanız yapın:

for item in sequence:

yukarıda açıklandığı gibi sequencebir liste, bir dize, bir sözlük veya bir jeneratör nesnesi olup olmadığı önemli değildir ; sonuç aynıdır: dizideki öğeleri tek tek okursunuz.

Not defBir içeren bir işlev içerebilen ve yieldanahtar kelime bir jeneratör oluşturmak için tek yol değildir; sadece bir tane oluşturmanın en kolay yolu.

Daha doğru bilgi için, Python belgelerindeki yineleyici türleri , verim ifadesi ve jeneratörler hakkında bilgi edinin .


130

Birçok yanıt, neden yieldbir jeneratör oluşturmak için a kullandığınızı gösterirken, bunun için daha fazla kullanım alanı vardır yield. İki kod bloğu arasında bilgi geçişini sağlayan bir koroutin oluşturmak oldukça kolaydır. yieldBir jeneratör oluşturmak için kullanmakla ilgili verilen güzel örneklerin hiçbirini tekrarlamayacağım .

yieldAşağıdaki kodda a'nın ne yaptığını anlamanıza yardımcı olması için, parmağınızı kullanarak yield. Parmağınız her vurduğunda, yieldbir nextveya a sendgirilmesini beklemeniz gerekir. A nextçağrıldığında,… tuşunun yieldsağındaki kod yielddeğerlendirilip arayan kişiye geri dönünceye kadar kodu izlersiniz ... sonra beklersiniz. Ne zaman nexttekrar denir, kod aracılığıyla başka döngü gerçekleştirin. Ancak, bir eşyordamın içinde, dikkat edeceğiz yieldayrıca kullanılabilir sendarayan bir değer gönderecek olan ... içine veren fonksiyonu. A sendverilirse,yieldgönderilen değeri alır ve sol tarafa tükürür ... sonra siz yieldtekrar vurana kadar koddaki iz ilerler (sondaki değeri döndürülmüş gibi döndürür next).

Örneğin:

>>> def coroutine():
...     i = -1
...     while True:
...         i += 1
...         val = (yield i)
...         print("Received %s" % val)
...
>>> sequence = coroutine()
>>> sequence.next()
0
>>> sequence.next()
Received None
1
>>> sequence.send('hello')
Received hello
2
>>> sequence.close()

Şirin! Bir trambolin (Lisp anlamda). Sık sık kimse görmez!
00prometheus

129

Başka bir yieldkullanım ve anlam var (Python 3.3'ten beri):

yield from <expr>

Gönderen PEP 380 - dizimi için yetki verme bir Subgenerator için :

Bir jeneratörün operasyonlarının bir kısmını başka bir jeneratöre devretmesi için bir sözdizimi önerilir. Bu, 'verim' içeren bir kod bölümünün hesaba katılmasını ve başka bir jeneratöre yerleştirilmesini sağlar. Ek olarak, alt jeneratörün bir değerle geri dönmesine izin verilir ve değer, temsilci jeneratör için kullanılabilir hale getirilir.

Yeni sözdizimi, bir jeneratör diğeri tarafından üretilen değerleri yeniden verdiğinde optimizasyon için de bazı fırsatlar sunuyor.

Dahası bu tanıtacaktır (Python 3.5'ten beri):

async def new_coroutine(data):
   ...
   await blocking_action()

Koroutinlerin düzenli bir jeneratörle karıştırılmaması için (bugün yieldher ikisinde de kullanılmaktadır).


117

Tüm büyük cevaplar, ancak yeni başlayanlar için biraz zor.

returnBeyanı öğrendiğinizi varsayıyorum .

Bir benzetme returnve yieldikizler. return"getiri ve durma" anlamına gelir, "verim" ise "getiri, ancak devam et" anlamına gelir

  1. İle bir num_list almaya çalışın return.
def num_list(n):
    for i in range(n):
        return i

Çalıştır:

In [5]: num_list(3)
Out[5]: 0

Bakın, bir liste yerine sadece tek bir sayı elde edersiniz. returnasla mutlu hakim olmanıza izin vermez, sadece bir kez uygular ve istifa eder.

  1. Orada geliyor yield

Değiştir returnile yield:

In [10]: def num_list(n):
    ...:     for i in range(n):
    ...:         yield i
    ...:

In [11]: num_list(3)
Out[11]: <generator object num_list at 0x10327c990>

In [12]: list(num_list(3))
Out[12]: [0, 1, 2]

Şimdi, tüm sayıları almak için kazanırsınız.

returnHangisinin bir kez çalışıp durduğuna kıyasla planladığınız yieldsüreleri çalıştırır. returnOlarak return one of themve yieldolarak yorumlayabilirsiniz return all of them. Buna denir iterable.

  1. Bir adım daha biz yeniden yazabilirsiniz yieldbeyanınareturn
In [15]: def num_list(n):
    ...:     result = []
    ...:     for i in range(n):
    ...:         result.append(i)
    ...:     return result

In [16]: num_list(3)
Out[16]: [0, 1, 2]

Çekirdek bu yield.

Bir liste returnçıktıları ve nesne yieldçıktısı arasındaki fark :

Her zaman bir liste nesnesinden [0, 1, 2] alırsınız, ancak bunları yalnızca bir yieldkez ' nesne çıktısından' alabilirsiniz. Yani, içinde gösterildiği generatorgibi yeni bir isim nesnesine sahiptir Out[11]: <generator object num_list at 0x10327c990>.

Sonuç olarak, onu bir metafor olarak:

  • returnve yieldikizler
  • listve generatorikizler

Bu anlaşılabilir bir durumdur, ancak önemli bir fark, bir işlev / yöntemde birden fazla verim alabilmenizdir. Benzetme bu noktada tamamen yıkılıyor. Verim bir işlevdeki yerini hatırlar, bu nedenle next () işlevini bir sonraki çağırışınızda, işleviniz bir sonrakine devam eder yield. Bence bu önemlidir ve ifade edilmelidir.
Mike S

104

Aşağıda, Python onlar için sözdizimsel şeker sağlamıyormuş gibi jeneratörlerin gerçekte nasıl uygulanacağına dair bazı Python örnekleri:

Bir Python üreticisi olarak:

from itertools import islice

def fib_gen():
    a, b = 1, 1
    while True:
        yield a
        a, b = b, a + b

assert [1, 1, 2, 3, 5] == list(islice(fib_gen(), 5))

Jeneratörler yerine sözcüksel kapanışları kullanma

def ftake(fnext, last):
    return [fnext() for _ in xrange(last)]

def fib_gen2():
    #funky scope due to python2.x workaround
    #for python 3.x use nonlocal
    def _():
        _.a, _.b = _.b, _.a + _.b
        return _.a
    _.a, _.b = 0, 1
    return _

assert [1,1,2,3,5] == ftake(fib_gen2(), 5)

Jeneratörler yerine nesne kapanışlarını kullanma (çünkü ClosuresAndObjectsAreEquivalent )

class fib_gen3:
    def __init__(self):
        self.a, self.b = 1, 1

    def __call__(self):
        r = self.a
        self.a, self.b = self.b, self.a + self.b
        return r

assert [1,1,2,3,5] == ftake(fib_gen3(), 5)

97

"Jeneratörlerin hızlı bir açıklaması için Beazley'nin 'Python: Essential Reference' sayfa 19'unu yayınlayacaktım, ama diğer pek çok kişi zaten iyi açıklamalar yayınladı.

Ayrıca, yieldprogramcıların jeneratör işlevlerinde kullanımlarının ikizi olarak kullanılabileceğini unutmayın . Kod snippet'inizle aynı kullanım olmamasına rağmen (yield), bir işlevde ifade olarak kullanılabilir. Bir arayan yöntemi kullanarak yönteme bir değer gönderdiğinde, bir send()sonraki (yield)ifadeyle karşılaşılana kadar eş program yürütülür .

Jeneratörler ve yardımcı programlar, veri akışı tipi uygulamaları kurmanın harika bir yoludur. İfadenin yieldişlevlerde diğer kullanımı hakkında bilgi sahibi olmanın faydalı olacağını düşündüm .


97

Programlama açısından bakıldığında, yineleyiciler thunks olarak uygulanır .

Eşzamanlı yürütme vb. İçin yineleyiciler, jeneratörler ve iş parçacığı havuzlarını thunks (anonim işlevler olarak da adlandırılır) olarak uygulamak için, bir dağıtım nesnesine sahip bir kapatma nesnesine gönderilen iletileri kullanır ve dağıtım görevlisi "iletilere" yanıt verir.

http://en.wikipedia.org/wiki/Message_passing

" next ", " iter " çağrısı tarafından oluşturulan bir kapatmaya gönderilen bir mesajdır .

Bu hesaplamayı uygulamanın birçok yolu vardır. Mutasyon kullandım, ancak mevcut değeri ve bir sonraki vereni döndürerek mutasyon olmadan yapmak kolaydır.

İşte R6RS'nin yapısını kullanan bir gösteri, ancak anlambilim Python'larla tamamen aynı. Aynı hesaplama modeli ve Python'da yeniden yazmak için sadece sözdiziminde bir değişiklik gerekiyor.

Welcome to Racket v6.5.0.3.

-> (define gen
     (lambda (l)
       (define yield
         (lambda ()
           (if (null? l)
               'END
               (let ((v (car l)))
                 (set! l (cdr l))
                 v))))
       (lambda(m)
         (case m
           ('yield (yield))
           ('init  (lambda (data)
                     (set! l data)
                     'OK))))))
-> (define stream (gen '(1 2 3)))
-> (stream 'yield)
1
-> (stream 'yield)
2
-> (stream 'yield)
3
-> (stream 'yield)
'END
-> ((stream 'init) '(a b))
'OK
-> (stream 'yield)
'a
-> (stream 'yield)
'b
-> (stream 'yield)
'END
-> (stream 'yield)
'END
->

84

İşte basit bir örnek:

def isPrimeNumber(n):
    print "isPrimeNumber({}) call".format(n)
    if n==1:
        return False
    for x in range(2,n):
        if n % x == 0:
            return False
    return True

def primes (n=1):
    while(True):
        print "loop step ---------------- {}".format(n)
        if isPrimeNumber(n): yield n
        n += 1

for n in primes():
    if n> 10:break
    print "wiriting result {}".format(n)

Çıktı:

loop step ---------------- 1
isPrimeNumber(1) call
loop step ---------------- 2
isPrimeNumber(2) call
loop step ---------------- 3
isPrimeNumber(3) call
wiriting result 3
loop step ---------------- 4
isPrimeNumber(4) call
loop step ---------------- 5
isPrimeNumber(5) call
wiriting result 5
loop step ---------------- 6
isPrimeNumber(6) call
loop step ---------------- 7
isPrimeNumber(7) call
wiriting result 7
loop step ---------------- 8
isPrimeNumber(8) call
loop step ---------------- 9
isPrimeNumber(9) call
loop step ---------------- 10
isPrimeNumber(10) call
loop step ---------------- 11
isPrimeNumber(11) call

Ben bir Python geliştiricisi değilim, ama bana yieldgöre program akışı pozisyonunu tutar ve bir sonraki döngü "verim" pozisyonundan başlar. Görünüşe göre o pozisyonda bekliyor ve bundan hemen önce, dışarıdaki bir değeri döndürmek ve bir dahaki sefere çalışmaya devam ediyor.

İlginç ve güzel bir yetenek gibi görünüyor: D


Haklısın. Peki "verim" davranışını görmek için akış üzerindeki etkisi nedir? Matematiksel adıyla algoritmayı değiştirebilirim. "Verim" in farklı değerlendirmesini almanıza yardımcı olur mu?
Engin OZTURK

68

İşte neyin zihinsel bir görüntüsü yield.

Bir iş parçacığı (bu şekilde uygulanmadığında bile) yığını gibi düşünmek istiyorum.

Normal bir işlev çağrıldığında, yerel değişkenlerini yığına koyar, bazı hesaplamalar yapar, ardından yığını temizler ve geri döner. Yerel değişkenlerinin değerleri bir daha asla görülmez.

Bir yieldişlevle, kodu çalışmaya başladığında (yani işlev next()çağrıldıktan sonra , yöntemi daha sonra çağrılan bir jeneratör nesnesini döndürmek ), benzer şekilde yerel değişkenlerini yığına koyar ve bir süre hesaplar. Ancak, yieldifadeye çarptığında, yığının bir kısmını temizleyip geri dönmeden önce, yerel değişkenlerinin bir anlık görüntüsünü alır ve bunları jeneratör nesnesinde saklar. Ayrıca şu anda bulunduğu yere kendi kodunu (yani belirli bir yieldifade) yazar.

Yani jeneratörün asılı olduğu bir tür donmuş fonksiyon.

Daha next()sonra çağrıldığında, işlevin eşyalarını yığına alır ve yeniden canlandırır. Fonksiyon, soğuk depoda sonsuzluğu geçirmiş olmasından habersiz, kaldığı yerden hesaplamaya devam ediyor.

Aşağıdaki örnekleri karşılaştırın:

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

İkinci işlevi çağırdığımızda, ilk işlevden çok farklı davranır. yieldİfadesi ulaşılamaz olabilir, ama mevcut yerde ise, biz uğraşıyoruz doğasını değiştirir.

>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>

Arama yielderFunction()kodunu çalıştırmaz, ancak koddan bir jeneratör oluşturur. (Belki bu tür şeyleri yielderokunabilirlik önekiyle adlandırmak iyi bir fikirdir .)

>>> gen = yielderFunction()
>>> dir(gen)
['__class__',
 ...
 '__iter__',    #Returns gen itself, to make it work uniformly with containers
 ...            #when given to a for loop. (Containers return an iterator instead.)
 'close',
 'gi_code',
 'gi_frame',
 'gi_running',
 'next',        #The method that runs the function's body.
 'send',
 'throw']

gi_codeVe gi_framedondurulmuş devlet saklandığı yere alanlardır. Onları araştırarak dir(..), yukarıdaki zihinsel modelimizin inandırıcı olduğunu doğrulayabiliriz.


59

Her cevabın önerdiği gibi, yieldbir sekans oluşturucu oluşturmak için kullanılır. Dinamik olarak bir dizi oluşturmak için kullanılır. Örneğin, bir ağdaki bir dosyayı satır satır okurken, yieldişlevi aşağıdaki gibi kullanabilirsiniz :

def getNextLines():
   while con.isOpen():
       yield con.read()

Kodunuzda aşağıdaki gibi kullanabilirsiniz:

for line in getNextLines():
    doSomeThing(line)

İcra Kontrol Transferi gotcha

Yürütme denetimi, forgetiri yürütüldüğünde getNextLines () öğesinden döngüye aktarılır . Böylece, getNextLines () her çağrıldığında, yürütme son kez duraklatıldığı noktadan başlar.

Kısacası, aşağıdaki kod ile bir fonksiyon

def simpleYield():
    yield "first time"
    yield "second time"
    yield "third time"
    yield "Now some useful value {}".format(12)

for i in simpleYield():
    print i

yazdıracak

"first time"
"second time"
"third time"
"Now some useful value 12"

59

Ne olduğunu anlamak için kolay bir örnek: yield

def f123():
    for _ in range(4):
        yield 1
        yield 2


for i in f123():
    print (i)

Çıktı:

1 2 1 2 1 2 1 2

5
bu çıktıdan emin misin? Bu baskı deyimini kullanarak çalıştırırsanız yalnızca tek bir satıra yazdırılmaz print(i, end=' ')mı? Aksi takdirde, varsayılan davranışın her sayıyı yeni bir satıra koyacağına inanıyorum
user9074332

@ user9074332, Haklısınız, ancak anlaşmayı kolaylaştırmak için tek bir satırda yazılmıştır
Gavriel Cohen

57

(Aşağıdaki cevabım sadece Python üreteci kullanma perspektifinden bahsediyor, , yığın ve yığın manipülasyonunun bazı hilelerini içeren jeneratör mekanizmasının altında yatan uygulamayı .)

Ne zaman yieldyerine ait kullanılan returnbir piton işlevindeki bu işlevi denen bir şey special dönüştü generator function. Bu işlev generatortüründe bir nesne döndürür . Anahtar kelime özel olarak bu tür fonksiyon tedavisi için piton derleyici bildirmek için bir bayraktır. Bir değer döndürüldüğünde normal işlevler sonlanır. Ancak derleyicinin yardımıyla, jeneratör işlevi sürdürülebilir olarak düşünülebilir . Yani, yürütme içeriği geri yüklenecek ve yürütme son çalıştırmadan devam edecektir. Açıkça geri dönene kadar (istisna protokolünün bir parçası olan) bir istisna oluşturur veya işlevin sonuna ulaşır. Hakkında birçok referans buldum ama buyieldStopIterationgenerator onedan functional programming perspectiveen digestable olduğunu.

(Şimdi arkasındaki gerekçeden generatorve iteratorkendi anlayışımdan yola çıkarak konuşmak istiyorum . Umarım bu, yineleyici ve jeneratörün temel motivasyonunu kavramanıza yardımcı olabilir . Bu kavram C # gibi diğer dillerde de görülür.)

Anladığım kadarıyla, bir grup veriyi işlemek istediğimizde, genellikle önce verileri bir yerde saklarız ve sonra teker teker işleriz. Ancak bu naif yaklaşım sorunludur. Veri hacmi çok büyükse, bunları önceden bir bütün olarak saklamak pahalıdır. Öyleyse, datakendisini doğrudan depolamak yerine , neden metadatadolaylı olarak bir çeşit depolamıyorsunuz, yanithe logic how the data is computed .

Bu tür meta verileri sarmak için 2 yaklaşım vardır.

  1. OO yaklaşımı, meta verileri sararız as a class. Bu, iteratoryineleyici protokolünü uygulayan sözde (yani __next__(), ve __iter__()yöntemler). Bu aynı zamanda yaygın olarak görülen yineleyici tasarım modelidir .
  2. İşlevsel yaklaşım, meta verileri sararız as a function. Bu sözde generator function. Ancak kaputun altında, geri dönen generator objecthala IS-Ayineleyici çünkü yineleyici protokolünü de uygular.

Her iki durumda da, bir yineleyici oluşturulur, yani size istediğiniz verileri verebilecek bazı nesneler. OO yaklaşımı biraz karmaşık olabilir. Her neyse, hangisini kullanmak size kalmış.


54

Özetle, yieldifade, işlevinizi generator, orijinal işlevinizin gövdesini saran ve adında özel bir nesne üreten bir fabrikaya dönüştürür . Zaman generatortekrarlanır sonraki ulaşana kadar, bu işlevini yürütür yieldsonra iletilen değerin yürütülmesine ve değerlendirir askıya yield. Yürütme yolu işlevden çıkıncaya kadar bu işlemi her yinelemede yineler. Örneğin,

def simple_generator():
    yield 'one'
    yield 'two'
    yield 'three'

for i in simple_generator():
    print i

sadece çıktılar

one
two
three

Güç, jeneratörü bir sekansı hesaplayan bir döngü ile kullanmaktan gelir, jeneratör her seferinde hesaplamanın bir sonraki sonucunu 'vermek' için döngü durdurmayı yürütür, bu şekilde anında bir liste hesaplar, fayda hafızadır özellikle büyük hesaplamalar için kaydedildi

Diyelim rangeki, yinelenebilir bir sayı aralığı üreten kendi fonksiyonunuzu oluşturmak istiyorsunuz, bunu böyle yapabilirsiniz,

def myRangeNaive(i):
    n = 0
    range = []
    while n < i:
        range.append(n)
        n = n + 1
    return range

ve böyle kullanın;

for i in myRangeNaive(10):
    print i

Ama bu verimsiz çünkü

  • Yalnızca bir kez kullandığınız bir dizi oluşturursunuz (bu, belleği boşa harcar)
  • Bu kod aslında bu dizi üzerinde iki kez döngü yapar! :(

Neyse ki Guido ve ekibi jeneratörleri geliştirecek kadar cömertti, bu yüzden bunu yapabilirdik;

def myRangeSmart(i):
    n = 0
    while n < i:
       yield n
       n = n + 1
    return

for i in myRangeSmart(10):
    print i

Şimdi her yinelemede, jeneratörün adı verilen bir işlev next(), işlevi durduğu ve değeri 'verdiği' veya işlevin sonuna ulaştığı bir 'verim' ifadesine ulaşana kadar işlevi yürütür. Bu durumda, ilk çağrıda, next()verim ifadesine kadar çalışır ve 'n' değerini verir, bir sonraki çağrıda artış ifadesini yürütür, 'while' a geri döner, değerlendirir ve doğruysa durur ve yine 'n' verirse, while koşulu false değerine dönene ve jeneratör işlevin sonuna atlayana kadar bu şekilde devam eder.


53

Verim bir nesnedir

Bir returnişlevdeki A tek bir değer döndürür.

Bir işlevin büyük bir değer kümesi döndürmesini istiyorsanız , kullanın yield.

Daha da önemlisi, yieldbir engeldir .

CUDA dilinde bariyer gibi, tamamlanıncaya kadar kontrolü aktarmaz.

Yani, fonksiyonunuzdaki kodu baştan vuruncaya kadar çalıştıracaktır yield. Ardından, döngünün ilk değerini döndürür.

Ardından, diğer her çağrı, işleve yazdığınız döngüyü bir kez daha çalıştıracak ve döndürülecek değer kalmayana kadar bir sonraki değeri döndürecektir.


52

Birçok insan kullanmak returnyerine yield, ancak bazı durumlarda yielddaha verimli ve daha kolay çalışabilir.

İşte yieldkesinlikle en iyi örnek :

dönüş (işlevde)

import random

def return_dates():
    dates = [] # With 'return' you need to create a list then return it
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        dates.append(date)
    return dates

verim (işlevde)

def yield_dates():
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        yield date # 'yield' makes a generator automatically which works
                   # in a similar way. This is much more efficient.

Arama işlevleri

dates_list = return_dates()
print(dates_list)
for i in dates_list:
    print(i)

dates_generator = yield_dates()
print(dates_generator)
for i in dates_generator:
    print(i)

Her iki işlev de aynı şeyi yapar, ancak yieldbeş yerine üç satır kullanır ve endişelenecek bir daha az değişken vardır.

Bu kodun sonucudur:

Çıktı

Gördüğünüz gibi her iki fonksiyon da aynı şeyi yapıyor. Tek fark return_dates()bir liste yield_dates()verir ve bir jeneratör verir.

Gerçek hayat örneği, bir dosyayı satır satır okumak veya sadece bir jeneratör yapmak istiyorsanız.


43

yieldbir işlev için bir dönüş öğesi gibidir. Fark, yieldelemanın bir işlevi jeneratöre dönüştürmesidir. Jeneratör, bir şey 'elde edilene kadar bir fonksiyon gibi davranır. Jeneratör bir sonraki çağrılıncaya kadar durur ve başladığı noktadan devam eder. 'Verilmiş' tüm değerlerin bir dizisini arayarak alabilirsiniz list(generator()).


41

yieldAnahtar kelime basitçe sonuçlarını döndürmek toplar. yieldGibi düşünreturn +=


36

İşte yieldfibonacci serisini hesaplamak için basit bir temel yaklaşım:

def fib(limit=50):
    a, b = 0, 1
    for i in range(limit):
       yield b
       a, b = b, a+b

Bunu REPL'inize girip denemeye çalıştığınızda gizemli bir sonuç elde edersiniz:

>>> fib()
<generator object fib at 0x7fa38394e3b8>

Bunun nedeni, yieldPython'a bir jeneratör , yani talep üzerine değerler üreten bir nesne oluşturmak istediğiniz sinyalin var olmasıdır.

Peki, bu değerleri nasıl üretiyorsunuz? Bu, doğrudan yerleşik işlev kullanılarak yapılabilirnext veya dolaylı olarak değer tüketen bir yapıya beslenerek yapılabilir.

Yerleşik kullanarak next()fonksiyonu, doğrudan çağırmak .next/ __next__bir değer üretmek için jeneratör zorlayarak,:

>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5

Dolaylı olarak, fibbir fordöngüye, bir listbaşlatıcıya, bir tuplebaşlatıcıya veya değer üreten / üreten bir nesneyi bekleyen başka bir şey sağlarsanız , üretici tarafından daha fazla değer üretilinceye kadar (ve geri döner) jeneratörü "tüketirsiniz" :

results = []
for i in fib(30):       # consumes fib
    results.append(i) 
# can also be accomplished with
results = list(fib(30)) # consumes fib

Benzer şekilde, bir tuplebaşlatıcı ile:

>>> tuple(fib(5))       # consumes fib
(1, 1, 2, 3, 5)

Jeneratör, tembel olması bakımından bir fonksiyondan farklıdır. Bunu, yerel durumunu koruyarak ve ihtiyacınız olduğunda devam ettirmenize izin vererek başarır.

İlk fibarayarak çağırdığınızda :

f = fib()

Python işlevi derler, yieldanahtar kelimeyle karşılaşır ve basitçe bir jeneratör nesnesini size geri döndürür. Çok yararlı değil gibi görünüyor.

Daha sonra ilk değeri doğrudan veya dolaylı olarak üretmesini istediğinizde, a ile karşılaşana kadar bulduğu tüm ifadeleri yürütür yield, daha sonra sağladığınız değeri geri döndürür yieldve duraklatır. Bunu daha iyi gösteren bir örnek için, bazı printçağrıları kullanalım ( print "text"Python 2'de if ile değiştirin ):

def yielder(value):
    """ This is an infinite generator. Only use next on it """ 
    while 1:
        print("I'm going to generate the value for you")
        print("Then I'll pause for a while")
        yield value
        print("Let's go through it again.")

Şimdi REPL kodunu girin:

>>> gen = yielder("Hello, yield!")

artık bir değer üretmesi için bir komut bekleyen bir jeneratör nesneniz var. nextNelerin yazdırıldığını kullanın ve görün:

>>> next(gen) # runs until it finds a yield
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

Alıntılanmamış sonuçlar yazdırılan sonuçlardır. Alıntılanan sonuç, döndürülen sonuçtur yield. nextŞimdi tekrar arayın :

>>> next(gen) # continues from yield and runs again
Let's go through it again.
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

Jeneratör duraklatıldığını hatırlar yield valueve oradan devam eder. Bir sonraki mesaj yazdırılır ve yieldifadede duraklatılacak ifadenin aranması ( whiledöngüden dolayı ) tekrarlanır.

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.