38 Soru: “Verim” anahtar kelimesi ne işe yarar?

tarafından oluşturulan soru Thu, May 2, 2019 12:00 AM

yield anahtar sözcüğünün Python'da kullanımı nedir? Ne işe yarıyor?

Örneğin, bu 1 kodunu 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

_get_child_candidates yöntemi çağrıldığında ne olur? Bir liste iade edildi mi? Tek bir element mi? Yine mi arandı? Sonraki çağrılar ne zaman durur?


1. Bu kod parçası, metrik uzaylar için harika bir Python kütüphanesi yapan Jochen Schulz (jrschulz) tarafından yazılmıştır. Bu, tüm kaynağın bağlantısıdır: Modül mspace .

    
9314
30 Yanıtlar                              30                         

yield’un ne yaptığını anlamak için, jeneratörlerin ne olduğunu anlamalısınız. Ve jeneratörleri anlamadan önce, yinelenebilir 'i anlamalısınız.

Iterables

Bir liste oluşturduğunuzda, öğelerini birer birer okuyabilirsiniz. Öğelerini tek tek okumak yineleme olarak adlandırılır:

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

mylist, yinelenebilir bir . Bir liste anlayışı kullandığınızda, bir liste yaratırsınız ve bu sayede tekrar edilebilir:

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

"for... in..." u kullanabileceğiniz her şey yinelenebilir; lists, strings, dosyalar ...

Bu yinelemeler kullanışlıdır, çünkü bunları istediğiniz kadar okuyabilirsiniz, ancak tüm değerleri bellekte saklarsınız ve bu, her zaman istediğiniz gibi olmaz.

Jeneratörler

Jeneratörler yineleyicilerdir, bir tür yinelenebilir yalnızca bir defadan fazla yineleyebilirsiniz . Jeneratörler, tüm değerleri hafızada tutmaz, anında değerleri oluştururlar :

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

() yerine [] kullanmanız dışında aynı. Ancak, jeneratörler yalnızca bir kez kullanılabildiğinden, ikinci kez for i in mygenerator gerçekleştiremezsiniz : 0'ı hesaplar, sonra unut ve 1'i hesapla ve 4'ü birer birer hesaplamayı bitirir.

Getiri

yield, işlev bir jeneratör döndüreceği dışında, return 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

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

yield’da ustalaşmak için, işlevi çağırdığınızda, işlev gövdesine yazdığınız kodun çalışmadığını anlamalısınız. İşlev yalnızca jeneratör nesnesini döndürür, bu biraz zor : -)

Sonra, kodunuz for, jeneratörü her kullandığında bıraktığı yerden devam eder.

Şimdi zor kısım:

for, fonksiyonunuzdan yaratılan jeneratör nesnesini ilk kez çağırdığında, fonksiyonunuzdaki kodu en baştan yield’a ulaşana kadar çalıştırır, sonra döngünün ilk değerini döndürür. Ardından, diğer aramalar işlevde yazdığınız döngüyü bir kez daha çalıştırır ve döndürülecek değer bulunmayana kadar bir sonraki değeri döndürür.

İşlev çalıştıktan sonra jeneratör boş sayılır, ancak artık yield'a gelmez. Bu, döngünün sona ermesi nedeniyle veya artık bir "if/else"’u tatmin etmediğiniz olabilir.


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 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 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 candidates 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ı bölüm içeriyor:

  • Döngü bir listede yinelenir, ancak döngü yinelenirken listeyi genişletir :-) Sonunda gelebildiğinden bu yana biraz tehlikeli olsa bile, tüm bu iç içe geçmiş verilerden geçmenin özlü bir yoludur. sonsuz döngü. Bu durumda, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)), jeneratörün tüm değerlerini tüketir, ancak while, aynı düğümde uygulanmadığından önceki değerlerden farklı değerler üretecek yeni jeneratör nesneleri oluşturmaya devam eder.

  • extend() yöntemi yinelenebilir bekleyen ve değerlerini listeye ekleyen bir liste nesnesi yöntemidir.

Genellikle bir liste yazarız:

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

Ancak kodunuzda bir jeneratör bulunur; bu iyi bir işlemdir:

  1. Değerleri iki kez okumanıza gerek yoktur.
  2. Çok fazla çocuğunuz olabilir ve hepsinin bellekte saklanmasını istemiyorsunuz.

İşe yarıyor çünkü Python bir yöntemin argümanının bir liste olup olmadığına aldırmıyor. Python, yineleyiciler bekler, böylece dizelerle, listelerle, totelerle ve jeneratörler ile çalışır! Buna ördek yazması denir ve Python'un bu kadar havalı olmasının sebeplerinden biridir. Fakat bu başka bir soru için başka bir hikaye…

Bir jeneratörün gelişmiş kullanımını görmek için burada durabilir veya 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__()) veya print(next(corner_street_atm)) kullanın

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

İtertools, en iyi arkadaşın

itertools modülü, yinelemeleri işlemek için özel işlevler içerir. Hiç bir jeneratörü çoğaltmak istediniz mi? Zincir iki jeneratör? Tek astarlı iç içe bir listede grup değerleri? Başka bir liste oluşturmadan Map / Zip?

Sonra sadece import itertools.

Bir örnek? Dört atlı bir yarış için olası varış talimatlarını 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)]

Yinelemenin iç mekanizmalarını anlama

Yineleme yinelemeleri (__iter__() yöntemini uygulayarak) ve yinelemeleri (__next__() yöntemini uygulayarak) içeren bir işlemdir. Yinelemeler, yineleyiciyi alabileceğiniz nesnelerdir. Yineleyiciler yinelemeler üzerinde yinelemenizi sağlayan nesnelerdir.

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

    
13495
2019-05-02 19: 34: 54Z
  1. Tüm yineleyiciler, yalnızca jeneratör işlevleri tarafından üretilenleri değil, yalnızca bir kez yinelenebilir. Bana inanmıyorsanız, yinelenebilir herhangi bir nesnede iter() numaralı telefonu arayın ve sonucu birden fazla kez yinelemeye çalışın.
    2015-03-03 22: 54: 31Z
  2. @ Craicerjack Terimlerinizi karıştırdınız. Yinelenebilir bir __iter__ yöntemiyle bir şeydir. Bir yineleyici, yinelenebilir bir numarada iter()'u aramanın sonucudur. Yineleyiciler yalnızca bir kez yinelenebilir.
    2015-08-24 20: 52: 18Z
  3. yield bu cevabın önerdiği kadar büyülü değil. Herhangi bir yerde yield ifadesi içeren bir işlevi çağırdığınızda, bir jeneratör nesnesi alırsınız, ancak kod çalıştırılmaz. Ardından, nesneyi jeneratörden her çıkardığınızda Python, yield deyimine gelinceye kadar işlevdeki kodu çalıştırır, sonra nesneyi duraklatır ve iletir. Başka bir nesneyi çıkardığınızda, Python yield'dan hemen sonra devam eder ve başka bir yield'a ulaşana kadar devam eder (genellikle aynı, ancak bir yineleme sonra). Bu, fonksiyon sona erene kadar devam eder, bu noktada jeneratörün bitmiş sayılır.
    2017-05-23 21: 41: 53Z
  4. "Bu yinelemeler kullanışlıdır ... ancak tüm değerleri bellekte saklarsınız ve bu her zaman istediğiniz değildir", ya yanlış ya da kafa karıştırıcıdır. Yinelemeli yineleyici üzerinde iter () işlevini çağırdığında yineleyici döndürür ve yineleyici, yineleyici yönteminin uygulanmasına bağlı olarak değerlerini her zaman bellekte depolamak zorunda kalmaz talep sırasındaki değerler.
    2018-02-15 19: 21: 11Z

yield’u anlama kısayolu

yield ifadeli bir işlev gördüğünüzde, ne olacağını anlamak için bu kolay numarayı uygulayın:

  1. İşlevin başına bir satır result = [] ekleyin.
  2. Her yield expr'u result.append(expr) ile değiştirin.
  3. İşlevin altına bir satır return result ekleyin.
  4. Yay - artık yield ifade yok! Kodu oku ve çöz.
  5. İşlevi orijinal tanımla karşılaştırın.

Bu numara, işlevin arkasındaki mantık hakkında bir fikir verebilir, ancak yield'da gerçekte olanlar, liste temelli yaklaşımda olanlardan önemli ölçüde farklıdır. Çoğu durumda, verim yaklaşımı çok daha fazla bellek verimli ve daha hızlı olacaktır. Diğer durumlarda, bu numara, orijinal işlev düzgün çalışsa bile sizi sonsuz bir döngüye sokar. Daha fazla bilgi edinmek için okumaya devam edin ...

Yineleyenleri, Yineleyicileri ve Jeneratörleri karıştırmayın

Öncelikle, yineleyici protokolü - yazarken

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

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

  1. mylist için bir yineleyici alır:

    iter(mylist) numaralı telefonu arayın - > bu, next() yöntemiyle (veya Python 3'te __next__()) bir nesne döndürür.

    [Bu, çoğu kişinin size anlatmayı unuttuğu adımdır]

  2. Öğeleri dolaşmak için yineleyiciyi kullanır:

    Yineleyicideki next() yöntemini çağırmaya devam edin, adım 1'den geri dönün.next() a atanır ve döngü gövdesi çalıştırılır. Bir istisna x, StopIteration içinden yükseltilirse, yinelemede başka değer olmadığı ve döngüden çıkıldığı anlamına gelir.

Gerçek şu ki Python, yukarıdaki iki adımı bir nesnenin içeriğine döngülemek istediği zaman uygular - yani bir döngü için olabilir, ancak next() gibi bir kod da olabilir (burada otherlist.extend(mylist) Python listesidir).

otherlist burada bir yinelenebilir çünkü yineleyici protokolünü uyguluyor. Kullanıcı tanımlı bir sınıfta, sınıfınızın örneklerini yinelenebilir hale getirmek için mylist yöntemini uygulayabilirsiniz. Bu yöntem bir yineleyici döndürmelidir. Bir yineleyici, __iter__() yöntemiyle bir nesnedir. next() ve __iter__()'un aynı sınıfa uygulanması ve next()'un __iter__()'u iade etmesi mümkündür. Bu, basit durumlar için işe yarar, ancak aynı anda iki nesne üzerinde aynı anda tekrarlanan iki yineleyici kullanmak istemezsiniz.

Yani yineleyici protokolüdür, birçok nesne bu protokolü uygular:

  1. Yerleşik listeler, sözlükler, dosyalar, kümeler, dosyalar.
  2. self'u uygulayan kullanıcı tanımlı sınıflar.
  3. Jeneratörler.

Bir __iter__() döngüsünün ne tür bir nesne ile uğraştığını bilmediğini unutmayın - yinelemeli protokolü izler ve for adındaki öğeden sonra öğeyi almaktan mutluluk duyar. Yerleşik listeler öğelerini birer birer döndürür, sözlükler tuşlarını birer birer döndürür, dosyalar satırları birer birer döndürür, vb. next()'un girdiği yer:

 yield

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item
ifadeler yerine, yield'da üç return ifadeniz olsaydı yalnızca birincisi çalıştırılır ve işlev kapanır. Fakat f123() sıradan bir işlev değil. f123() çağrıldığında, çıkmaz verim ifadelerindeki hiçbir değeri döndürmez! Bir jeneratör nesnesini döndürür. Ayrıca, işlev gerçekten çıkmıyor - askıya alınmış bir duruma geçiyor. f123() döngüsü, jeneratör nesnesi üzerinde dönmeye çalıştığında, işlev, daha önce geri döndüğü for'dan sonraki satırda askıya alınmış durumundan devam eder, bir sonraki kod satırını çalıştırır, bu durumda bir yield ifadesi ve bu değeri döndürür. sonraki öğe. Bu, fonksiyon çıkıncaya kadar gerçekleşir, bu noktada jeneratör yield yükseltir ve döngü kapanır.

Böylece jeneratör nesnesi bir adaptöre benzer - bir ucunda StopIteration döngüsünü mutlu etmek için __iter__() ve next() yöntemlerini göstererek yineleyici protokolünü gösterir. Bununla birlikte, diğer ucunda, bir sonraki değeri elde etmek için yeterli işlevi çalıştırır ve askıya alma moduna geri döndürür.

Neden Jeneratör Kullanmalıyım?

Genellikle jeneratörler kullanmayan ancak aynı mantığı uygulayan bir kod yazabilirsiniz. Bir seçenek daha önce bahsettiğim geçici liste 'hile' kullanmaktır. Örneğin, bu her durumda işe yaramayacaktır. Sonsuz döngüleriniz varsa veya çok uzun bir listeniz varsa hafızayı verimsiz kullanabilir. Diğer yaklaşım, durumlarını üye tutan ve for (veya Python 3'te SomethingIter) yönteminde bir sonraki mantıksal adımı gerçekleştiren next() sınıfını uygulamaktır. Mantığa bağlı olarak, __next__() yönteminin içindeki kod çok karmaşık görünebilir ve hatalara açık olabilir. Burada jeneratörler temiz ve kolay bir çözüm sunar.

    
1810
2019-04-24 09: 37: 06Z
  1. "Verim ifadeleri olan bir işlev gördüğünüzde, ne olacağını anlamak için bu kolay numarayı uygulayın" Bu, gerçeği tamamen görmezden gelmiyor mu? next()'u, jeneratörün noktalarının büyük bir parçası olan bir jeneratöre atabileceğinizi mi düşünüyorsunuz?
    2017-06-17 22: 41: 34Z
  2. "bu bir for döngüsü olabilir, ancak send gibi bir kod da olabilir" - > Bu yanlış. otherlist.extend(mylist) listeyi yerinde değiştirir ve yinelenebilir bir sonuç vermez. extend() üzerinde dönmeye çalışmak otherlist.extend(mylist) ile başarısız olur çünkü TypeError dolaylı olarak extend() döndürür ve None üzerinden dönemezsiniz.
    2017-09-14 14: 48: 17Z
  3. @ pedro Bu cümleyi yanlış anladın. Bu, python'un None'u çalıştırırken mylist'da (otherlist'da değil) belirtilen iki adımı gerçekleştirdiği anlamına gelir.
    2017-12-26 18: 53: 57Z

Bunu şu şekilde düşünün:

Bir yineleyici sadece bir fantezidirotherlist.extend(mylist) yöntemi olan bir nesne için sondaj terimi. Böylece, bir ed-ed işlevi şöyle bir şey olur:

Orijinal sürüm:

 next()

Bu temel olarak Python yorumlayıcısının yukarıdaki kodla yaptığı şeydir:

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

for i in some_function():
    print i

Sahne arkasında neler olup bittiğiyle ilgili daha fazla bilgi edinmek için

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
döngüsü buna yeniden yazılabilir:  for

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

Bunun bunun açıklayıcı amaçlar için aşırı basitleştirici olduğunu unutmamalıyım. :)

    
476
2019-05-07 13: 28: 35Z
  1. iterator = some_func()
    try:
        while 1:
            print iterator.next()
    except StopIteration:
        pass
    
    yerine __getitem__ tanımlanabilir. Örneğin: __iter__, Yazdırılacak: 0, 10, 20, ..., 90
    2008-10-25 02: 03: 38Z
  2. Bu örneği Python 3.6'da denedim ve class it: pass; it.__getitem__ = lambda self, i: i*10 if i < 10 else [][0]; for i in it(): print(i) oluşturursam, iterator = some_function() değişkeni artık iterator adlı bir işleve sahip değil, yalnızca next() işlevine sahip. Bahsettiğimi düşündüm.
    2017-05-06 14: 37: 55Z

__next__() anahtar sözcüğü iki basit gerçeğe indirgenmiştir:

  1. Derleyici, bir işlev içinde her yerde yield anahtar sözcüğünü algılarsa, bu işlev artık yield ifadesiyle geri dönmez. Insead , hemen , jeneratör adı verilen bir tembel "bekleyen liste" nesnesini döndürür
  2. Bir jeneratör yinelenebilir. yinelenebilir nedir? Her öğeyi belirli bir sırayla ziyaret etmek için yerleşik bir protokolle, return veya list veya set veya dict-view gibi bir şey olabilir.

Özetle: bir jeneratör tembel, aşamalı olarak bekleyen bir listedir ve range ifadeleri liste değerlerini programlamak için işlev gösterimini kullanmanıza izin verir tükürmek.

 yield

Örnek

Python'un

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]]
'u gibi bir işlev makeRange tanımlayalım. range numaralı telefonu arayın JENERATÖR BAŞLADI:  makeRange(n)

Jeneratörü bekleyen değerlerini hemen geri göndermeye zorlamak için

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>
'a aktarabilirsiniz (tıpkı yinelenebilir gibi):  list()

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

Yukarıdaki örnekte sadece eklediğiniz ve iade ettiğiniz bir liste oluşturduğu düşünülebilir:

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

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


Jeneratörleri nasıl kullanabilirsiniz?

Bir yinelenebilir, listenin kavranmasının son kısmıdır ve tüm üreticiler yinelenebilir, bu yüzden genellikle böyle kullanılırlar:

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

Jeneratörler için daha iyi hissetmek için

#                   _ITERABLE_
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]
modülüyle oynayabilirsiniz (garanti edildiğinde itertools yerine chain.from_iterable kullandığınızdan emin olun). Örneğin, chain gibi sonsuz uzun tembel listeleri uygulamak için jeneratörleri bile kullanabilirsiniz. Kendi itertools.count()'unuzu uygulayabilir veya alternatif olarak def enumerate(iterable): zip(count(), iterable) anahtar kelimesini while döngüsünde kullanabilirsiniz.

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


Perdelerin ardında

"Python yineleme protokolü" bu şekilde çalışır. Yani, yield yaptığınızda neler oluyor. Bu daha önce "tembel, artan bir liste" olarak tanımladığım şey.

 list(makeRange(5))

Yerleşik işlev

>>> 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
, yalnızca "yineleme protokolünün" bir parçası olan ve tüm yineleyicilerde bulunan nesneleri next() işlevini çağırır. .next() işlevini (ve yineleme protokolünün diğer bölümlerini), genellikle okunabilirlik pahasına olmak üzere, süslü şeyleri uygulamak için el ile kullanabilirsiniz, bu yüzden bunu yapmaktan kaçının ...

Minutiae

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

Python-speak'te bir yinelenebilir , bir liste next() gibi "bir for döngüsü kavramını anlayan" herhangi bir nesnedir ve bir yineleyici belirli bir örnektir [1,2,3] gibi istenen döngü için. Bir jeneratör , yazılma şekli dışında (işlev sözdizimiyle), tüm yineleyicilerle tamamen aynıdır.

Listeden bir yineleyici istediğinde, yeni bir yineleyici oluşturur. Ancak, bir yineleyiciden (nadiren yapacağınız) bir yineleyici isteğinde bulunduğunuzda, yalnızcakendisi.

Böylece, olası bir durumda, böyle bir şey yapmayı başaramazsınız ...

 [1,2,3].__iter__()

... sonra bir jeneratörün bir yineleyici olduğunu unutmayın; yani, bir kerelik kullanımdır. Tekrar kullanmak istiyorsanız,

> x = myRange(5)
> list(x)
[0, 1, 2, 3, 4]
> list(x)
[]
'u tekrar aramalısınız. Sonucu iki kez kullanmanız gerekirse, sonucu bir listeye dönüştürün ve myRange(...) değişkeninde saklayın. Bir jeneratörü kesinlikle klonlamak isteyenler (örneğin, korkunç şekilde metaprogramlamayı hackleyenler) x = list(myRange(5)) , kodlu Python yinelenen yineleyiciden beri PEP standartların teklifi ertelenmiştir.     
402
2017-03-19 08: 07: 35Z
  

itertools.tee anahtar sözcüğü Python'da ne yapar?

Cevap Anahat /Özet

  • Çağrıldığında yield ile bir işlev , bir Jeneratör 'ü döndürür.
  • Jeneratörler yineleyicilerdir çünkü yineleyici protokolü üçlü> , böylece onların üzerinde yineleme yapabilirsiniz.
  • Bir oluşturucu aynı zamanda bilgi gönderilir olabilir, bu da kavramsal olarak bir koroin olmasını sağlar.
  • Python 3'te, yield ile her iki yönde bir jeneratörden diğerine yetki verebilirsiniz .
  • (Ek, birincisi de dahil olmak üzere birkaç yanıtı eleştirir ve yield from'un bir jeneratörde kullanımını tartışır.)

Jeneratörler:

return yalnızca bir işlev tanımının yasal olanıdır ve bir işlev tanımına yield'un dahil edilmesi onu bir jeneratör döndürür.

Jeneratörler için fikir, farklı uygulamalara sahip diğer dillerden (bkz. dipnot 1) geliyor. Python'un Jeneratörlerinde, kodun yürütülmesi, kodun donmuş şeklindedir. verim noktası. Jeneratör çağrıldığında (yöntemler aşağıda tartışılmıştır), yürütme devam eder ve bir sonraki verimle donar.

yield’da bir Aşağıdaki iki kişi tarafından tanımlanan, yineleyici protokolünü uygulama işlemlerinin kolay yolu yöntemleri:  yield ve __iter__ (Python 2) veya next (Python 3). Bu yöntemlerin ikisi de __next__ Abstract Base ile yazabileceğiniz bir nesneyi yineleyici yapın Iterator modülünden sınıf.

 collections

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

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

Gerekirse şöyle kontrol edebiliriz:

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

>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True
numaralı bir bitkin bir durumdur özelliği yeniden kullanmayın veya sıfırlayın:  Iterator

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

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

Bir kişi programlı olarak veri üretebilir, örneğin:

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

Yukarıdaki basit jeneratör ayrıca aşağıdakine de eşittir - Python 3.3'ten itibaren (ve Python 2'de bulunmaz),

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

 yield from

Ancak,

def func(an_iterable):
    yield from an_iterable
, alt üreticiler için delegasyona izin veriyor, Aşağıdaki bölümde alt koroinler ile kooperatif delegasyonu açıklanacaktır.

Eşyordamlar:

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

İşte bir örnek, yield değişkenine dikkat edin; bu, jeneratöre gönderilen verileri gösterir:

 received

İlk önce, jeneratörü yerleşik işleviyle sıraya sokmalıyız,

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)
. Olacak sürümüne bağlı olarak uygun next veya next yöntemini çağırın Kullanmakta olduğunuz Python:

 __next__

Ve şimdi jeneratöre veri gönderebiliriz. (

>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0
gönderiliyor None’u aramakla aynı şey .):

 next

>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5
ile Alt Coroutine İşbirliği Heyeti

Şimdi, yield from'un Python 3'te bulunduğunu hatırlayın. Bu, yetki vermemize izin verirbir alt coutine giden coroutinler:

 yield from

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

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

>>> 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
’un kesin anlambilimi hakkında daha fazla bilgiyi PEP 380 .

Diğer Yöntemler: kapat ve fırlat

yield from yöntemi, işlev noktasında close'u yükseltir infaz donduruldu. Bu GeneratorExit tarafından da aranacak __del__’u işlediğiniz herhangi bir temizleme kodunu koyabilirsiniz:

 GeneratorExit

Ayrıca, jeneratörde işlenebilecek bir istisna da atabilirsiniz veya kullanıcıya geri gönderilir:

 
>>> my_account.close()

Sonuç

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

  

>>> 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
anahtar sözcüğü Python'da ne yapar?

yield’un çok şey yaptığı ortaya çıktı. Daha fazlasını ekleyebileceğime eminim buna ayrıntılı örnekler. Daha fazla istiyorsan veya yapıcı bir eleştirin varsa, yorum yaparak bana bildir. Aşağıda.


Ek:

En İyi /Kabul Edilen Yanıtın Eleştirisi **

  • Yalnızca bir listeyi kullanarak, yinelenebilir hale getiren şey ile karıştırılıyor. Yukarıdaki referanslarıma bakın, ancak özet olarak: yinelemeli bir yineleyici döndüren yield yöntemine sahiptir. Bir yineleyici , __iter__ döngüler tarafından .next yükseltinceye kadar dolaylı olarak adlandırılan .__next__ (Python 2 veya for (Python 3) yöntemi sağlar ve bir kez daha yapmaya devam eder.
  • Daha sonra bir jeneratörün ne olduğunu açıklamak için bir jeneratör ifadesi kullanır. Bir jeneratör, bir yineleyici oluşturmak için uygun bir yol olduğundan, yalnızca konuyu karıştırır ve biz hala StopIteration bölüme gelmedik.
  • Bir jeneratörün tükenmesini kontrol etme bölümünde yield yöntemini çağırır; bunun yerine yerleşik işlev olan .next'u kullanması gerekir. Kodu Python 3'te çalışmadığı için uygun bir dolaylı katman olacaktır.
  • Itertools? Bu, next'un yaptıkları ile ilgili değildi.
  • yield'un Python 3'teki yeni işlevsellik yield ile birlikte sağladığı yöntemlerle ilgili tartışma yok. En üstte /kabul edilen cevap çok eksik bir cevaptır.

Bir üretici ifadesinde veya anlamada yield from'u öneren yanıtın eleştirisi.

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

 yield

Verim bir ifade olduğu için, bazıları tarafından, anlama veya üreteç ifadesinde kullanması ilginçtir, özellikle de iyi bir kullanım durumu olmamasına rağmen.

CPython çekirdek geliştiricileri ödeneğinin kaldırılmasını tartışıyor . İşte posta listesinden alakalı bir yazı:

  

30 Ocak 2017'de saat 19: 05'te Brett Cannon şunu yazdı:

     
    

Güneş, 29 Ocak 2017, 16:39 Craig Rodrigues yazdı:

         
      

Her iki yaklaşımda da iyiyim. Şeyleri Python 3'teki gibi bırakma       iyi değil, IMHO.

    
         

Benim oyum, beklediğiniz şeyi alamadığınızdan beri bir SyntaxError olması.     sözdizimi.

  
     

Herhangi bir kod olarak, bizim için son bulmamızın makul bir yer olduğuna katılıyorum.   Mevcut davranışa güvenmek gerçekten de çok akıllıca   sürdürülebilir.

     

Oraya ulaşmak açısından muhtemelen isteyeceğiz:

     
  • 3.7'de Sözdizimi Uyarısı veya Kullanımdan Kaldırma Uyarısı
  •   
  • 2.7.x’de Py3k uyarısı
  •   
  • 3.8’de SyntaxError
  •   

Şerefe Nick.

     

- Nick Coghlan | gco şirketinde ncoghlan | Brisbane, Avustralya

Dahası, bunun yönünde işaret eden bir bekleyen sorun (10544) var asla iyi bir fikir olmak (Python'da yazılmış bir Python uygulaması olan PyPy, zaten sözdizimi uyarıları yükseltiyor.)

CPython geliştiricileri bize başka bir şey söyleyene kadar alt satırda:

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
'u bir üretici ifadesine veya anlama konusuna koymayın.

Jeneratördeki yield deyimi

Python 2 'de:

  

Bir jeneratör fonksiyonunda, return deyiminin return içermesine izin verilmez. Bu bağlamda, çıplak bir expression_list, jeneratörün yapıldığını ve return'un yükseltilmesine neden olacağını gösterir.

Bir StopIteration, temelde virgüllerle ayrılmış herhangi bir sayıdır - temelde Python 2'de, jeneratörü expression_list ile durdurabilirsiniz, ancak bir değer döndüremezsiniz.

Python 3’de:

  

Bir jeneratör fonksiyonunda, return deyimi, jeneratörün yapıldığını ve return'un yükselmesine neden olacağını gösterir. Dönen değer (varsa) StopIteration'u oluşturmak için bir argüman olarak kullanılır ve StopIteration niteliği olur.

Dipnotlar

  1. Teklifte CLU, Sather ve Icon dillerine referans verildi Jeneratörler kavramını Python'a tanıtmak. Genel fikir bir fonksiyonun dahili durumunu koruyabileceği ve ara verim sağlayabileceği kullanıcı tarafından talep üzerine veri noktaları. Bu, performansta üstün olacağına söz verdi Bazı sistemlerde bile bulunmayan Python iş parçacığı dahil diğer yaklaşımlara .

  2. Bu, örneğin, StopIteration.value nesnenin (Python 3'te xrange) range s olmadığı, tekrarlanabilir olmalarına rağmen, yeniden kullanılabildikleri için değildir. Listeler gibi, Iterator yöntemleri yineleyici nesnelerini döndürür.

  3. __iter__ başlangıçta bir ifade olarak sunuldu, yani yalnızca bir kod bloğundaki bir satırın başında görünebilirdi. Şimdi yield bir verim ifadesi oluşturuyor. https://docs.python.org/2/reference/simple_stmts .html # dilbilgisi-belirteci yield_stmt Bu değişiklik izin vermek için önerildi oldu bir jeneratöre veri göndermek için bir kullanıcı biri onu alabilir. Veri göndermek için, kişinin bir şeye ataması gerekir ve bunun için bir açıklama işe yaramayacak.

321
2017-12-06 20: 20: 40Z

yield tam olarak yield'a benzer - ne söylerseniz verir (bir jeneratör olarak). Fark, bir dahaki sefere jeneratörü çağırdığınızda, yürütmenin son çağrıdan return cümlesine kadar başlamasıdır. Dönüşten farklı olarak, getiri meydana geldiğinde küme çerçevesi temizlenmez, ancak kontrol arayana geri gönderilir, böylece işlev bir daha çağrıldığında durumu devam eder.

Kodunuzda yield işlevi bir yineleyici gibi işlev görür, böylece listenizi genişlettiğinizde yeni listeye birer birer öğe ekler.

get_child_candidates tükenene kadar bir yineleyici çağırır. Gönderdiğiniz kod örneğinde, sadece bir tuple döndürüp listeye eklemek çok daha net olacaktır.

    
276
2019-01-24 09: 39: 59Z
  1. Bu yakın, ancak doğru değil. Bir verim ifadesine sahip bir işlevi her çağırdığınızda, yeni bir jeneratör nesnesi döndürür. Yalnızca bu üretecin .next () yöntemini çağırdığınızda, yürütme son verimden sonra devam eder.
    2008-10-24 18: 11: 04Z

Söylenecek ilave bir şey var: Getiri sağlayan bir fonksiyonun aslında sonlandırılması gerekmiyor. Böyle bir kod yazdım:

 list.extend

O zaman bunu başka bir kodla kullanabilirim:

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

Gerçekten bazı sorunların basitleştirilmesine yardımcı olur ve bazı şeylerin birlikte çalışmasını kolaylaştırır.

    
209
2013-04-21 15: 42: 14Z

Minimal bir çalışma örneği tercih edenler için, bu etkileşimli Python oturumunda meditasyon yapın:

 
for f in fib():
    if some_condition: break
    coolfuncs(f);
    
184
2019-05-08 12: 20: 10Z

TL; DR

Bunun yerine:

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

bunu yapın:

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

Sıfırdan bir liste oluştururken, her bir parça yerine

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

Bu benim verim ile ilk "aha" anımdı.


yield, bir şekerli söyleme yolu

  

bir dizi şey oluşturun

Aynı davranış:

 yield

Farklı davranış:

Verim, tek geçişli dir: yalnızca bir kez yinelenebilir. Bir işlevin içinde bir verim olduğunda, buna jeneratör işlevi diyoruz. Ve bir yineleyici döndürür. Bu terimler ortaya çıkıyor. Bir konteynerin rahatlığını kaybediyoruz, ancak gerektiğinde hesaplanan bir dizinin gücünü ve keyfi olarak kazanıyoruz.

Kazanç tembeldir , hesaplamayı durdurur. 'de verimi olan bir işlev aslında onu çağırdığınızda hiç çalışmaz. Bir bıraktığı yeri hatırlayan yineleyici nesnesi . Yinelemede

>>> for square in square_list(4):
...     print(square)
...
0
1
4
9
>>> for square in square_yield(4):
...     print(square)
...
0
1
4
9
'u her aradığınızda (bu for-loop'larda gerçekleşir) bir sonraki verime ilerleyen inç. next(), StopIteration'ı yükseltir ve diziyi sonlandırır (bu, for döngüsünün doğal sonu).

Kazanç çok yönlüdür . Verilerin hep birlikte depolanması gerekmemektedir, her seferinde bir tane kullanılabilir hale getirilebilir. Sonsuz olabilir.

 return

çoklu geçişlere ihtiyacınız varsa ve seri çok uzun değilse, bunun için

>>> 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
’u arayın:  list()

>>> list(square_yield(4))
[0, 1, 4, 9]
sözcüğünün mükemmel seçimi, çünkü her iki anlama da uyar:
  

verim - üretin veya sağlayın (tarımda olduğu gibi)

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

  

verim - yol verin veya vazgeçmeyin (siyasi iktidarda olduğu gibi)

... yineleyici ilerleyene kadar CPU yürütme işleminden vazgeç.

    
174
2019-01-04 15: 30: 21Z

Verim size bir jeneratör verir.

 yield

Gördüğünüz gibi, ilk durumda

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
listenin tamamını aynı anda bellekte tutuyor. 5 elementli bir liste için büyük bir sorun değil, peki ya 5 milyonluk bir liste istiyorsan? Bu sadece çok büyük bir bellek yiyici değil, aynı zamanda işlevin çağrıldığı anı oluşturmak için de çok zaman alıyor.

İkinci durumda, foo sadece size bir jeneratör verir. Bir jeneratör yinelenebilir - bu, bar döngüde vb. Kullanabileceğiniz anlamına gelir, ancak her değere yalnızca bir kere erişilebilir. Tüm değerler aynı zamanda bellekte de saklanmaz; Jeneratör nesnesi, onu en son ne zaman aradığınızda döngüdeyken nerede olduğunu "hatırlar" - bu yolla, (yani) 50 milyar'a kadar sayılabilir bir sayı kullanıyorsanız, 50 milyara kadar saymanız gerekmez. hemen ve saymak için 50 milyar numarayı saklayın.

Yine, bu oldukça tartışmalı bir örnek, gerçekten 50 milyar sayarsanız, muhtemelen itertools kullanırsınız. :)

Bu, jeneratörlerin en basit kullanım şeklidir. Söylediğiniz gibi, bir çeşit yığın değişkeni kullanmak yerine arama yığınında bir şeyleri yukarı itmek için verimi kullanarak verimli permütasyonlar yazmak için kullanılabilir. Jeneratörler ayrıca özelleştirilmiş ağaç geçişi ve diğer her türlü şey için de kullanılabilir.

    
160
2019-03-13 06: 04: 08Z
  1. Sadece bir not - Python 3, for'da ayrıca liste yerine bir jeneratör döndürür, böylece range /__repr__'un geçersiz kılınması dışında da benzer bir fikir görürsünüz daha iyi bir sonuç göstermek için bu durumda __str__.
    2019-03-21 18: 33: 13Z

Bir jeneratör döndürüyor. Python'a özellikle aşina değilim, ancak bunun C # 'in yineleyici blokları ile aynı türde olduğuna inanıyorum. /a> bunlara aşina iseniz.

Temel fikir, derleyici /tercüman /bazı hileler yaparsa, arayan kişi söz konusu olduğunda, bir sonraki aramaya devam edebileceklerdir () ve gene de jeneratörü metodu sanki değerleri döndürmeye devam edecektir - durduruldu. Şimdi açıkçası bir yöntemi gerçekten "duraklatamazsınız", böylece derleyici nerede olduğunuzu hatırlamanız için bir durum makinesi oluştururşu anda yerel değişkenler vb. Bu, bir yineleyici yazmaktan çok daha kolaydır.

    
153
2018-10-31 08: 42: 59Z

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

Python'daki range(1, 10, 2) ifadesi bir jeneratör döndürür. Python'daki bir üretici, sürekliliği (ve özellikle de bir tür coroutine döndüren) döndüren bir işlevdir;

Programlama dilleri teorisindeki süreklilikler çok daha temel bir hesaplama türüdür, ancak sıklıkla kullanılmazlar, çünkü bunun için çok zor ve bunların uygulanması çok zordur. Ancak, devam etmenin ne olduğu fikri basit ve açıktır: henüz bitmemiş bir hesaplamanın halidir. Bu durumda, değişkenlerin mevcut değerleri, henüz gerçekleştirilmemiş olan işlemler ve benzerleri kaydedilir. Ardından programın ilerleyen noktalarında programın değişkenleri bu duruma sıfırlanacak ve kaydedilen işlemler gerçekleştirilebilecek şekilde devam etme çağrılabilir.

Devam etmeler, bu daha genel biçimde, iki şekilde uygulanabilir. yield yolunda, programın yığını tam anlamıyla kaydedilir ve sonra devam başlatıldığında, yığın geri yüklenir.

Devam eden geçiş stilinde (CPS), süreklilikler, programlayıcı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şkenlerden ziyade kapaklar (ve bunlarda kodlanmış olan değişkenler) ile temsil edilir. Kontrol akışını yöneten fonksiyonlar devam etmeyi argüman olarak kabul eder (bazı CPS varyasyonlarında, fonksiyonlar birden fazla devam etmeyi kabul edebilir) ve kontrol akışını basitçe onları çağırarak ve daha sonra geri döndürerek değiştirerek işler. Devamlı geçiş stilinin çok basit bir örneği şöyledir:

 call/cc

Bu (çok basit) örnekte, programcı dosyayı fiilen bir süreklilik içine yazma işlemini kaydeder (bu da potansiyel olarak çok sayıda ayrıntı içeren çok karmaşık bir işlem olabilir) ve ardından bu sürekliliği geçer (yani, biraz daha fazla işlem yapan başka bir operatöre birinci sınıf bir kapanış) ve sonra gerekirse çağırır. (Bu tasarım desenini gerçek GUI programlamasında çok 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, devam etmeyi CPS olarak kavramlaştırır, çünkü anlaşılması ve okunması çok daha kolaydır.


Şimdi Python'daki jeneratörlerden bahsedelim. Jeneratörler, devam etmenin belirli bir alt tipidir. Devam etmeler genel olarak bir hesaplama durumunu (örneğin, programın arama yığınını) kaydedebiliyorken, üreticiler yalnızca yineleme durumunu kaydedebilirler. bir yazarlar . Bununla birlikte, bu tanım jeneratörlerin belirli kullanım durumları için biraz yanıltıcıdır. Örneğin:

 
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 açıkça, davranışları iyi tanımlanmış olan makul bir yinelemelidir - jeneratör her yinelendiğinde, 4 değerini döndürür (ve bunu sonsuza dek yapar). Ancak, muhtemelen tekrarlayıcıları düşünürken akla gelen, prototipik bir tekrarlanabilir tip değildir (yani,

def f():
  while True:
    yield 4
). Bu örnek, jeneratörlerin gücünü gösterir: eğer bir şey bir yineleyici ise, bir jeneratör yinelemesinin durumunu kaydedebilir.

Yinelemek için: Devam etmeler, bir program yığınının durumunu kaydedebilir ve jeneratörler yineleme durumunu kaydedebilir. Bu, sürekliliğin jeneratörlerden çok daha güçlü olduğu, ancak jeneratörlerin çok, çok daha kolay olduğu anlamına gelir. Dil tasarımcısının uygulaması kolaydır ve programcının kullanması daha kolaydır (yazmak için biraz zamanınız varsa, süreklilik ve çağrı hakkında bu sayfa /cc ).

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

Her for x in collection: do_something(x) ne zaman çağrılırsa, işleve devam etmesini bildirir. İşlev tekrar çağrıldığında, bıraktığı yerden başlar. Dolayısıyla, sözde-sözde kodda (yani, sözde kod değil, ancak kod değil), jeneratörün yield yöntemi temel olarak aşağıdaki gibidir:

 next

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
anahtar kelime, gerçek üreteç işlevi için aslında sözdizimsel şekerdir;  yield

Unutmayın ki bu sadece sahte koddur ve jeneratörlerin Python'daki gerçek uygulaması daha karmaşıktır. Ancak, neler olup bittiğini anlama alıştırması olarak,

def generatorfun(iterable):
  if len(iterable) == 0:
    raise StopIteration
  else:
    return (iterable[0], lambda:generatorfun(iterable[1:]))
anahtar sözcüğünü kullanmadan jeneratör nesnelerini uygulamak için sürekli geçiş stilini kullanmaya çalışın.     
140
2018-05-20 10: 25: 32Z

İşte düz dilde bir örnek. Üst düzey insan kavramları ile düşük seviyeli Python kavramlarına yazışma sağlayacağım.

Bir dizi dizide işlem yapmak istiyorum, ancak bu dizinin oluşturulmasıyla kendimi rahatsız etmek istemiyorum, sadece yapmak istediğim işleme odaklanmak istiyorum. Bu yüzden, aşağıdakileri yapıyorum:

  • Seni ararım ve size belirli bir şekilde üretilen bir sayı dizisi istediğimi söylerim ve algoritmanın ne olduğunu size bildiririm.
    Bu adım, yield, jeneratör işlevini, yani def içeren işlevi başlatır.
  • Bir süre sonra size, "Tamam, bana sayıların sırasını anlatmaya hazırlanın" dedim.
    Bu adım, bir jeneratör nesnesini döndüren jeneratör işlevini çağırmaya karşılık gelir. Bana henüz herhangi bir sayı söylemediğinizi unutmayın; Sadece kağıt ve kalemini al.
  • "Bana bir sonraki numarayı söyle" diye soruyorum, ve sen bana ilk numarayı söyle ondan sonra, bir sonraki numarayı sormamı beklersin. Nerede olduğunuzu, daha önce hangi sayıları söylediğinizi ve bir sonraki sayının ne olduğunu hatırlamak sizin işiniz. Detaylar umurumda değil.
    Bu adım, jeneratör nesnesinde yield numarasının aranmasına karşılık gelir.
  • … bir önceki adımı tekrar edin,… başa kadar
  • sonunda bir sona gelebilir. Bana bir numara söylemiyorsun; sadece bağırdın, "atlarını tut! İşim bitti! Artık numara yok!"
    Bu adım, işini bitiren ve bir .next() istisna oluşturduğu jeneratör nesnesine karşılık gelir Jeneratör işlevinin istisnayı arttırması gerekmez. İşlev sona erdiğinde veya bir StopIteration yayınladığında otomatik olarak kaldırılır.

Bu, jeneratörün yaptığı şeydir (return içeren bir işlev); çalışmaya başlar, yield yaptığında duraklar ve yield değeri istendiğinde en son olduğu yerden devam eder. Python'un sıralı olarak nasıl değer talep edeceğini açıklayan yineleyici protokolü ile tasarıma mükemmel uyum sağlar.

Yineleyici protokolünün en ünlü kullanıcısı Python'daki .next() komutudur. Bu yüzden ne zaman bir yaparsanız:

 for

for item in sequence:
'un yukarıda açıklandığı gibi bir liste, bir dize, sözlük veya bir üretici nesnesi olması önemli değildir; sonuç aynıdır: bir sıradaki öğeleri tek tek okuyorsunuz.

sequence'un, def anahtar sözcüğü içeren bir işlev başlatmasının, jeneratör oluşturmanın tek yolu olmadığını unutmayın; bu, oluşturmanın en kolay yoludur.

Daha doğru bilgi için yineleyici türleri , sonuç bildirimi ve jeneratörler .

    
126
2018-05-20 10: 06: 05Z

Pek çok cevap bir jeneratör oluşturmak için neden yield kullandığınızı gösterirken, yield için daha fazla kullanım alanı vardır. Bilginin iki kod bloğu arasında aktarılmasını sağlayan bir coroutine yapmak oldukça kolaydır. Jeneratör oluşturmak için yield'un kullanılmasıyla ilgili olarak verilen iyi örneklerin hiçbirini tekrarlamayacağım.

Bir yield’un aşağıdaki kodda ne yaptığını anlamanıza yardımcı olmak için, yield’a sahip herhangi bir kodda çevrimi izlemek için parmağınızı kullanabilirsiniz. Parmağınız yield'a her ulaştığında, yield veya next'un girilmesini beklemeniz gerekir. Bir send çağrıldığında, next'a ulaşana kadar kodu izlersiniz… yield'un sağındaki kod değerlendirilir ve arayana geri gönderilir… sonra beklersiniz. yield tekrar arandığında, kodda başka bir döngü gerçekleştirirsiniz. Bununla birlikte, bir koroinde next'un yield ile de kullanılabileceğini unutmayın ki bu, arayan işlevinden yerine bir değer gönderir. send verilirse, send gönderilen değeri alır ve tükürür.sol taraftan… sonra yield'a tekrar basana kadar kod boyunca iz ilerler (sonunda değeri yield çağrılmış gibi döndürür).

Örneğin:

 next     
110
2014-02-04 02: 27: 35Z
  1. Şirin! Bir trambolin (Lisp anlamında). Bunları pek sık görmüyor!
    2015-12-04 18: 31: 17Z

Başka bir

>>> 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()
kullanım ve anlamı var (Python 3.3'ten beri):  yield

'den PEP 380 - Bir Alt Jeneratörü Temsil Etme Sözdizimi :

  

Bir jeneratörün faaliyetlerinin bir bölümünü başka bir jeneratöre devretmesi için bir sözdizimi önerilmiştir. Bu, 'verim' içeren bir kod bölümünün dışarı çıkarılıp başka bir jeneratöre yerleştirilmesine izin verir. Ek olarak, alt üretecin bir değerle dönmesine izin verilir ve değer, temsilci oluşturucu için kullanılabilir duruma gelir.

     

Yeni sözdizimi, bir jeneratör başkaları tarafından üretilen değerleri yeniden ürettiğinde optimizasyon için bazı fırsatlar da açar.

Ayrıca bu tanıtılacak (Python 3.5'ten beri):

 
yield from <expr>

Korotinlerin normal bir jeneratör ile karıştırılmasını önlemek için (bugün her ikisi de

async def new_coroutine(data):
   ...
   await blocking_action()
kullanılmaktadır)     
102
2018-05-20 10: 34: 03Z

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

yield bildirimini öğrendiğinizi varsayıyorum.

Bir analoji olarak, return ve return ikizlerdir. yield “geri dönüş ve dur” anlamına gelirken, “ürün” “geri dönüş, ancak devam” anlamına gelir.

  
  1. return ile num_list almayı deneyin.
  2.   
 return

Çalıştır:

 
def num_list(n):
    for i in range(n):
        return i

Gördüğünüz gibi, bir liste yerine yalnızca bir numara alıyorsunuz.

In [5]: num_list(3)
Out[5]: 0
Asla mutlu olmana izin vermez, sadece bir kere uygular ve çıkar.
  
  1. return geliyor
  2.   

yield'u return ile değiştirin:

 yield

Şimdi, tüm sayıları almaya hak kazanıyorsunuz.

Bir kez çalışan ve duran

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]
ile karşılaştırıldığında, planladığınız return kez çalışır. yield'u return ve return one of them'u yield olarak yorumlayabilirsiniz. Buna return all of them denir.
  
  1. iterable ile yield deyimini yeniden yazabiliriz
  2.   
 return

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]
'un çekirdeği.

Bir liste yield çıktısı ile nesne return çıktısı arasındaki fark şudur:

Bir liste nesnesinden her zaman [0, 1, 2] alırsınız, ancak bunları yalnızca 'nesne yield çıktısı' ndan bir kez alırsınız. Bu nedenle, yield'da görüntülenen generator nesnesinin yeni bir adı var.

Sonuç olarak, onu parçalamak için bir metafor olarak:

  •  Out[11]: <generator object num_list at 0x10327c990> ve return ikizlerdir
  •  yield ve list ikizlerdir
88
2018-05-28 09: 06: 22Z
  1. Bu anlaşılabilir bir durumdur, ancak en büyük fark, bir işlev /yöntemde birden fazla verim elde etmeniz olabilir. Analoji o noktada tamamen bozuluyor. Verim, bir işlevdeki yerini hatırlar, bu nedenle bir sonraki çağrışınızda (), işleviniz bir sonraki generator'da devam eder. Bu önemlidir, bence ve ifade edilmeli.
    2018-08-23 13: 27: 21Z

İşte Python, sanki Python onlar için sözdizimsel şeker sağlamıyormuş gibi uygulayacağına dair bazı Python örnekleri:

Python oluşturucu olarak:

 yield

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

 
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 nesne kapanışlarını kullanma (çünkü ClosuresAndObjectsAreEquivalent )

 
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)
    
86
2017-10-24 10: 46: 05Z

"Jeneratörlerin hızlı bir açıklaması için Beazley'in" Python: Essential Reference "sayfasını 19. sayfasını okuyacağım" yazacaktım, ancak çoğu kişi zaten iyi açıklamalar yaptı.

Ayrıca,

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)
'un coutinler içinde jeneratör işlevlerinde kullanımlarının ikili olarak kullanılabileceğini unutmayın. Kod snippet'inizle aynı kullanım olmasa da, yield bir işlevde ifade olarak kullanılabilir. Bir arayan (yield) yöntemini kullanarak yönteme bir değer gönderdiğinde, coroutine bir sonraki send() deyimiyle karşılaşana kadar yürütülür.

Jeneratörler ve koroinler veri akışı türü uygulamaları ayarlamak için harika bir yoldur. (yield) deyiminin işlevlerde diğer kullanımları hakkında bilgi sahibi olmanın faydalı olacağını düşündüm.

    
82
2013-01-28 01: 37: 10Z

Bir programlama bakış açısından, yineleyiciler thunks olarak uygulanır.

Eşzamanlı çalıştırma vb. için yineleyiciler, jeneratörler ve iş parçacığı havuzlarını eşyalar olarak (anonim işlevler olarak da bilinir) uygulamak için, kişi bir dağıtıcıya sahip olan bir kapatma nesnesine gönderilen iletilerden yararlanır ve gönderici "iletilere" yanıt verir.

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

" next ", " iter " araması tarafından oluşturulan bir kapanmaya gönderilen bir mesajdır.

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

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

 yield
    
80
2018-05-20 10: 29: 34Z

İşte basit bir örnek:

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

Çıktı:

 
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)

Bir Python geliştiricisi değilim, fakat bana öyle görünüyor ki

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
program akışının pozisyonunu koruyor ve bir sonraki döngü "verim" konumundan başlıyor. Bu pozisyonda bekliyor gibi görünüyor ve bundan hemen önce dışarıya bir değer döndürmek ve bir dahaki sefere çalışmaya devam ediyor.

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

    
72
2018-05-20 10: 31: 01Z
  1. 2'nin asal olduğunu unutma :-)
    2018-07-02 01: 20: 23Z
  2. Haklısın. Fakat “verim” davranışını görmek için akış üzerindeki etki nedir? Algoritmayı matematik adına değiştirebilirim. Farklı bir "verim" değerlendirmesi almak yardımcı olur mu?
    2018-07-02 01: 44: 08Z

İşte yield'un yaptıklarının zihinsel bir görüntüsü.

Bir iş parçacığının bir yığına sahip olduğunu düşünmeyi seviyorum (bu şekilde uygulanmadığında bile).

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

Bir yield işleviyle, kodu çalışmaya başladığında (yani işlev çağrıldıktan sonra, yield yöntemi daha sonra çağrılan bir jeneratör nesnesini döndürerek), benzer şekilde yerel değişkenlerini yığına koyar ve bir süre hesaplar. Ama sonra, next() deyimine ulaştığında, yığının bir bölümünü temizlemeden ve geri dönmeden önce, yerel varyasının bir görüntüsünü alır.bunları temizler ve jeneratör nesnesine depolar. Ayrıca şu anda kodunda bulunduğu yeri de yazar (örneğin, belirli yield ifadesi).

Yani jeneratörün üzerinde durduğu donmuş bir işlev.

Daha sonra yield çağrıldığında, işlevin eşyalarını yığına alır ve yeniden canlandırır. Bu işlev, kaldığı yerden hesaplamaya devam ediyor, soğuk hava deposunda sonsuzluk harcadığı gerçeğini habersiz.

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

 next()

İkinci işlevi çağırdığımızda, birinciden çok farklı davranır.

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12
deyimine ulaşılamayabilir, ancak herhangi bir yerde varsa, uğraştığımızın niteliğini değiştirir.  yield

>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>
’un aranması kodunu çalıştırmaz ancak bir kodu kodun dışında yapar. (Belki de bu tür şeyleri okunabilirlik için yielderFunction() öneki ile adlandırmak iyi bir fikirdir.)  yielder

>>> 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']
ve gi_code alanları dondurulmuş durumun kaydedildiği alanlardır. Onları gi_frame ile keşfederek, yukarıdaki zihinsel modelimizin güvenilir olduğunu onaylayabiliriz.     
59
2017-03-01 13: 36: 58Z

Her cevabın önerdiği gibi, dir(..) bir dizi üreteci oluşturmak için kullanılır. Dinamik olarak bir dizi oluşturmak için kullanılır. Örneğin, bir dosyayı bir ağ üzerinde satır satır okurken yield işlevini aşağıdaki gibi kullanabilirsiniz:

 yield

Kodunuzda aşağıdaki gibi kullanabilirsiniz:

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

Yürütme Kontrol Transferi gotcha

Uygulama kontrolü, verim uygulandığında getNextLines () 'den

for line in getNextLines():
    doSomeThing(line)
döngüye aktarılır. Bu nedenle, getNextLines () her çalıştırıldığında, yürütme en son duraklatıldığı yerden başlar.

Dolayısıyla, kısaca, aşağıdaki kodla bir işlev

 for

yazdırılacak

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

for i in simpleYield():
    print i
    
50
2018-05-20 10: 42: 59Z

Verim bir nesnedir

Bir işlevdeki

"first time"
"second time"
"third time"
"Now some useful value 12"
, tek bir değer döndürür.

çok fazla değer kümesi döndürecek bir işlev istiyorsanız, return'u kullanın.

Daha da önemlisi, yield bir engeldir .

  

CUDA dilindeki bariyer gibi, kontrolü alana kadar kontrolü transfer etmez   tamamladı.

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

Ardından, diğer tüm çağrılar işlevde yazdığınız döngüyü bir kez daha çalıştırır ve döndürülen herhangi bir değer bulunmayana kadar bir sonraki değeri döndürür.

    
44
2018-05-20 10: 45: 50Z

(Aşağıdaki cevabım, yalnızca Python jeneratörü kullanma perspektifinden bahseder, bazı yığın ve yığın manipülasyon hilelerini içeren, jeneratör mekanizmasının temelini oluşturan .)

Bir python işlevinde yield yerine yield kullanıldığında, bu işlev return adlı özel bir şeye dönüştürülür. Bu işlev generator function türünde bir nesne döndürür. generator anahtar sözcüğü, bu işlevi özellikle ele alması için python derleyicisine bildirimde bulunan bir bayraktır. Normal işlevler, ondan bir değer döndürüldüğünde sona erer. Ancak, derleyicinin yardımıyla, jeneratör işlevi devam ettirilebilir olarak düşünülebilir . Yani, yürütme içeriği geri yüklenecek ve yürütme son çalıştırmadan itibaren devam edecek. Açıkça bir geri dönüş çağrısı yapın; bu, yield istisnasını (ayrıca yineleyici protokolünün bir parçasıdır) oluşturur veya işlevin sonuna ulaşır. StopIteration ile ilgili birçok referans buldum, ancak generator’dan bu bir en sindirilebilir.

(Şimdi functional programming perspective'un ardındaki mantık ve kendi anlayışımı temel alan generator hakkında konuşmak istiyorum. Bunun yinelemenin temel motivasyonunu kavramanıza yardımcı olabilir. ve jeneratör: Bu kavram diğer dilde ortaya çıkıyorC # gibi) kullanımları.

Anladığım kadarıyla, bir sürü veriyi işlemek istediğimizde, genellikle verileri bir yerde saklar ve sonra birer birer işleriz. Ancak bu saf yaklaşım sorunludur. Veri hacmi çok büyükse, bunları önceden bir bütün olarak saklamak pahalıdır. Öyleyse, doğrudan iterator'u doğrudan saklamak yerine, neden dolaylı olarak bir tür data depolamıyorsunuz, yani metadata .

Bu meta verileri kaydırmak için 2 yaklaşım vardır.

  1. OO yaklaşımı, the logic how the data is computed meta verilerini kaydırıyoruz. Yineleyici protokolünü uygulayan as a class olarak adlandırılan budur (yani, iterator ve __next__() yöntemleri). Bu aynı zamanda yaygın olarak görülen yineleyici tasarım deseni 'dir.
  2. İşlevsel yaklaşım, __iter__() meta verilerini sarıyoruz. Bu sözde as a function. Ancak kaputun altında, geri gönderilen generator function hala generator object yineleyicidir, çünkü yineleyici protokolünü de uygular.

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

    
44
2018-11-23 01: 38: 59Z

Özetle, IS-A deyimi, işlevinizi orijinal işlevinizin etrafında saran, yield adlı özel bir nesne üreten bir fabrikaya dönüştürür. generator yinelendiğinde, işlevinizi bir sonraki generator'a ulaşana kadar yürütür, ardından yürütmeyi askıya alır ve yield'a iletilen değere göre değerlendirir. Yürütme yolu işlevden çıkana kadar bu işlemi her yinelemede tekrar eder. Örneğin,

 yield

basitçe çıktılar

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

for i in simple_generator():
    print i

Güç, bir sekansı hesaplayan bir döngü ile jeneratörü kullanmaktan gelir, jeneratör, bir sonraki hesaplamanın sonucunu 'vermek' için her seferinde durma döngüsünü çalıştırır, bu şekilde anında bir liste hesaplar. özellikle büyük hesaplamalar için kaydedilen hafızanın olması

Yinelenebilir sayı aralığı üreten kendi

one
two
three
işlevinizi oluşturmak istediğinizi söyleyin, böyle yapabilirsiniz.  range

ve bu şekilde kullanın;

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

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 o dizinin üzerine iki kez dönüyor! : (

Neyse ki Guido ve ekibi jeneratörler geliştirebilecek kadar cömert davrandılar ki bunu yapabildik;

 
for i in myRangeNaive(10):
    print i

Şimdi, her yinelemede,

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

for i in myRangeSmart(10):
    print i
adlı jeneratör üzerindeki bir işlev, işlevi durduğu ve değeri veren veya işlevin sonuna ulaşan bir "verim" ifadesine ulaşana kadar işlevi gerçekleştirir. Bu durumda, ilk çağrıda next(), verim ifadesine ve 'n' verimine kadar yürütür, bir sonraki çağrıda, artış ifadesini yürütür, 'süre'ye geri atlar, değerlendirir ve doğruysa durur ve tekrar 'n' vermesi, while koşulu yanlış dönene ve jeneratör fonksiyonun sonuna atlayana kadar bu şekilde devam edecektir.     
43
2018-05-20 11: 04: 36Z

Birçok kişi next() yerine return kullanır, ancak bazı durumlarda yield ile çalışmak daha verimli ve kolay olabilir.

yield'un kesinlikle en iyisi olduğu bir örnek:

  

iade et (işlevde)

 yield
  

verim (çalışır durumda)

 
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
  

Arama işlevleri

 
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.

Her iki işlev de aynı şeyi yapar, ancak

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)
, beş yerine üç satır kullanır ve endişelenecek bir daha az değişkene sahiptir.
  
    

Bu, kodun sonucudur:

  

 Çıktı

Gördüğünüz gibi her iki işlev de aynı şeyi yapıyor. Tek fark yield'un bir liste vermesi ve return_dates()'un bir jeneratör vermesidir.

Gerçek hayattan bir örnek, bir dosyayı satır satır okumak veya yalnızca bir jeneratör oluşturmak gibi bir şey olabilir.

    
41
2018-05-20 11: 02: 52Z

yield_dates(), bir işlev için bir dönüş elemanı gibidir. Aradaki fark, yield elemanının bir işlevi bir jeneratöre dönüştürmesidir. Bir jeneratör 'bir şey elde edilinceye kadar' bir fonksiyon gibi davranır. Jeneratör bir daha aranana kadar durur ve tam olarak aynı noktadan devam eder. yield numaralı telefonu arayarak, 'elde edilen' değerlerin tümünü bir sırada bulabilirsiniz.

    
36
2015-05-20 06: 19: 32Z

list(generator()) anahtar sözcüğü, yalnızca dönen sonuçları toplar. yield'u yield gibi düşünün

    
35
2016-04-23 03: 16: 19Z

İşte fibonacci serisini hesaplamak için return += tabanlı basit bir yaklaşım şöyle açıklandı:

 yield

Bunu REPL'inize girip ardından onu aramaya çalıştığınızda, gizemli bir sonuç alırsınız:

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

Bunun nedeni,

>>> fib()
<generator object fib at 0x7fa38394e3b8>
'un Python'a bir jeneratör , yani talep üzerine değer üreten bir nesne oluşturmak istediğinize işaret etmesidir.

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

Dahili next işlevini kullanarak, doğrudan üreteci bir değer üretmeye zorlayarak next() /.next'u doğrudan çağırırsınız:

 __next__

Dolaylı olarak,

>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5
- fib döngüsüne, for başlatıcısına, list başlatıcısına veya değer üreten /üreten bir nesne bekleyen başka bir şey sağlarsanız, daha fazla değer üretilinceye kadar jeneratörü "tüketeceksiniz" onunla (ve geri döner):  tuple

Benzer şekilde,

results = []
for i in fib(30):       # consumes fib
    results.append(i) 
# can also be accomplished with
results = list(fib(30)) # consumes fib
başlatıcı ile:  tuple

Bir jeneratör, tembel olduğu anlamındaki bir fonksiyondan farklıdır. Bunu, yerel durumunu koruyarak ve ihtiyaç duyduğunuzda sürdürmenize izin vererek gerçekleştirir.

İlk olarak

>>> tuple(fib(5))       # consumes fib
(1, 1, 2, 3, 5)
'u arayarak çağırdığınızda:  fib

Python işlevi derler,

f = fib()
anahtar sözcüğüyle karşılaşır ve yalnızca bir nesneyi geri döndürür. Çok yardımcı değil görünüyor.

Daha sonra doğrudan veya dolaylı olarak ilk değeri üretmesini talep ettiğinizde, bulduğu tüm ifadeleri, bir yield ile karşılaşıncaya kadar, yield'a verdiğiniz değeri geri verir ve duraklatır. Bunu daha iyi gösteren bir örnek için, bazı yield çağrıları kullanalım (Python 2'de ise print ile değiştirelim):

 print "text"

Şimdi, REPL’e girin:

 
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 bir değer üretmesi için bir komut bekleyen bir jeneratör nesnesine sahipsiniz.

>>> gen = yielder("Hello, yield!")
kullanın ve neyin basıldığını görün:  next

Belirtilmeyen sonuçlar yazdırılan sonuçlardır. Alınan sonuç

>>> 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!'
'dan döndürülen sonuçtur. Şimdi yield'u tekrar arayın:  next

Jeneratör

>>> 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!'
'da duraklatıldığını hatırlar ve oradan devam eder. Bir sonraki mesaj yazdırılır ve duraklatmak için yield value ifadesinin aranması tekrar gerçekleştirilir (yield döngüsünden dolayı).     
32
2017-07-12 12: 44: 24Z

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

 yield

Çıktı:

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


for i in f123():
    print i
    
32
2019-06-06 12: 08: 26Z
1 2 1 2 1 2 1 2
kaynak yerleştirildi İşte