Soru “Getiri” anahtar kelimesi ne yapar?


Kullanımı nedir yield anahtar kelime Python? Bu ne işe yarıyor?

Örneğin, bu kodu anlamaya çalışıyorum1:

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 ne olursa _get_child_candidates denir? Bir liste mi geri döndü? Tek eleman mı? Yine mi aradı? Sonraki çağrılar ne zaman duracak?


1. Kod, metrik uzaylar için harika bir Python kütüphanesi yapan Jochen Schulz'dan (jrschulz) geliyor. Bu, tam kaynağın bağlantısıdır: Modül mspace.


8325
2017-10-23 22:21


Menşei




Cevaplar:


Ne anlamak için yield ne olduğunu anlamalısın jeneratörler vardır. Ve jeneratörler gelmeden önce Iterables.

Iterables

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

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

mylist bir iterable. Liste kavramasını 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

Kullanabileceğin her şey "for... in..."açık yinelenebilir; lists, strings, Dosyalar...

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

Jeneratörler

Jeneratörler yineleyiciler, bir çeşit yinelenebilir sadece bir kez tekrarlayabilirsiniz. Jeneratörler tüm değerleri hafızaya almazlar, anında değerleri üretiyorlar:

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

Kullandığın dışında sadece aynı. () yerine []. Ama sen yapamam yapmak for i in mygenerator Jeneratörlerin sadece bir kez kullanılabildiğinden beri ikinci bir kez: 0'ı hesaplar, sonra unutun ve 1'i hesaplayın ve 4'ü hesaplayın, tek tek.

Yol ver

yield gibi kullanılan bir anahtar kelimedir returnişlev dışında bir jeneratör döndürecektir.

>>> 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 bu işe yaramaz bir örnektir, ancak fonksiyonunuzu bildiğiniz zaman, sadece bir kez okumanız gereken büyük bir değerler kümesi getirecektir.

Usta yieldbunu anlamalısın işlevi çağırdığınızda, işlev gövdesinde yazdığınız kod çalışmaz. Fonksiyon sadece jeneratör nesnesini döndürür, bu biraz zor :-)

Daha sonra kodunuz her seferinde for Jeneratörü kullanır.

Şimdi zor kısmı:

İlk kez for fonksiyonunuzdan oluşturulan jeneratör nesnesini çağırır, kodun başından sonuna kadar işlevini çalıştırır. yieldDaha sonra döngünün ilk değerini döndürür. Ardından, her bir çağrı, fonksiyonda yazdığınız döngüyü bir kez daha çalıştıracak ve geri dönüş değeri olmayana kadar bir sonraki değeri döndürecektir.

İşlev çalıştıktan sonra jeneratör boş olarak kabul edilir, ancak vurulmaz. yield Artık. Döngü sonunda sona ermiş olabilir, ya da bir "if/else" Artık.


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ı parça içerir:

  • Döngü bir listede yinelenir, ancak döngü tekrarlanırken liste genişler :-) Bu sonsuz bir döngü ile sonuçlanabildiğiniz için biraz tehlikeli olsa bile tüm bu iç içe geçmiş verilerden geçmenin kısa bir yoludur. 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ığı için öncekilerden farklı değerler üretecek yeni jeneratör nesneleri oluşturmaya devam ediyor.

  • extend() yöntem, yinelenebilir olmasını bekler ve değerlerini listeye ekleyen bir liste nesnesi yöntemidir.

Genellikle bir listeyi geçiyoruz:

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

Fakat kodunuzda bir jeneratör var, bu da iyi çünkü:

  1. Değerleri iki kez okumana gerek yok.
  2. Çok fazla çocuğunuz olabilir ve bunların hepsinin hafızada saklanmasını istemezsiniz.

Ve çalışır çünkü bir yöntemin argümanı bir liste olup olmadığını Python umursamaz. Python iterables bekler böylece dizeleri, listeler, tuples ve jeneratörler ile çalışacak! Bu, ördek tiplemesi olarak adlandırılır ve Python'un neden bu kadar havalı olmasının nedenlerinden biridir. Ama bu başka bir hikaye, başka bir soru için ...

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 kullanınprint(corner_street_atm.__next__()) veya print(next(corner_street_atm))

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

Itertools, en iyi arkadaşın

Itertools modülü, yinelenenleri işlemek için özel işlevler içerir. Hiç bir jeneratör çoğaltmak ister misiniz? Zincir iki jeneratörü? İç içe geçmiş bir listedeki tek değerli grup değerleri? Map / Zip başka bir liste oluşturmadan

O zaman sadece import itertools.

Bir örnek? Dört at 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ı anlamak

Yineleme, yinelenenleri ima eden bir süreçtir. __iter__() yöntem) ve yineleyicileri ( __next__() yöntem). Yinelemeler, bir yineleyiciden alabileceğiniz nesnelerdir. Yineleyiciler, yinelenenlerde yinelemenize izin veren nesnelerdir.

Bu makalede bu konuda daha fazlası var. Nasıl for döngüler çalışmak.


12189
2017-10-23 22:48



Herşey Yineleyiciler sadece jeneratör fonksiyonları tarafından üretilenler değil, sadece bir kez tekrarlanabilir. Eğer bana inanmıyorsan, ara iter() yinelenen herhangi bir nesne üzerinde ve sonucu bir defadan fazla yinelemeye çalışın. - augurar
@Craicerjack Şartlarınızı karıştırdınız. Tekrarlanabilir bir şey __iter__ yöntem. Bir yineleyici arama sonucudur iter() yinelenebilir. Yineleyiciler sadece bir kez tekrarlanabilir. - augurar
yield Bu cevap önerdiği kadar büyülü değildir. İçeren bir işlevi çağırdığınızda yield Herhangi bir yerde, bir jeneratör nesnesini alırsınız, ancak kod çalıştırmaz. Sonra, bir nesneyi jeneratörden her çıkardığınızda, Python, bir yield deyim, sonra nesneyi duraklatır ve sunar. Başka bir nesneyi çıkardığınızda, Python hemen ardından yield ve diğerine ulaşıncaya kadar devam eder yield (genellikle aynı olanı, ancak bir iterasyon daha sonra). Bu, fonksiyonun sonuna kadar devam edene kadar devam eder, bu noktada jeneratör tükenmiş sayılır. - Matthias Fripp
@MatthiasFripp bana bu cevabı okuduğumda yanıltıcı olanı işaret etti. "kodunuz her seferinde çalıştırılacak" yanıltıcı bir ifadedir. "Kodunuz kaldığı yerden devam edecek" ifadesi daha doğru bir yoldur. - Indigenuity
"Bu yinelemeler kullanışlı ... ama tüm değerleri bellekte saklıyorsunuz ve bu her zaman istediğiniz gibi değil", ya yanlış ya da kafa karıştırıcı. Yinelenen bir iterator iter () iterable üzerinde çağırarak bir yineleyici döndürür ve bir yineleyici her zaman bellekte değerleri bellekte saklamak zorunda kalmaz iter yöntem, aynı zamanda talep üzerine sırayla değerler üretebilir. - picmate 涅


Kısayol grokking  yield

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

  1. Bir çizgi ekle result = [] Fonksiyonun başında.
  2. Her birini değiştirin yield expr ile result.append(expr).
  3. Bir çizgi ekle return result Fonksiyonun altında.
  4. Yay - artık yield ifadeleri! Kodu oku ve oku.
  5. Fonksiyonu orijinal tanımla karşılaştırın.

Bu hile, işlevin arkasındaki mantık hakkında bir fikir verebilir, ancak aslında ne ile olur yield liste tabanlı yaklaşımda ne olduğu ö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 işlev, orijinal işlev gayet iyi çalışsa da, sonsuz bir döngüde kalmanıza neden olur. Daha fazla bilgi için okumaya devam edin ...

Iterables, Yineleyiciler ve Jeneratörleri karıştırmayın

İlk önce yineleyici protokolü - yazarken

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

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

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

    Aramak iter(mylist) -> bu, bir nesneyi next() yöntem (veya __next__() Python 3).

    [Bu, çoğu insanın size söylemeyi unutduğu adım]

  2. Öğeleri döngülemek için yineleyiciyi kullanır:

    Aramaya devam et next() yineleyicide yöntem 1. adımdan döndürülür. next() atandı x ve döngü gövdesi yürütülür. Bir istisna varsa StopIteration içinden yükseltilir next()Bu, yineleyicide daha fazla 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. ilmek Bir nesnenin içeriği - bu yüzden bir döngü olabilir, ancak aynı zamanda kod gibi olabilir otherlist.extend(mylist) (nerede otherlist bir Python listesidir).

İşte mylist bir iterable çünkü yineleyici protokolünü uygular. Kullanıcı tanımlı bir sınıfta, __iter__() sınıfınızın örneklerini yinelenebilir hale getirme yöntemi. Bu yöntem bir yineleyici. Yineleyici, bir nesneye sahip bir nesnedir. next() yöntem. Her ikisini de uygulamak mümkündür __iter__() ve next() aynı sınıfta ve __iter__() dönüş self. Bu, basit durumlar için çalışacaktır, ancak aynı nesne üzerinde aynı anda döngü yapan iki yineleyici istediğinizde işe yaramaz.

Yani yineleyici protokolü bu protokolü uygulayan birçok nesne:

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

Unutmayın ki for döngü, ne tür bir nesne ile uğraştığını bilmez - sadece yineleyici protokolünü izler ve aramadan sonra öğeden sonra öğeyi almaktan mutluluk duyar next(). Yerleşik listeler öğelerini teker teker döndürür, sözlükler geri döner anahtarlar tek tek dosyalar geri dönüyor hatlar teker teker, vs. Ve jeneratörler geri dönüyorlar. yield içeri gelir:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

Yerine yield üç ifadeniz varsa return ifadeleri f123() sadece ilk idam edilecekti ve işlev çıkacaktı. Fakat f123() sıradan bir işlev değildir. Ne zaman f123() denir, o değil getiri beyanlarındaki değerlerden herhangi birini iade edin! Bir jeneratör nesnesini döndürür. Ayrıca, işlev gerçekten çıkmıyor - askıya alınmış duruma geçer. Ne zaman for döngü, jeneratör nesnesinin üzerine dönmeyi dener; işlev, sonraki sonraki satırdaki askıya alınmış durumundan devam eder. yield Daha önce geri döndü, bu durumda, bir sonraki kod satırı yürütür yield deyim ve bunu bir sonraki öğe olarak döndürür. Bu, fonksiyon çıkana kadar olur, bu noktada jeneratör yükselir StopIterationve döngü çıkar.

Yani jeneratör nesnesi bir adaptör gibi bir şeydir - bir ucunda açığa vurmak suretiyle yineleyici protokolünü sergiler. __iter__() ve next() tutmak için yöntemler for döngü mutlu. Diğer taraftan, ancak, bir sonraki değeri elde etmek için yeterli işlevi çalıştırır ve askıya alma moduna geri getirir.

Neden Jeneratör Kullanmalı?

Genellikle jeneratörü kullanmayan ancak aynı mantığı uygulayan kod yazabilirsiniz. Bir seçenek daha önce bahsettiğim geçici hileyi kullanmamdır. Bu, tüm durumlarda, örneğin, sonsuz döngülere sahipseniz veya gerçekten uzun bir listeye sahipseniz, belleğin verimsiz kullanımını sağlayabilir. Diğer yaklaşım, yeni bir yinelenebilir sınıfın uygulanmasıdır. SomethingIter Bu, örneğin üyelerini devlet tutar ve bir sonraki mantıksal adımı gerçekleştirir next() (veya __next__() Python 3) yönteminde. Mantığa bağlı olarak, içindeki kod next() Yöntem çok karmaşık görünebilir ve hatalara eğilimli olabilir. Burada jeneratörler temiz ve kolay bir çözüm sunuyor.


1638
2017-10-25 21:22



"Getiri bildirimlerini içeren bir işlev gördüğünüzde, ne olacağını anlamak için bu kolay yolu uygulayın" Bu tamamen gerçeği görmezden gelmez send Jeneratörlerin büyük bir parçası olan bir jeneratöre mi? - DanielSank
"bir döngü olabilir, ama aynı zamanda kod gibi olabilir otherlist.extend(mylist)"-> Bu yanlış. extend() listeyi yerinde değiştirir ve yinelenen bir dönüş yapmaz. Dönmeye çalışıyorum otherlist.extend(mylist) ile başarısız olur TypeError Çünkü extend() dolaylı olarak iade Noneve sen üstesinden gelemezsin None. - Pedro
@pedro Bu cümleyi yanlış anladınız. Python, belirtilen iki adımı gerçekleştirdiği anlamına gelir. mylist (değil otherlist) yürütürken otherlist.extend(mylist). - today


Bu şekilde düşün:

Bir yineleyici, bir next () yöntemine sahip olan bir nesne için sadece süslü bir sondaj ifadesidir. Böylece bir verim işlevi, böyle bir şey olmaktan çıkıyor:

Orijinal versiyon:

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

for i in some_function():
    print i

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

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

Sahnelerin arkasında neler olduğuna dair daha fazla bilgi için, for döngü buna yeniden yazılabilir:

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

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

Buna dikkat etmeliyim olduğu açıklayıcı amaçlar için bir aşırı basitleştirme. :)


397
2017-10-23 22:28



__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: 0, 10, 20, ..., 90 - jfs
Bu örneği Python 3.6'da denedim ve eğer yaratsam iterator = some_function(), değişken iterator adında bir işlev yok next() artık, ama sadece bir __next__() işlevi. Bahsetmeyi düşündüm. - Peter


yield anahtar kelime iki basit gerçekliğe indirgenir:

  1. Derleyici algılarsa yield anahtar kelime herhangi bir yer bir işlev içinde, bu işlev artık return Beyan. Yerine, o hemen bir döndürür tembel "bekleyen liste" nesnesi bir jeneratör denir
  2. Bir jeneratör yinelenebilir. Nedir iterable? Bir şey gibi list veya set veya range veya dict-view ile her öğeyi belirli bir sırada ziyaret etmek için yerleşik protokol.

Kısaca: Bir jeneratör tembel, aşamalı olarak bekleyen bir listedir, ve yield ifadeleri, liste değerlerini programlamak için işlev gösterimini kullanmanızı sağlar Jeneratör kademeli olarak tükürmelidir.

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

Örnek

Bir fonksiyonu tanımlayalım makeRange Python'unki gibi range. çağrı makeRange(n) JENERATÖRÜN İADE EDİLMESİ:

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ü hemen bekleyen değerlerini geri göndermeye zorlamak için list() (herhangi bir yinelenebilir gibi):

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

Örnekte "sadece bir liste döndürme" ile karşılaştırılıyor

Yukarıdaki örnek, yalnızca eklediğiniz ve geri eklediğiniz 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]

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


Jeneratörleri nasıl kullanabilirsiniz?

Yinelenen bir liste kavramasının son kısmıdır ve tüm jeneratörler yinelenebilirdir, bu yüzden sıklıkla 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 itertools modül kullandığınızdan emin olun chain.from_iterable ziyade chain garanti edildiğinde). Örneğin, sonsuz süresiz tembel listeleri uygulamak için jeneratörler bile kullanabilirsiniz. itertools.count(). Kendi kendini uygulayabilirsin def enumerate(iterable): zip(count(), iterable)veya alternatif olarak bunu yieldwhile döngüsünde anahtar kelime.

Lütfen dikkat: jeneratörler aslında daha birçok şey için kullanılabilir koroutinler uygulamak 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.


Kamera ARKASI

"Python yineleme protokolü" bu şekilde çalışır. Yani, ne zaman gidiyorsun? list(makeRange(5)). Daha önce "tembel, artan liste" olarak tanımladığım şey 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 fonksiyonu next() sadece nesneleri çağırır .next() "yineleme protokolü" nin bir parçası olan ve tüm yineleyicilerde bulunan işlev. El ile kullanabilirsiniz next() genellikle okunabilirlik pahasına, fantezi şeyleri uygulamak için işlev (ve yineleme protokolünün diğer bölümleri), bu yüzden bunu yapmaktan kaçınmaya çalışın ...


Önemsiz ayrıntılar

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

Python konuşmasında iterable bir liste gibi "bir for döngüsü kavramını anlayan" herhangi bir nesnedir [1,2,3], ve bir yineleyici istenen for-loop gibi belirli bir örneğidir [1,2,3].__iter__(). bir jeneratör yazılan şekilde (işlev sözdizimi ile) hariç, herhangi bir yineleyici ile tam olarak aynıdır.

Bir listeden bir yineleyici isteğinde bulunduğunuzda, yeni bir yineleyici oluşturur. Ancak, bir yineleyiciden bir yineleyici isteğinde bulunduğunuzda (ki bunu nadiren yaparsınız), sadece size bir kopyasını verir.

Böylece, beklenmedik bir durumda böyle bir şey yapamayacaksınız ...

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

... o zaman bir jeneratör olduğunu hatırlayın. yineleyici; bir kerelik kullanımdır. Yeniden kullanmak istersen, aramalısın myRange(...) tekrar. 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)). Bir jeneratörü klonlamaya ihtiyaç duyanlar (örneğin, korkunç bir şekilde hackish metaprogramını yapan) itertools.tee kesinlikle gerekli ise, kopyalanabilir yineleyici Python beri PEP standart teklifi ertelendi.


348
2018-06-19 06:33





Ne yapar yield anahtar kelime Python'da mı?

Özet / Özet Özeti

  • İle bir işlev yield, çağrıldığında bir döndürür Jeneratör.
  • Jeneratörler yineleyicidir çünkü yineleyici protokolüOnları üzerinde yineleyebilirsiniz.
  • Bir jeneratör de olabilir bilgi gönderildikavramsal olarak eşyordam.
  • Python 3'te yapabilirsiniz temsilci her iki yönde bir jeneratörden diğerine yield from.
  • (Ek eleştiriler, en üstteki dahil olmak üzere birkaç yanıtı ve kullanımlarını tartışır. return bir jeneratörde.)

Jeneratörler:

yield bir fonksiyon tanımının içinde sadece yasaldır ve dahil edilmesi yield Bir işlev tanımında bir jeneratör döndürür.

Jeneratörler fikri, farklı uygulamalarla diğer dillerden (Dipnot 1'e bakınız) gelir. Python Jeneratörlerinde, kodun yürütülmesi dondurulmuş verim noktasında. Jeneratör çağrıldığında (yöntemler aşağıda tartışılmaktadır) yürütme devam eder ve daha sonra bir sonraki verimde donar.

yield sağlar kolay yolu yineleyici protokolünü uygulamakaşağıdaki iki yöntemle tanımlanmıştır: __iter__ ve next (Python 2) veya __next__ (Python 3). Her iki yöntem Bir nesneyi, yazıp yazabileceğiniz bir yineleyici yapın. Iterator Özet, temel Sınıftan collections modülü.

>>> 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, bir alt yineleyici türüdür:

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

Ve gerekirse, şu şekilde kontrol edebiliriz:

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

Bir özellik Iterator  bir zamanlar bitmiş, yeniden kullanamaz veya sıfırlayamazsınız:

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

İşlevselliklerini tekrar kullanmak isterseniz bir tane daha yapmalısınız (dipnot 2'ye bakınız):

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

Biri programlı olarak veri sağlayabilir, örneğin:

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

Yukarıdaki basit jeneratör de Python 3.3'ten (Python 2'de mevcut değildir) aşağıdakilere eşdeğerdir. yield from:

def func(an_iterable):
    yield from an_iterable

Ancak, yield from ayrıca alt-oluşturucular için delegasyona izin verir, Alt-koroutinlerle kooperatif delegasyonunun bir sonraki bölümünde açıklanacaktır.

Eşyordamlar:

yield verilerin jeneratöre gönderilmesine izin veren bir ifade oluşturur (dipnot 3'e bakınız).

İşte bir örnek, not al received alternatöre gönderilen verilere işaret eden değişken:

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. Olacak uygun aramak next veya __next__ yöntemi, sürümüne bağlı olarak Kullandığınız Python:

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

Ve şimdi veri üretecine gönderebiliriz. (gönderme None olduğu çağrıyla aynı next.):

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

Alt Coroutine ile Kooperatif Delegasyonu yield from

Şimdi şunu hatırla yield from Python 3'te kullanılabilir. Bu bize yetki vermemizi sağlar. bir alt rutin için koroutinler:

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 kullanılabilir Yukarıdaki gibi bir jeneratör tarafından:

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

Tam anlamıyla semantik hakkında daha fazla bilgi edinebilirsiniz. yield from içinde PEP 380.

Diğer Yöntemler: yakın ve atmak

close yöntem yükseltir GeneratorExit noktada fonksiyon yürütme donduruldu. Bu da tarafından çağrılacak __del__ yani sen işlem yaptığınız herhangi bir temizleme kodunu koyabilirsiniz GeneratorExit:

>>> my_account.close()

Jeneratörde kullanılabilecek bir istisna da atabilirsiniz. veya kullanıcıya geri yayılır:

>>> 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 soruların tüm yönlerini ele aldığımı düşünüyorum:

Ne yapar yield anahtar kelime Python'da mı?

Şekline dönüştü yield çok şey yapar. Eminim daha fazlasını ekleyebilirim bunun için kapsamlı örnekler. Daha fazlasını istiyorsanız veya yapıcı eleştirileriniz varsa, yorum yaparak bana bildirin. altında.


Ek:

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

  • Ne yapar ne üzerinde kafa karıştırıcı iterablesadece örnek olarak bir liste kullanmak. Yukarıdaki referanslarıma bakın, ancak özet olarak: yinelenen bir __iter__ bir yöntem döndüren yineleyici. bir yineleyici sağlar .next (Python 2 veya .__next__ (Python 3) yöntemi, dolaylı olarak foryükselene kadar döngüler StopIterationve bir kez olsun, bunu yapmaya devam edecektir.
  • Daha sonra bir jeneratörün ne olduğunu tanımlamak için bir jeneratör ifadesi kullanır. Bir jeneratör sadece bir yineleyiciSadece meseleyi karıştırır ve hala yield Bölüm.
  • İçinde Jeneratör tükenmesini kontrol etme o çağırır .next yöntem, bunun yerine yerleşik işlevi kullanması gerektiğini next. Uygun bir dolaylılık katmanı olurdu çünkü onun kodu Python 3'te çalışmaz.
  • Itertools? Bu ne ile alakalı değildi yield hiç yok.
  • Bu yöntemlerle ilgili tartışma yok yield yeni işlevsellik ile birlikte sunar yield from Python 3'te. Üst / kabul edilen cevap çok eksik bir cevaptır.

Düşündüren yanıtın eleştirisi yield Bir jeneratör ifadesinde veya anlamada.

Dilbilgisi şu anda liste anlamasında herhangi bir ifadeye izin veriyor.

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 ifadeden ibaret olduğu için, bazıları tarafından, özellikle iyi bir kullanım durumu gerekçe gösterilmese de, anlama veya üretme ifadesinde kullanması ilginç olmuştur.

CPython çekirdek geliştiricileri onun ödeneğini elimine etmek. İşte posta listesinden ilgili bir yayın:

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

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

Her iki yaklaşımda da iyiyim. Eşyaları Python 3'teki gibi bırakarak.       iyi değil, IMHO.

Benim oyumdan beklediğiniz şeyi almıyorsunuz diye bir SyntaxError olması     sözdizimi.

Herhangi bir kod gibi, bunun bizim için bitmesi mantıklı bir yer olduğunu kabul ediyorum.   Şu anki davranışa güvenmek gerçekten çok akıllıca   sürdürülebilir.

Oraya varmak için muhtemelen isteyeceğiz:

  • SyntaxWarning veya DeprecationWarning'de 3.7
  • 2.73'de Py3k uyarısı
  • SyntaxError 3,8'de

Şerefe, Nick.

- Nick Coghlan | ncoghlan at gmail.com | Brisbane, Avustralya

Ayrıca, bir olağanüstü sorun (10544) bunun yönünde işaret ediyor gibi görünüyor asla İyi bir fikir olmak (PyPy, Python'da yazılmış bir Python uygulaması zaten sözdizimi uyarılarını yükseltiyor.)

Alt satır, CPython'un geliştiricileri bize başka türlü söyleyene kadar: Koyma yield Bir jeneratör ifadesinde veya anlamada.

return jeneratörde ifade

İçinde Python 2:

Bir jeneratör fonksiyonunda return ifadenin bir expression_list. Bu bağlamda, bir çıplak return jeneratörün yapıldığını gösterir ve neden olur StopIteration yükseltilmek.

bir expression_list temelde virgülle ayrılmış herhangi bir sayı ifadesidir - aslında Python 2'de jeneratörü durdurabilirsiniz. returnama bir değer veremezsin.

İçinde Python 3:

Bir jeneratör fonksiyonunda return ifadesi, jeneratörün yapıldığını ve neden olacağını gösterir StopIteration yükseltilmek. Döndürülmüş değer (eğer varsa) bir argüman olarak kullanılır StopIteration ve olur StopIteration.valuebağlıyor.

Dipnotlar

  1. Teklifte CLU, Sather ve Simge referansları verildi. Jeneratör kavramını Python'a tanıtmak. Genel fikir Bir fonksiyonun içsel durumu koruyabildiği ve ara verebileceği Kullanıcı tarafından talep edilen veri noktaları. Bu söz verdi performansta üstün Python iş parçacığı da dahil olmak üzere diğer yaklaşımlaraBazı sistemlerde bile mevcut değildir.

  2.  Bu, örneğin, xrange nesnelerrange Python'da 3) değil Iterators, yinelenebilir olsalar da tekrar kullanılabilirler. Listeler gibi __iter__ yöntemler yineleyici nesnelerini döndürür.

  3. yield başlangıçta bir ifade olarak tanıtıldı, yani Bir kod bloğunda sadece bir satırın başında görünebilir. şimdi yield bir verim ifadesi oluşturur. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt  Bu değişiklik önerilen Bir kullanıcının, vericiye veri göndermesine izin vermek biri onu alabilir. Veri göndermek için, onu bir şeye atayabilmeli ve Bunun için bir ifade işe yaramaz.


254
2018-06-25 06:11





yield aynen return - ne söylersen söyler (jeneratör olarak). Aradaki fark, jeneratörü aradığınız zaman, yürütmenin son aramaya başlamasıdır. yield Beyan. Dönüşten farklı olarak, Yığın çerçevesi bir verim oluştuğunda temizlenmez, ancak kontrol tekrar arayan kişiye aktarılır, böylece durumu bir sonraki seferde devam ettirir.

Kodunuzun durumunda, işlev get_child_candidates bir yineleyici gibi davranıyor, böylece listenizi genişlettiğinizde, yeni listeye her seferinde bir öğe ekler.

list.extend Bitene kadar bir yineleyici çağırır. Gönderdiğiniz kod örneğinde, bir tuple geri dönüp listeye eklemek daha doğru olur.


230
2017-10-23 22:24



Bu yakın ama doğru değil. Verim deyimiyle bir işlevi her çağırdığınızda, yepyeni bir jeneratör nesnesi döndürür. Yalnızca, bu verim son verimden sonra devam edecek olan bu jeneratörün .next () yöntemini çağırdığınızda. - kurosch
Bir şey eksik gibi görünüyor "bir dahaki sefere fonksiyona devam edecek". Olmalı mı "fonksiyonu bir sonraki seferde devam edecek koşar"? - Peter Mortensen


Söylenecek bir şey daha var: verimlerin aslında sonlandırmak zorunda olmadığı bir işlev. Böyle bir kod yazdım:

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

Öyleyse bunu başka bir kodda kullanabilirim:

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

Bazı problemleri basitleştirmeye yardımcı olur ve bazı şeylerin birlikte çalışmasını kolaylaştırır.


182
2017-10-24 08:44





En az çalışan bir örneği tercih edenler için, bu etkileşimde meditasyon yapın piton oturum, toplantı, celse:

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

155
2018-01-18 17:25



Bu soruya cevap vermiyor - ppperry


Verim size bir jeneratör sunar.

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 foo tüm listeyi bir kerede bellekte tutar. 5 elementli bir liste için büyük bir anlaşma değil, ama 5 milyonluk bir liste istiyorsanız ne olur? Bu sadece büyük bir bellek yiyici değil, aynı zamanda fonksiyon çağrıldığında inşa etmek için çok fazla zaman harcıyor. İkinci durumda, çubuk size sadece bir jeneratör veriyor. Bir jeneratör yinelenebilir - yani bir for döngüsünde kullanabilirsiniz, ancak her bir değere sadece bir kez erişilebilir. Tüm değerler aynı zamanda bellekte de saklanmaz; Jeneratör nesnesi, onu son kez aradığınız döngüde "hatırlar" - bu şekilde, 50 milyara kadar yinelenen bir süre kullanıyorsanız, 50 milyara kadar saymanıza gerek yoktur. Bir kerede 50 milyar rakamı saklayın. Yine, bu oldukça çekişmeli bir örnek, muhtemelen 50 milyara kadar saymak istiyorsanız itertools kullanacaksınız. :)

Bu, jeneratörler için en basit kullanım durumudur. Dediğiniz gibi, bir çeşit yığın değişkeni kullanmak yerine, çağrı yığını üzerinden işleri 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 şey için kullanılabilir.


133
2018-01-16 06:42