Jeneratörü çağıran bir fonksiyondan dönüş veya verim?


30

Bir jeneratör generatorve aynı zamanda bir kolaylık yöntemi var - generate_all.

def generator(some_list):
  for i in some_list:
    yield do_something(i)

def generate_all():
  some_list = get_the_list()
  return generator(some_list) # <-- Is this supposed to be return or yield?

Olmalı generate_all returnveya yield? Her iki yöntemin de kullanıcılarının aynı şekilde kullanılmasını istiyorum, yani

for x in generate_all()

eşit olmalı

some_list = get_the_list()
for x in generate(some_list)

2
Her ikisini de kullanmanın bir nedeni var. Bu örnek için, geri dönüş daha verimli
Mad Physicist

1
Bu bana bir zamanlar sorduğum benzer bir soruyu hatırlatıyor: “tekrarlanabilir verim” ve “dönüş iter (tekrarlanabilir)” . Özellikle jeneratörler hakkında olmasa da, temel olarak jeneratörlerle aynıdır ve yineleyiciler python'da oldukça benzerdir. Ayrıca cevap tarafından önerilen bayt kodunu karşılaştırma stratejisi burada yararlı olabilir.
PeterE

Yanıtlar:


12

Jeneratörler tembel olarak değerlendiriyorlar returnya yieldda kodunuzda hata ayıklarken veya bir istisna atıldığında farklı davranıyorlar.

İle returnSepetinde olur herhangi istisna generatorhakkında hiçbir şey bilmeyecek generate_allne zaman çünkü bu, generatorgerçekten yürütülür zaten bırakmış generate_allişlevi. yieldOrada ile geri generate_allizleme olacak.

def generator(some_list):
    for i in some_list:
        raise Exception('exception happened :-)')
        yield i

def generate_all():
    some_list = [1,2,3]
    return generator(some_list)

for item in generate_all():
    ...
Exception                                 Traceback (most recent call last)
<ipython-input-3-b19085eab3e1> in <module>
      8     return generator(some_list)
      9 
---> 10 for item in generate_all():
     11     ...

<ipython-input-3-b19085eab3e1> in generator(some_list)
      1 def generator(some_list):
      2     for i in some_list:
----> 3         raise Exception('exception happened :-)')
      4         yield i
      5 

Exception: exception happened :-)

Ve eğer kullanıyorsa yield from:

def generate_all():
    some_list = [1,2,3]
    yield from generator(some_list)

for item in generate_all():
    ...
Exception                                 Traceback (most recent call last)
<ipython-input-4-be322887df35> in <module>
      8     yield from generator(some_list)
      9 
---> 10 for item in generate_all():
     11     ...

<ipython-input-4-be322887df35> in generate_all()
      6 def generate_all():
      7     some_list = [1,2,3]
----> 8     yield from generator(some_list)
      9 
     10 for item in generate_all():

<ipython-input-4-be322887df35> in generator(some_list)
      1 def generator(some_list):
      2     for i in some_list:
----> 3         raise Exception('exception happened :-)')
      4         yield i
      5 

Exception: exception happened :-)

Ancak bu, performans maliyetine neden olur. İlave jeneratör katmanının bir miktar ek yükü vardır. Bu yüzden (veya ) returngenellikle biraz daha hızlı olacaktır . Çoğu durumda bu çok önemli olmayacaktır, çünkü jeneratörde ne yaparsanız yapın tipik olarak çalışma zamanına hakim olur, böylece ek katman fark edilmeyecektir.yield from ...for item in ...: yield item

Bununla birlikte yield, bazı ek avantajları vardır: Tek bir yinelenebilirle sınırlı değilsiniz, ayrıca kolayca ek öğeler de verebilirsiniz:

def generator(some_list):
    for i in some_list:
        yield i

def generate_all():
    some_list = [1,2,3]
    yield 'start'
    yield from generator(some_list)
    yield 'end'

for item in generate_all():
    print(item)
start
1
2
3
end

Sizin durumunuzda işlemler oldukça basittir ve bunun için birden fazla işlev oluşturmanın gerekli olup olmadığını bilmiyorum, biri kolayca dahili mapveya jeneratör ifadesini kullanabilir:

map(do_something, get_the_list())          # map
(do_something(i) for i in get_the_list())  # generator expression

Her ikisi de aynı olmalıdır (istisnalar olduğunda bazı farklılıklar hariç). Ve daha açıklayıcı bir isme ihtiyaçları varsa, onları yine de bir işlevde sarabilirsiniz.

Yerleşik tekrarlanabilirler üzerinde çok yaygın işlemleri saran birden fazla yardımcı vardır ve yerleşik modülde başka işlemler de bulunabilir itertools. Böyle basit durumlarda sadece bunlara başvururum ve sadece önemsiz durumlar için kendi jeneratörlerinizi yazarım.

Ancak gerçek kodunuzun daha karmaşık olduğunu varsayarım ki bu geçerli olmayabilir, ancak alternatiflerden bahsetmeden bunun tam bir cevap olmayacağını düşündüm.


17

Muhtemelen Jeneratör Temsilcisi (PEP380) arıyorsunuz

Basit yineleyiciler için, yield from iterableaslında sadece kısaltılmışfor item in iterable: yield item

def generator(iterable):
  for i in iterable:
    yield do_something(i)

def generate_all():
  yield from generator(get_the_list())

Oldukça özlüdür ve ayrıca keyfi / farklı yinelemeleri zincirleyebilme gibi başka avantajlara da sahiptir!


Ah ismini mi kastediyorsun list? Kötü bir örnek, soruya yapıştırılan gerçek kod değil, muhtemelen düzenlemeliyim.
hyankov

Evet - asla korkmayın, ilk
sormada

2
Birincisi de bir astar olabilir :). yield from map(do_something, iterable)hatta çiftyield from (do_something(x) for x in iterable)
Mad Physicist

2
"Bu tamamen aşağı örnek kod!"
ti7

3
Sadece yeni jeneratörü iade etmekten başka bir şey yapıyorsanız, yetki vermeniz yeterlidir. Sadece yeni jeneratörü iade ederseniz, yetki vermenize gerek yoktur. Yani yield fromsizin sarıcı yapmazsa anlamsızdır şey başka jeneratör-y.
ShadowRanger

14

return generator(list)ne istersen yapar. Ama unutmayın

yield from generator(list)

eşdeğer olacaktır, ancak bittikten sonra daha fazla değer elde etme fırsatı ile generator. Örneğin:

def generator_all_and_then_some():
    list = get_the_list()
    yield from generator(list)
    yield "one last thing"

5
Bir ince arasındaki fark olduğuna inanmak yield fromve returnzaman jeneratör tüketici throwsbunun bir durum içinde - ve yığın izleme etkilenmektedir Diğer işlemler ile.
WorldSEnder

9

Aşağıdaki iki ifade bu özel durumda işlevsel olarak eşdeğer görünecektir:

return generator(list)

ve

yield from generator(list)

Sonuncusu yaklaşık olarak aynı

for i in generator(list):
    yield i

returnİfadesi Aradığınız jeneratör döner. Bir yield fromveya yielddeyimi, tüm işlevinizi, aradığınızdan geçen bir jeneratörü döndüren bir şeye dönüştürür.

Kullanıcı bakış açısından hiçbir fark yoktur. Bununla birlikte, dahili olarak, gereksiz bir geçiş jeneratörüne sarılmadığı returniçin tartışmasız daha etkilidir generator(list). Sarılı jeneratörün elemanları üzerinde herhangi bir işlem yapmayı planlıyorsanız yield, elbette bir form kullanın .


4

Yapardın return.

yielding * generate_all()bir jeneratörün kendisini değerlendirmeye neden olur ve nextbu dış jeneratörü çağırmak , ilk fonksiyonun döndürdüğü iç jeneratörü geri döndürür, bu da istemezsiniz.

* İçermiyor yield from

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