Məhsuldarlıq açar sözü nədir?

Python-da yield açar sözünün istifadə edilməsi nədir? Nədir?

Məsələn, bu kodun 1 :

 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 

Və bu bir dilerdir

 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 metodu ilə nə baş verir? Siyahı geri döndü? Tək maddə? Yenidən çağırıldı mı? İzləmə çağrısı nə vaxt dayanacaq?


1. Kod metrik boşluqlar üçün mükəmməl Python kitabxanasını yaratan Jochen Schulz (jrschulz) tərəfindən alınır. Bu, tam mənbəyə bir əlaqədir : mspace Module .

8878
Alex tərəfindən verilmişdir . 24 окт. S. 24 oct. 2008-10-24 01:21 '08 at 01:21 2008-10-24 01:21
@ 45 cavab
  • 1
  • 2

yield olduğunu anlamaq üçün, hansı generatorların olduğunu anlamaq lazımdır. Və jeneratörlər əvvəl iterators gəlib.

iterated

Bir siyahı yaratdığınız zaman maddələrinizi tək-tək oxuya bilərsiniz. Elementləri bir-birinə oxumağa təkrarlanma deyilir:

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

mylist təkrarlanabilir. Siyahı anlamağınızı istifadə etdiyiniz zaman, bir siyahı yaradır və bu səbəbdən təkrarlana bilər:

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

Bütün "istifadə etmək for... in... " istifadə edə bilərsiniz. lists , strings , faylları ...

Bu yinelemeler rahatdır, çünki istədiyiniz kimi çox oxuya bilərsiniz, lakin bütün dəyərləri yaddaşda saxlayırsınız və bu da bir çox dəyərlərə malik olduğunuz zaman istənilən deyil.

Generatorlar

Jeneratörlər yineleyicilərdir, təkrar təkrar edə biləcəyiniz bir növ iteration. Jeneratörlər bütün dəyərləri yadda saxlamırlar, həddindən artıq dəyərlər yaradırlar :

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

Bu, [] əvəzinə istifadə etdiyiniz istisna olmaqla, eyni. for я in mygenerator yalnız bir dəfə istifadə edildikdən sonra, ikinci dəfə for я in mygenerator bunu edə bilməzsiniz : 0 hesablayırlar, sonra onu for я in mygenerator , 1 hesablayırlar və 4 hesablamaya son qoyurlar.

Verim

yield funksiyası bir jeneratörü qaytaracaq istisna olmaqla, return şəklində istifadə olunan bir sözdür.

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

İşdə yararsız bir nümunədir, amma funksiyanızın yalnız bir dəfə oxumağınız lazım olan dəyərlər dəstini qaytaracağını bildiyiniz zaman faydalıdır.

Səmərə ilə mübarizə etmək üçün bir funksiyanı çağırdığınız zaman funksiyanın orqanında yazılan kod başlamaz. Bu funksiya yalnız generatorun obyektini qaytarır, bir az mürəkkəbdir :-)

Daha sonra kodunuz generatorun istifadəsi for hər dəfə buraxdığı yerdən davam edəcəkdir.

İndi ən çətin hissəsidir:

Zəng etdiyimiz ilk dəfə funksiyadan yaradılan bir generator obyektini çağırır for funksiyanı kodunuzun başından başlayaraq yield çatdıqdan sonra loopun ilk dəyərini qaytarır. Daha sonra, hər bir sonrakı zəng funksiyaya yazdığınız loopu başlayacaq və dəyər geri qaytarılana qədər növbəti dəyəri qaytarır.

Bu funksiya işə salındıqdan sonra generator boş hesab olunur, lakin artıq yield çevrilmir. Bu, dövrünün sona çatdığına və ya "if/else" uyğun gəlmədiyinə görə ola bilər.


Kodunuz açıqlandı

Generator:

 # 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 

Abunəçilər:

 # 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 bir neçə zərif hissədən ibarətdir:

  • Döngə siyahıda təkrarlanır, lakin siyahı təkrar iteration zamanı genişlənir :-) Bu sonsuz bir loop əldə edə bilərsiniz, çünki bir az təhlükəli olsa belə, bütün bu iç içə məlumatları keçmək üçün qısa bir yoldur. Bu halda candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) bütün generator dəyərlərini candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) , lakin eyni node üçün tətbiq edilmədiyindən əvvəlki dəyərlərdən başqa dəyərlər yaradan yeni generator obyektləri yaratmağa davam while .

  • extend() metodu təkrarlanmağa gözləyən siyahı obyektinin bir metodudur və onun dəyərlərini siyahıya əlavə edir.

Adətən ona bir siyahı veririk:

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

Ancaq kodunuzda yaxşı bir generator olur, çünki yaxşıdır:

  1. Qiymətləri iki dəfə oxumaq lazım deyil.
  2. Çox övladınız ola bilər və bunların hamısının yaddaşda saxlanmasını istəmirsiniz.

Python, metodun mübahisəsi bir siyahı olub olmadığını düşünməməsi səbəbiylə işləyir. Python yinelemeyi gözləyir, buna görə də strings, siyahıları, tuples və generatorlar ilə işləyəcək! Bu ördək adlanır və Pythonun nə qədər sərin olduğu səbəblərindən biridir. Ancaq bu başqa bir sual üçün başqa bir hekayə ...

Burada dayanmaq və generatorun inkişaf etmiş istifadəsini görmək üçün bir az oxuya bilərsiniz:

Generator tükənməsi nəzarət

 >>> class Bank(): # Let create a bank, building ATMs ... crisis = False ... def create_atm(self): ... while not self.crisis: ... yield "$100" >>> hsbc = Bank() # When everything 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 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 ... 

Qeyd Python üçün 3, print(corner_street_atm.__next__()) və ya print(next(corner_street_atm))

Bu bir qaynaqa girişin idarə olunması kimi müxtəlif şeylər üçün faydalı ola bilər.

Itertools, ən yaxşı dostunuz

Itertools modulunda yinelərin idarə olunması üçün xüsusi funksiyalar var. Heç bir generatoru təkrarlamaq istədi mi? İki generatorlar zənciri? Bir xətt ilə daxili bir siyahıda qrup dəyərləri? Başqa siyahı yaratmadan Map/Zip ?

Sonra import itertools .

Məsələn? At yarışı üçün mümkün olan gəliş prosedurlarına nəzər salaq:

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

Daxili təkrarlanma mexanizmlərini anlamaq

__iter__() yinelemeleri ( __iter__() metodunun həyata keçirilməsi) və yineleyiciləri nəzərdə tutan bir prosesdir ( __iter__() metodunu tətbiq). İterasyonlar, yineleyici ala biləcək hər hansı bir obyektdir. Yörterler, yinelemeleri təkrar etməyə imkan verən obyektlərdir.

Bu məqalədə işi necə birləşdirmək barədə daha çox məlumat var.

12976
24 окт. Cavab 24 gün e-satqı ilə verilir . 2008-10-24 01:48 '08 at 1:48 2008-10-24 01:48

Burulma veriminə etiket

yield bir funksiyanı gördüyünüzdə, nə baş verəcəyini anlamaq üçün bu sadə hiylədən istifadə edin:

  1. Nəticəni result = [] funksiyanın başında yerləşdirin.
  2. Hər bir yield expr nəticəsini result.append(expr) ilə result.append(expr) .
  3. İşarənin altındakı return result xəttinin nəticəsini daxil edin.
  4. Yay - daha çox yield hesabat! Kod oxuyun və öyrənin.
  5. Funksiyanı orijinal təriflə müqayisə edin.

Bu texnika sizə bir funksiyanın məntiqinə dair bir fikir verə bilər, amma əslində bir yield ilə baş verənlər siyahıya əsaslanan yanaşmada baş verənlərdən əhəmiyyətli dərəcədə fərqlənir. Bir çox hallarda, verim anlayışı daha səmərəli və daha sürətli olacaqdır. Digər hallarda, əsl funksiya yaxşı işləsə belə, bu hiyləgər sonsuz bir döngəyə sıxılır. Daha ətraflı məlumat üçün oxuyun ...

Yineleyicilərinizi, yineleyicilərinizi və generatorlarınızı qarışdırmayın.

Birincisi, iterator protokolu - yazarkən

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

Python aşağıdakı iki addımı həyata keçirir:

  1. mylist üçün yineleyiciyi alır:

    iter(mylist) → Python-da next() metodu (və ya __next__() next() ilə bir obyekt qaytarır 3).

    [Bu, insanların çoxu haqqında danışmağı unutmuş bir addımdır]

  2. Döngü elementlərinə bir iterator istifadə edir:

    1-ci addımdan gələn yineleyici üzərində next() metodu çağırmağa davam edin. Geri dönüş dəyəri next() x təyin olunur və döngənin gövdəsi icra edilir. StopIteration istisnası next() içərisindən çağırılırsa, yəni iteratorda və dövrün sonlarında daha çox dəyər yoxdur.

Həqiqət Pythonun hər hansı bir zamanda bir obyektin məzmunu üzərində təkrarlamaq istədiyi zaman yuxarıda göstərilən iki addımı yerinə yetirir - beləliklə, bu otherlist.extend(mylist) loop ola bilər, amma otherlist.extend(mylist) kimi otherlist.extend(mylist) ola bilər otherlist.extend(mylist) siyahı Python siyahısı ),

border=0

Burada, iterator protokolunu həyata mylist təkrar edir. İstifadəçi müəyyən bir sinifdə, sinif nümunələrini yineleyici etmək üçün __iter__() metodunu tətbiq edə bilərsiniz. Bu üsul yineleyici qayıtmalıdır. Bir iterator next() metodu olan bir obyektdir. Eyni sinifdə __iter__()next() __iter__() hər __iter__() tətbiq edə bilərsiniz və __iter__() self qaytarır. Bu, sadə məsələlər üçün işləyəcək, lakin iki təkrarlayıcı eyni obyekti eyni anda dövr etməyiniz üçün deyil.

Yəni iterator protokolunda bir çox obyekt bu protokolu həyata keçirir:

  1. Daxili siyahılar, lüğətlər, kuponlar, dəstlər, fayllar.
  2. __iter__() tətbiq edən xüsusi siniflər.
  3. Generatorlar.

Qeyd edək ki, for döngü ilə məşğul olan obyektin nə olduğunu bilmir - yalnız yineleyici protokoluna əməl edir və next() çağırış zamanı elementdən element əldə etməkdən xoşbəxtdir next() . Daxili siyahılar məhsullarını bir-birinə qaytarır, lüğətlər bir-birinə qayıtır, fayllar bir-bir təkrarlanır və s. Və generatorlar geri gəlir ... yaxşı, gəlir verəndə gəlir:

 def f123(): yield 1 yield 2 yield 3 for item in f123(): print item 

f123() f123() funksiyasındakı f123() funksiyasını yerinə f123() f123() yalnız f123() funksiyası f123() . Ancaq f123() adi bir funksiya deyil. f123() , məhsul hesabatında heç bir dəyər vermir! Bir generator obyektini qaytarır. Bundan əlavə, funksiya həqiqətən çıxmır - gözləmə vəziyyətinə daxil olur. For loop generator obyektini loop etməyə çalışırsa, funksiya əvvəlcədən geri qaytarılmış nəticə verildikdən sonra növbəti nöqtədə durdurulmuş vəziyyətindən dönərsə, növbəti xəttin kodunu, bu halda yield yerinə yetirir və növbəti maddə kimi qaytarır. Bu funksiya sərbəst buraxılmır və bu anda StopIteration generatoru və StopIteration dövrü olur.

Beləliklə, generator obyekti bir adapterə bənzəyir - bir ucunda iterator protokolunu nümayiş etdirir, __iter__()next() yaxşı vəziyyətdə loop təmin etmək for . Digər tərəfdən isə, sonrakı dəyəri almaq üçün kifayət qədər funksiyanı işə salır və onu yenidən gözləmə rejiminə keçirir.

Niyə generatorlardan istifadə edirsiniz?

Ümumiyyətlə generatorları istifadə etməyən kodu yaza bilərsiniz, amma eyni məntiqi tətbiq edir. Bir seçim əvvəllər qeyd etdiyim müvəqqəti hiyləgər siyahıdan istifadə etməkdir. Bu, bütün hallarda işləməyəcək, məsələn, sonsuz döngələr varsa və ya həqiqətən uzun bir siyahı olduğunuzda yaddaşın səmərəsiz istifadəsinə səbəb ola bilər. Başqa bir yanaşma, dövləti nümunənin elementlərində saxlayır və Python 3-də next() üsulla (və ya __next__() növbəti mantıksal addımı yerinə yetirən yeni yinelər sinfi SomethingIter tətbiq etməkdir. Mantığa bağlı olaraq, next() metoddakı kod çox çətin və səhvlərə meylli ola bilər. Burada generatorlar təmiz və sadə bir həll təqdim edirlər.

1741
26 окт. istifadəçi tərəfindən verilmiş cavabı 28409 26 oktyabr 2008-10-26 00:22 '08 at 0:22 2008-10-26 00:22

Bunun kimi düşünün:

Bir yineleyici, növbəti () metodu olan bir obyekt üçün yalnız fantezi bir müddətdir. Beləliklə, ver-ed funksiyası nəticədə belə görünür:

Orijinal versiyası:

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

Bu əsasən Python tərcüməçisinin yuxarıdakı kodu ilə nədir:

 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 

Səhnələrin arxasında nə baş verdiyini daha yaxşı başa düşmək üçün, for loop aşağıdakı kimi yazıla bilər:

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

Daha mənalı və ya yalnız sizi qarışdırır mu? :)

Mən bunu nümunəvi məqsədlər üçün sadələşdirmə olduğuna işarə etməməlidir. :)

439
24 окт. 24 oktyabrda Jason Beykerə cavab verin 2008-10-24 01:28 '08 at 1:28 2008-10-24 01:28

yield sözü iki sadə həqiqətə əsaslanır:

  1. Derivator bir funksiyanın içərisində hər hansı bir yield açar sözünü algılarsa, bu funksiya artıq return ilə return . Bunun əvəzinə, dərhal generator deyilən tənbəl gözləyin siyahısı obyektini qaytarır.
  2. Generator təkrarlanır. Təkrarlana bilən nədir? Bu, hər bir maddəni müəyyən bir qaydada ziyarət etmək üçün quraşdırılmış bir protokol ilə bir range list range və ya diksiya görünüş kimi bir şeydir.

Bir ümumiyyətlə: bir generator tənbəl, tədricən artan bir siyahıdıryield generatorun tədricən çıxması lazım olan siyahı dəyərlərini proqramlaşdırmaq üçün qeyd funksiyasından istifadə etməyə imkan verir .

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

bir nümunə

Python range bənzəyən makeRange funksiyasını təyin edək. The makeRange(n) çağırışı makeRange(n) :

 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> 

Generatoru dərhal bekleyen dəyərləri qaytarmaq üçün onu list() (hər hansı təkrarlanan kimi list() ilə ötürə bilərsiniz:

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

Bir nümunəni "sadəcə bir siyahı qaytararkən" müqayisə etmək

Yuxarıdakı nümunə sadəcə əlavə etdiyiniz və qaytardığınız bir siyahısını yaratmaq kimi baxıla bilər:

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

Ancaq bir əhəmiyyətli fərq var; Son hissəyə baxın.


Generatorları necə istifadə edə bilərsiniz

Iterated siyahı anlayışın son hissəsidir və bütün generatorlar yinelenerek, beləliklə də tez-tez istifadə olunur:

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

Generatorları daha yaxşı başa düşmək üçün, itertools modulu ilə oynaya bilər ( chain.from_iterablechain.from_iterable üçün zəmanət chain istifadə chain.from_iterable əmin olun). Məsələn, itertools.count() kimi sonsuz uzun tənbəl siyahıları tətbiq etmək üçün generatorları istifadə edə bilərsiniz. Öz def enumerate(iterable): zip(count(), iterable) tətbiq edə bilərsiniz def enumerate(iterable): zip(count(), iterable) və ya alternativ olaraq, bir müddət loopda yield açar sözünü istifadə edin.

Qeyd edək ki, generatorlar bir çox digər məqsədlər üçün istifadə edilə bilər, məsələn, dotutinlər, qeyri-deterministik proqramlaşdırma və ya digər zərif şeylər. Ancaq burada təmsil etdiyim "tənbəl siyahılar" görünüşü, siz tapacaq ən çox istifadə edilən sahədir.


Səhnələr arxasında

Python İterasyon Protokolunun necə işlədiyi bu. Bir list(makeRange(5)) edərkən list(makeRange(5)) nə olur. Daha öncə mən "tənbəl, əlavə siyahı" kimi təsvir edirəm.

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

next() quraşdırılmış funksiya sadəcə "iteration protokolunun" bir hissəsi olan .next() obyektlərini çağırır və bütün iteratorlarda meydana gəlir. Əlavə qeyri-adi şeyləri həyata keçirmək üçün next() funksiyanı (və təkrarlama protokolunun digər hissələrini next() istifadə edə bilərsiniz, adətən okunabilirlik hesabına, beləliklə bunu etməmək üçün cəhd edin ...


kiçik şeylər

Adətən, əksər insanlar aşağıdakı fərqlərə diqqət yetirmirlər və yəqin ki, burada oxumağı dayandırmaq istəyirlər.

Python'da yineleyici, "bir for loop konsepsiyasını anlayan", məsələn, bir siyahı [1,2,3] hər hansı bir obyektdir və bir iterator üçün istənilən loopun xüsusi bir formasıdır, məsələn [1,2,3].__iter__() . Generator yazılmış şəkildə istisna olmaqla, hər hansı yineleyici ilə eynidır (funksiyanın sintaksisi ilə).

Bir yineleyiciyi siyahıdan soruşduğunuzda, yeni bir yineleyici yaradır. Lakin, yineleyicidən bir iteratordan (nadir hallarda etdiyiniz) müraciət etsəniz, sadəcə onun surətini verir.

Belə bir vəziyyətdə belə bir şey edə bilməyəcəyiniz bir vəziyyətdə ...

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

... sonra generatorun bir iterator olduğunu unutmayın; yəni bir dəfə istifadə edin. Yenidən istifadə etmək istəyirsinizsə, myRange(...) çağırmalısınız. x = list(myRange(5)) iki dəfə istifadə x = list(myRange(5)) və dəyişən x = list(myRange(5)) . Python PEP standartları iterator üçün təklif gecikdirildiğinden, tamamilə zəruri olduqda, bir generator (məsələn, hacker metaprogramcılığı çox pis işləyən məsələn) klonlamaq istəyənlər tamamilə zəruri olduqda itertools.tee istifadə edə bilərlər.

377
19 июня '11 в 9:33 2011-06-19 09:33 cavab 19 iyun 'da 9:33' də verildi

Pitonda yield açar sözü nədir?

Cavab Sxemi / Xülasə

  • Çağırış üzrə yield funksiyası bir generator qaytarır.
  • Jeneratörlər yineleyicilərdir, çünki onlar bir iterator protokolunu həyata keçirirlər, buna görə də onları təkrarlaya bilərsiniz.
  • Məlumat generator da göndərilə bilər ki, bu da konstruktiv bir vəziyyətdir.
  • В Python 3 вы можете делегировать от одного генератора другому в обоих направлениях с помощью yield from .
  • (Приложение критикует пару @, включая верхний, и обсуждает использование return в генераторе.)

Генераторы:

yield допустим только внутри определения функции, и включение yield в определение функции заставляет его возвращать генератор.

Идея для генераторов исходит из других языков (см. Сноску 1) с различными реализациями. В Python Generators выполнение кода заморожено в точке выхода. Когда вызывается генератор (методы обсуждаются ниже), выполнение возобновляется, а затем останавливается при следующем выходе.

yield предоставляет простой способ реализации протокола итератора , который определяется следующими двумя методами: __iter__ и next (Python 2) или __next__ (Python 3). Оба эти метода делают объект итератором, который можно проверить типом с помощью абстрактного базового класса Iterator из модуля collections .

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

Тип генератора является подтипом итератора:

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

И при необходимости мы можем проверить тип так:

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

Особенность Iterator заключается в том, что после его исчерпания вы не сможете повторно использовать или сбросить его:

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

Вам придется сделать еще один, если вы хотите снова использовать его функциональность (см. Сноску 2):

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

Можно получить данные программно, например:

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

Приведенный выше простой генератор также эквивалентен приведенному ниже - yield from Python 3.3 (и недоступен в Python 2), вы можете использовать yield from :

 def func(an_iterable): yield from an_iterable 

Тем не менее, yield from также позволяет делегировать субгенераторам, что будет объяснено в следующем разделе о совместном делегировании с субпрограммами.

Сопрограммы:

yield формирует выражение, позволяющее отправлять данные в генератор (см. сноску 3).

Вот пример, обратите внимание на received переменную, которая будет указывать на данные, которые отправляются в генератор:

 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) 

Сначала мы должны поставить генератор в очередь с помощью встроенной функции, next . Он будет вызывать соответствующий метод next или __next__ , в зависимости от используемой версии Python:

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

И теперь мы можем отправлять данные в генератор. ( Отправка None - это то же самое, что вызов next ):

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

Совместная делегация в суб-сопрограмме с yield from

Теперь напомним, что yield from доступен в Python 3. Это позволяет нам делегировать сопрограммы для подгруппы:

 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 

Вы можете прочитать больше о точной семантике yield from в PEP 380.

Другие методы: закрыть и бросить

Метод close вызывает GeneratorExit в тот момент, когда выполнение функции было заморожено. Это также будет вызываться __del__ так что вы можете поместить любой код очистки в место обработки GeneratorExit :

 >>> my_account.close() 

Вы также можете выдать исключение, которое может быть обработано в генераторе или передано обратно пользователю:

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

Nəticə

Я считаю, что я охватил все аспекты следующего вопроса:

Что делает ключевое слово yield в Python?

Оказывается, yield делает много. Я уверен, что мог бы добавить еще более подробные примеры к этому. Если вы хотите больше или имеете конструктивную критику, дайте мне знать, комментируя ниже.


Приложение:

Критика топ/принятого ответа **

  • Он запутался в том, что делает итерируемым , просто используя список в качестве примера. См. Мои ссылки выше, но вкратце: итерируемый имеет метод __iter__ возвращающий итератор . Итератор предоставляет .next (Python 2 или .__next__ (Python 3)), который неявно вызывается for циклов for до тех пор, пока он не StopIteration , и, как только он это сделает, он продолжит это делать.
  • Затем он использует выражение генератора, чтобы описать, что такое генератор. Поскольку генератор - это просто удобный способ создания итератора , он только сбивает с толку, а мы до сих пор не дошли до части yield .
  • В Управлении исчерпанием генератора он вызывает метод .next , когда вместо этого он должен использовать встроенную функцию, next . Это было бы подходящим уровнем косвенности, потому что его код не работает в Python 3.
  • Itertools? Это не имело отношения к тому, что yield вообще делает.
  • Нет обсуждения методов, которые предоставляет yield вместе с новой функциональностью, yield from в Python 3. Ответ верх/принят - очень неполный ответ.

Критика ответа, предполагающая yield в выражении или понимании генератора.

В настоящее время грамматика допускает любое выражение в понимании списка.

 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 

Так как yield является выражением, некоторые считают его интересным для использования в пониманиях или выражениях-генераторах, несмотря на то, что он не привел ни одного особенно хорошего варианта использования.

Разработчики ядра CPython обсуждают отказ от его разрешения . Вот соответствующий пост из списка рассылки:

On 30 January 2017 at 19:05, Brett Cannon wrote:

On Sun, 29 Jan 2017 at 16:39 Craig Rodrigues wrote:

I'm OK with either approach. Leaving things the way they are in Python 3 is no good, IMHO.

Мой голос - это ошибка синтаксиса, так как вы не получаете то, что ожидаете от синтаксиса.

Я бы согласился, что разумное место для нас в конечном итоге, так как любой код, основанный на текущем поведении, действительно слишком умен, чтобы его можно было поддерживать.

С точки зрения попадания туда, мы, вероятно, захотим:

  • Синтаксическое предупреждение или устаревшее предупреждение в 3.7
  • Py3k предупреждение в 2.7.x
  • Ошибка синтаксиса в 3.8

Cheers, Nick.

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

Кроме того, существует нерешенная проблема (10544), которая, похоже, указывает на то, что это никогда не будет хорошей идеей (PyPy, реализация Python, написанная на Python, уже вызывает предупреждения о синтаксисе.)

В итоге, пока разработчики CPython не скажут нам иначе: не помещайте yield в выражение или понимание генератора.

Оператор return в генераторе

В Python 2 :

В функции-генераторе оператор return не может включать expression_list список. В этом контексте пустой return указывает на то, что генератор завершен и вызовет StopIteration .

expression_list - это, по сути, любое количество выражений, разделенных запятыми. По сути, в Python 2 вы можете остановить генератор с помощью return , но вы не можете вернуть значение.

В Python 3 :

В функции генератора оператор return указывает на то, что генератор завершен и будет вызывать StopIteration . Возвращаемое значение (если оно есть) используется в качестве аргумента для создания StopIteration и становится атрибутом StopIteration.value .

Dəyişikliklər

  1. Языки CLU, Sather и Icon упоминались в предложении ввести концепцию генераторов в Python. Общая идея заключается в том, что функция может поддерживать внутреннее состояние и выдавать промежуточные точки данных по требованию пользователя. Это обещало быть превосходным по производительности по сравнению с другими подходами, включая потоки Python , которые даже недоступны в некоторых системах.

  2. Это означает, например, что объекты xrange ( range в Python 3) не являются Iterator , хотя они итеративны, потому что их можно использовать повторно. Как и списки, их методы __iter__ возвращают объекты итератора.

  3. yield был изначально представлен как оператор, то есть он мог появляться только в начале строки в блоке кода. Теперь yield создает выражение выхода. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt Это изменение было предложено, чтобы позволить пользователю отправлять данные в генератор так же, как они могут быть получены. Чтобы отправить данные, нужно иметь возможность назначить их чему-либо, и для этого оператор просто не будет работать.

286
ответ дан Aaron Hall 25 июня '15 в 9:11 2015-06-25 09:11

yield аналогичен return - он возвращает все, что вы ему скажете (как генератор). Разница в том, что при следующем вызове генератора выполнение начинается с последнего вызова оператора yield . В отличие от return, кадр стека не очищается при возникновении выхода, однако управление передается обратно вызывающей стороне, поэтому его состояние возобновится при следующем вызове функции.

В случае вашего кода функция get_child_candidates действует как итератор, поэтому при расширении списка она добавляет один элемент за один раз в новый список.

list.extend вызывает итератор, пока он не исчерпан. В случае с примером кода, который вы разместили, было бы намного проще просто вернуть кортеж и добавить его в список.

255
ответ дан Douglas Mayle 24 окт. '08 в 1:24 2008-10-24 01:24

Есть еще одна вещь, которую стоит упомянуть: функция, которая возвращает результат, на самом деле не должна завершаться. Я написал такой код:

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

Тогда я могу использовать его в другом коде, например так:

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

Это действительно помогает упростить некоторые проблемы и облегчает работу с некоторыми вещами.

197
ответ дан Claudiu 24 окт. '08 в 11:44 2008-10-24 11:44

Для тех, кто предпочитает минимальный рабочий пример, медитируйте на этом интерактивном сеансе Python :

 >>> 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 
168
ответ дан Daniel 18 янв. '13 в 20:25 2013-01-18 20:25

TL; DR

Вместо этого:

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

сделай это:

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

Всякий раз, когда вы оказываетесь строить список с нуля, yield каждого куска вместо этого.

Это был мой первый "ага" момент с доходностью.


yield - это сладкий способ сказать

построить серию вещей

Такое же поведение:

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

Разное поведение:

Урожай однопроходный : вы можете пройти только один раз. Когда у функции есть выход, мы называем ее функцией генератора . И итератор - это то, что он возвращает. Эти термины показательны. Мы теряем удобство контейнера, но получаем мощность ряда, который вычисляется по мере необходимости и произвольно долго.

Выход ленивый , он откладывает вычисления. Функция с выходом в нем фактически не выполняется вообще, когда вы ее вызываете. Он возвращает объект итератора, который запоминает, где он остановился. Каждый раз, когда вы вызываете next() на итераторе (это происходит в цикле for), выполнение в дюймах вперед до следующего выхода. return вызывает StopIteration и заканчивает серию (это естественный конец цикла for).

Урожай универсален . Данные не должны храниться все вместе, они могут быть доступны по одному за раз. Это может быть бесконечно.

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

Если вам нужно несколько проходов, и серия не слишком длинная, просто вызовите list() :

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

Блестящий выбор слова yield потому что применяются оба значения :

Урожай - производить или предоставлять (как в сельском хозяйстве)

... предоставить следующие данные в серии.

уступить - уступить или отказаться (как при политической власти)

... отказаться от выполнения процессора, пока итератор не продвинется.

150
ответ дан Bob Stein 25 марта '16 в 16:21 2016-03-25 16:21

Выход дает вам генератор.

 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 

Как видите, в первом случае foo хранит весь список в памяти сразу. Это не имеет большого значения для списка из 5 элементов, но что, если вы хотите список из 5 миллионов? Мало того, что это огромный пожиратель памяти, он также требует много времени для создания во время вызова функции. Во втором случае, бар просто дает вам генератор. Генератор является итеративным - это означает, что вы можете использовать его в цикле for и т.д., Но к каждому значению можно получить доступ только один раз. Все значения также не сохраняются в памяти одновременно; объект генератора "запоминает", где он находился в цикле в последний раз, когда вы его вызывали - таким образом, если вы используете итеративный подсчет (скажем) до 50 миллиардов, вам не нужно считать до 50 миллиардов всех и запомните 50 миллиардов номеров Опять же, это довольно надуманный пример, вы, вероятно, использовали бы itertools, если бы вы действительно хотели сосчитать до 50 миллиардов. :)

Это самый простой вариант использования генераторов. Как вы сказали, его можно использовать для написания эффективных перестановок, используя yield для продвижения по стеку вызовов вместо использования некоторой переменной стека. Генераторы также могут быть использованы для специализированного обхода дерева и всего прочего.

146
ответ дан RBansal 16 янв. '13 в 9:42 2013-01-16 09:42

Это возвращение генератора. Я не особенно знаком с Python, но я верю, что это то же самое, что и блоки итераторов С#, если вы знакомы с ними.

Ключевая идея заключается в том, что компилятор/интерпретатор/что-либо делает какую-то хитрость, так что, что касается вызывающего, они могут продолжать вызывать next(), и он будет продолжать возвращать значения - как если бы метод генератора был приостановлен. Теперь, очевидно, вы не можете "приостановить" метод, поэтому компилятор создает конечный автомат, чтобы вы могли запомнить, где вы находитесь в данный момент, как выглядят локальные переменные и т.д. Это гораздо проще, чем написать итератор самостоятельно.

140
ответ дан Jon Skeet 24 окт. '08 в 1:26 2008-10-24 01:26

Есть один тип ответа, который я не чувствую, был дан, среди многих отличных @, которые описывают, как использовать генераторы. Вот ответ теории языка программирования:

Оператор yield в Python возвращает генератор. Генератор в Python - это функция, которая возвращает продолжения (и, в частности, тип сопрограммы, но продолжения представляют собой более общий механизм для понимания происходящего).

Продолжения в теории языков программирования - гораздо более фундаментальный вид вычислений, но они не часто используются, потому что их чрезвычайно сложно рассуждать, а также очень трудно реализовать. Но идея о том, что такое продолжение, проста: это состояние вычислений, которое еще не закончено. В этом состоянии текущие значения переменных, операции, которые еще предстоит выполнить, и т.д. Сохраняются. Затем в какой-то момент позже в программе может быть вызвано продолжение, так что программные переменные сбрасываются в это состояние и выполняются сохраненные операции.

Продолжения в этом более общем виде могут быть реализованы двумя способами. В способе call/cc стек программы буквально сохраняется, а затем, когда вызывается продолжение, стек восстанавливается.

В стиле передачи продолжения (CPS), продолжения - это просто обычные функции (только в языках, где функции первого класса), которыми программист явно управляет и передает их подпрограммам. В этом стиле состояние программы представлено замыканиями (и переменными, которые в них кодируются), а не переменными, которые находятся где-то в стеке. Функции, управляющие потоком управления, принимают продолжение в качестве аргументов (в некоторых вариантах CPS функции могут принимать несколько продолжений) и управляют потоком управления, вызывая их, просто вызывая их и возвращая потом. Очень простой пример стиля передачи продолжения следующий:

 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) 

В этом (очень упрощенном) примере программист сохраняет операцию фактической записи файла в продолжение (которое может быть очень сложной операцией с большим количеством деталей для записи), а затем передает это продолжение (т.е. Как первый закрытие класса) другому оператору, который выполняет дополнительную обработку, а затем вызывает ее при необходимости. (Я часто использую этот шаблон проектирования в реальном программировании GUI, потому что он экономит мне строки кода или, что более важно, управляет потоком управления после запуска событий GUI.)

Остальная часть этого поста, без потери общности, концептуализирует продолжения как CPS, потому что это чертовски легко понять и прочитать.


Теперь давайте поговорим о генераторах в Python. Генераторы - это определенный подтип продолжения. В то время как продолжения в целом могут сохранять состояние вычислений (т.е. Стек вызовов программ), генераторы могут сохранять только состояние итерации по итератору . Хотя это определение слегка вводит в заблуждение для определенных случаев использования генераторов. Məsələn:

 def f(): while True: yield 4 

Это явно разумная итерация, поведение которой четко определено - каждый раз, когда генератор повторяет ее, он возвращает 4 (и делает это всегда). Но это, вероятно, не тип прототипа итерируемого, который приходит на ум, когда мы думаем об итераторах (т.е. for x in collection: do_something(x) ). Этот пример иллюстрирует мощь генераторов: если что-то является итератором, генератор может сохранить состояние своей итерации.

Повторим: продолжения могут сохранять состояние программного стека, а генераторы могут сохранять состояние итерации. Это означает, что продолжения более мощные, чем генераторы, но также и то, что генераторы намного, намного проще. Их легче реализовать для языкового дизайнера, и их легче использовать программисту (если у вас есть какое-то время для записи, попробуйте прочитать и понять эту страницу о продолжениях и вызвать /cc ).

Но вы можете легко реализовать (и концептуализировать) генераторы как простой, конкретный случай стиля передачи продолжения:

Всякий раз, когда вызывается yield , он сообщает функции о возвращении продолжения. Когда функция вызывается снова, она начинается с того места, где она остановилась. Итак, в псевдопсевдокоде (то есть не в псевдокоде, а в коде) генератор 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 

где ключевое слово yield самом деле является синтаксическим сахаром для реальной функции генератора, что-то вроде:

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

Помните, что это просто псевдокод, а фактическая реализация генераторов в Python более сложна. Но в качестве упражнения, чтобы понять, что происходит, попробуйте использовать стиль передачи продолжения для реализации объектов генератора без использования ключевого слова yield .

131
ответ дан aestrivex 04 апр. '13 в 17:56 2013-04-04 17:56

Вот пример на простом языке. Я приведу соответствие между человеческими концепциями высокого уровня и концепциями Python низкого уровня.

Я хочу работать с последовательностью чисел, но я не хочу беспокоить себя созданием этой последовательности, я хочу сосредоточиться только на операции, которую я хочу сделать. Итак, я делаю следующее:

  • Я позвоню вам и скажу, что мне нужна последовательность чисел, которая производится определенным образом, и я дам вам знать, что это за алгоритм.
    Этот шаг соответствует def функции генератора, то есть функции, содержащей yield .
  • Некоторое время спустя я говорю вам: "Хорошо, будьте готовы рассказать мне последовательность чисел".
    Этот шаг соответствует вызову функции генератора, которая возвращает объект генератора. Обратите внимание, что вы еще не сказали мне никаких чисел; Вы просто берете свою бумагу и карандаш.
  • Я спрашиваю вас: "Скажите мне следующий номер", а вы скажите мне первый номер; после этого вы ждете, чтобы я попросил у вас следующий номер. Ваша работа - помнить, где вы были, какие цифры вы уже сказали и какой следующий. Меня не волнуют детали.
    Этот шаг соответствует вызову .next() для объекта генератора.
  • ... повторять предыдущий шаг, пока...
  • в конце концов, вы можете прийти к концу. Вы не говорите мне номер; ты просто кричишь: "Держи лошадей! Я готов! Больше никаких цифр!"
    Этот шаг соответствует объекту-генератору, завершающему свою работу и StopIteration исключение StopIteration Функция-генератор не должна вызывать исключение. Возникает автоматически, когда функция завершается или выдает return .

Это то, что делает генератор (функция, которая содержит yield ); он начинает выполнение, делает паузу всякий раз, когда производит yield , а когда его запрашивают значение .next() он продолжает с того места, где он был последним. Он идеально подходит по дизайну к протоколу итератора Python, который описывает, как последовательно запрашивать значения.

Самым известным пользователем протокола итератора является команда for в Python. Итак, всякий раз, когда вы делаете:

 for item in sequence: 

не имеет значения, является ли sequence списком, строкой, словарем или объектом-генератором, как описано выше; результат тот же: вы читаете элементы из последовательности один за другим.

Обратите внимание, что def функции, содержащей ключевое слово yield - не единственный способ создания генератора; это просто самый простой способ создать его.

Для получения более точной информации читайте о типах итераторов , выражении yield и генераторах в документации по Python.

117
ответ дан tzot 24 окт. '08 в 3:36 2008-10-24 03:36

Хотя многие ответы показывают, почему вы используете yield для создания генератора, есть и другие способы использования yield . Сделать сопрограмму довольно просто, что позволяет передавать информацию между двумя блоками кода. Я не буду повторять ни одного из прекрасных примеров, которые уже были приведены об использовании yield для создания генератора.

Чтобы помочь понять, что делает yield в следующем коде, вы можете использовать палец для отслеживания цикла по любому коду, который имеет yield . Каждый раз, когда ваш палец достигает yield , вы должны ждать next или send которые будут введены. Когда вызывается next оператор, вы прослеживаете код до тех пор, пока не достигнете yield ... код справа от yield оценивается и возвращается вызывающей стороне... затем вы ждете. Когда снова вызывается next , вы выполняете еще один цикл по коду. Тем не менее, вы заметите, что в сопрограмме yield также может использоваться с send ..., который отправит значение из вызывающей стороны в функцию уступки. Если дано значение send , то yield возвращает полученное значение и выплевывает его в левую сторону... затем трассировка в коде продолжается до тех пор, пока вы снова не достигнете yield (возвращая значение в конце, как если бы вызывался next ).

Məsələn:

 >>> 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() 
103
ответ дан Mike McKerns 04 февр. '14 в 5:27 2014-02-04 05:27

Существует еще одно использование и значение yield (начиная с Python 3.3):

 yield from <expr> 

От PEP 380 - Синтаксис для делегирования субгенератору :

Синтаксис предлагается для генератора, чтобы делегировать часть своих операций другому генератору. Это позволяет разделить код, содержащий "yield", и поместить его в другой генератор. Кроме того, субгенератору разрешено возвращать со значением, и это значение становится доступным для делегирующего генератора.

Новый синтаксис также открывает некоторые возможности для оптимизации, когда один генератор возвращает значения, созданные другим.

Более того, это представит (начиная с Python 3.5):

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

чтобы не перепутать сопрограммы с обычным генератором (сегодня в обоих случаях используется yield ).

94
ответ дан Sławomir Lenart 25 июля '14 в 0:15 2014-07-25 00:15

Все отличные ответы, однако немного сложны для новичков.

Я полагаю, вы узнали return выражение.

По аналогии, return и yield близнецы. return означает "возврат и остановка", тогда как "yield" означает "возврат, но продолжение"

  1. Попробуйте получить num_list с return .
 def num_list(n): for i in range(n): return i 

Запустить его:

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

Видите, вы получаете только один номер, а не их список. return никогда не позволяет вам счастливо победить, просто реализуетесь один раз и выходите.

  1. Там приходит yield

Заменить return на yield :

 In [10]: def num_list(n): ...: for i in range(n): ...: yield i ...: In [11]: num_list(3) Out[11]: <generator object num_list at 0x10327c990> In [12]: list(num_list(3)) Out[12]: [0, 1, 2] 

Теперь вы выиграли, чтобы получить все цифры.

По сравнению с return который запускается один раз и останавливается, yield запускает запланированные вами времена. Вы можете интерпретировать return как return one of them , а yield как return all of them . Это называется iterable .

  1. Еще один шаг, который мы можем переписать yield оператора с 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] 

Это ядро о yield .

Разница между списком return выходами и объектом yield продукцией является:

Вы всегда будете получать [0, 1, 2] из объекта списка, но только мог извлечь их из "на объект yield продукции" один раз. Итак, у него есть новый объект generator имен, как показано в Out[11]: <generator object num_list at 0x10327c990> .

В заключение, в качестве метафоры, чтобы понять это:

  • return и yield близнецы
  • list и generator близнецы
82
ответ дан JawSaw 14 нояб. '17 в 15:02 2017-11-14 15:02

Вот несколько примеров Python о том, как на самом деле реализовать генераторы, как если бы Python не предоставил им синтаксический сахар:

Как генератор Python:

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

Использование лексических замыканий вместо генераторов

 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) 

Использование замыканий объектов вместо генераторов (потому что ClosuresAndObjectsAreEquivalent )

 class fib_gen3: def __init__(self): self.a, self.b = 1, 1 def __call__(self): r = self.a self.a, self.b = self.b, self.a + self.b return r assert [1,1,2,3,5] == ftake(fib_gen3(), 5) 
82
ответ дан Dustin Getz 03 окт. '12 в 23:38 2012-10-03 23:38

Я собирался опубликовать "прочитайте страницу 19 из Beazley" Python: Essential Reference "для быстрого описания генераторов", но многие другие уже опубликовали хорошие описания.

Также обратите внимание, что yield может быть использован в сопрограммах как двойное их использование в функциях генератора. Хотя это не то же самое использование, что и ваш фрагмент кода, (yield) может использоваться как выражение в функции. Когда вызывающая сторона отправляет значение методу с помощью метода send() , сопрограмма будет выполняться до тех пор, пока не встретится следующий оператор (yield) .

Генераторы и сопрограммы - отличный способ настроить приложения типа потока данных. Я подумал, что стоило бы знать о другом использовании оператора yield в функциях.

80
ответ дан johnzachary 28 янв. '13 в 4:37 2013-01-28 04:37

С точки зрения программирования, итераторы реализованы в виде блоков .

Чтобы реализовать итераторы, генераторы и пулы потоков для одновременного выполнения и т.д. В виде групповых сообщений (также называемых анонимными функциями), используются сообщения, отправляемые объекту замыкания, в котором есть диспетчер, а диспетчер отвечает на "сообщения".

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

"next" - это сообщение, отправленное закрытию, созданное вызовом "iter".

Есть много способов реализовать это вычисление. Я использовал мутацию, но это легко сделать без мутации, возвращая текущее значение и следующий урожай.

Вот демонстрация, которая использует структуру R6RS, но семантика абсолютно идентична Python. Это та же модель вычислений, и для ее переписывания на Python требуется только изменение синтаксиса.

 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) '(ab)) 'OK -> (stream 'yield) 'a -> (stream 'yield) 'b -> (stream 'yield) 'END -> (stream 'yield) 'END -> 
75
ответ дан alinsoar 21 авг. '13 в 22:01 2013-08-21 22:01

Вот простой пример:

 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) 

Выход:

 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 

Я не являюсь разработчиком Python, но, как мне кажется, yield сохраняет позицию потока программы, а следующий цикл начинается с позиции yield. Кажется, что он ждет в этой позиции, и только перед этим, возвращает значение за пределами, и в следующий раз продолжает работать.

Вроде бы интересная и приятная способность: D

68
ответ дан Engin OZTURK 20 дек. '13 в 16:07 2013-12-20 16:07

Вот мысленный образ того, что делает yield .

Мне нравится думать о потоке как о стеке (даже если он не реализован таким образом).

Когда вызывается нормальная функция, она помещает свои локальные переменные в стек, выполняет некоторые вычисления, затем очищает стек и возвращает результат. Значения его локальных переменных больше никогда не видны.

С помощью функции yield , когда ее код начинает работать (т.е. После вызова функции, возвращающей объект генератора, чей метод next() затем вызывается), он аналогичным образом помещает свои локальные переменные в стек и вычисляет некоторое время. Но затем, когда он попадает в оператор yield , прежде чем очистить свою часть стека и вернуться, он делает снимок своих локальных переменных и сохраняет их в объекте генератора. Он также записывает место, в котором он находится в данный момент в своем коде (т.е. Конкретный оператор yield ).

Так что это своего рода замороженная функция, на которой висит генератор.

Когда впоследствии вызывается next() , он извлекает принадлежащие функции в стек и реанимирует его. Функция продолжает вычислять с того места, где она остановилась, не обращая внимания на тот факт, что она только что провела вечность в холодильных камерах.

Сравните следующие примеры:

 def normalFunction(): return if False: pass def yielderFunction(): return if False: yield 12 

Когда мы вызываем вторую функцию, она ведет себя совершенно иначе, чем первая. Оператор yield может быть недоступен, но если он присутствует где-либо, он меняет природу того, с чем мы имеем дело.

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

Вызов yielderFunction() не запускает его код, но делает генератор из кода. (Возможно, было бы неплохо назвать такие вещи префиксом 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 body. 'send', 'throw'] 

В gi_code и gi_frame хранится замороженное состояние. Изучая их с помощью dir(..) , мы можем подтвердить, что наша ментальная модель выше заслуживает доверия.

57
ответ дан Evgeni Sergeev 14 июня '13 в 19:36 2013-06-14 19:36

Как и предполагает каждый ответ, yield используется для создания генератора последовательности. Он используется для генерации некоторой последовательности динамически. Например, читая файл построчно в сети, вы можете использовать функцию yield следующим образом:

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

Вы можете использовать его в своем коде следующим образом:

 for line in getNextLines(): doSomeThing(line) 

Контроль исполнения перевода получил

Управление выполнением будет передано из getNextLines() в цикл for при выполнении yield. Таким образом, каждый раз, когда вызывается getNextLines(), выполнение начинается с того места, где оно было приостановлено в последний раз.

Таким образом, вкратце, функция со следующим кодом

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

распечатает

 "first time" "second time" "third time" "Now some useful value 12" 
47
ответ дан Mangu Singh Rajpurohit 29 июля '15 в 9:11 2015-07-29 09:11

(Мой ответ ниже говорит только с точки зрения использования генератора Python, а не базовой реализации механизма генератора , который включает в себя некоторые приемы работы со стеком и кучей.)

Когда yield используется вместо return в функции python, эта функция превращается в нечто особенное, называемое generator function . Эта функция вернет объект типа generator . Ключевое слово yield - это флаг, который уведомляет компилятор python о специальной обработке такой функции. Нормальные функции завершатся, когда из него будет возвращено некоторое значение. Но с помощью компилятора функцию генератора можно считать возобновляемой. Таким образом, контекст выполнения будет восстановлен, и выполнение будет продолжено с последнего запуска. Пока вы явно не StopIteration return, что StopIteration исключение StopIteration (которое также является частью протокола итератора), или не достигнет конца функции. Я нашел много ссылок о generator , но это один из functional programming perspective является наиболее перевариваемой.

(Теперь я хочу поговорить об обосновании generator и об iterator основанном на моем собственном понимании. Я надеюсь, что это поможет вам понять основную мотивацию итератора и генератора. Такая концепция проявляется и в других языках, таких как С#.)

Как я понимаю, когда мы хотим обработать кучу данных, мы обычно сначала храним данные где-то, а затем обрабатываем их одну за другой. Но такой наивный подход проблематичен. Если объем данных огромен, заранее хранить их в целом дорого. Таким образом, вместо непосредственного хранения самих data , почему бы не сохранить некоторые metadata косвенно, то есть the logic how the data is computed .

Существует два подхода к переносу таких метаданных.

  1. Подход OO, мы as a class метаданные as a class . Это так называемый iterator который реализует протокол итератора (то есть __next__() и __iter__() ). Это также часто встречающийся шаблон проектирования итераторов .
  2. Функциональный подход, мы оборачиваем метаданные as a function . Это так называемая generator function . Но под капотом возвращенный generator object все еще является итератором IS-A поскольку он также реализует протокол итератора.

В любом случае создается итератор, то есть некоторый объект, который может дать вам нужные данные. Подход ОО может быть немного сложным. В любом случае, какой из них использовать - решать только вам.

42
ответ дан smwikipedia 25 марта '16 в 8:40 2016-03-25 08:40

Урожай является объектом

return в функции вернет одно значение.

Если вы хотите, чтобы функция возвращала огромный набор значений , используйте yield .

Что еще более важно, yield является барьером .

как барьер в языке CUDA, он не будет передавать управление, пока не будет завершено.

То есть он будет запускать код в вашей функции с самого начала, пока не достигнет yield . Затем он вернет первое значение цикла.

Затем каждый второй вызов будет запускать цикл, который вы написали в функции, еще раз, возвращая следующее значение, пока не будет возвращено никакого значения.

42
ответ дан Kaleem Ullah 01 сент. '15 в 15:42 2015-09-01 15:42

Таким образом, оператор yield превращает вашу функцию в фабрику, которая создает специальный объект, называемый generator который оборачивает тело вашей исходной функции. Когда generator повторяется, он выполняет вашу функцию, пока не достигнет следующего yield затем приостанавливает выполнение и оценивает значение, переданное в yield . Он повторяет этот процесс на каждой итерации до тех пор, пока путь выполнения не выйдет из функции. Məsələn,

 def simple_generator(): yield 'one' yield 'two' yield 'three' for i in simple_generator(): print i 

просто выводит

 one two three 

Питание исходит от использования генератора с циклом, который вычисляет последовательность, генератор выполняет остановку цикла каждый раз, чтобы "выдать" следующий результат вычисления, таким образом, он вычисляет список на лету, при этом преимущество заключается в памяти сохранено для особо крупных расчетов

Скажем, вы хотите создать свою собственную функцию range которая производит итеративный диапазон чисел, вы можете сделать это так,

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

и используйте это так;

 for i in myRangeNaive(10): print i 

Но это неэффективно, потому что

  • Вы создаете массив, который используете только один раз (это тратит впустую память)
  • Этот код фактически зацикливается на этом массиве дважды! :(

К счастью, Гвидо и его команда были достаточно щедры на разработку генераторов, поэтому мы могли просто сделать это;

 def myRangeSmart(i): n = 0 while n < i: yield n n = n + 1 return for i in myRangeSmart(10): print i 

Теперь после каждой итерации функция в генераторе, вызываемая next() выполняет функцию до тех пор, пока не достигнет оператора yield, в котором она останавливается и возвращает значение, или достигает конца функции. В этом случае при первом вызове next() выполняется до оператора yield и выдает "n", при следующем вызове выполняет оператор приращения, возвращается к "времени", оценивает его и, если оно истинно, остановится и снова выдаст 'n', так будет продолжаться до тех пор, пока условие while не вернет false и генератор не перейдет к концу функции.

41
ответ дан redbandit 13 окт. '16 в 16:43 2016-10-13 16:43

Многие люди используют return а не yield , но в некоторых случаях yield может быть более эффективным и с ним легче работать.

Вот пример, для которого yield определенно лучше:

return (in function)

 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 

yield (in function)

 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. 

Calling functions

 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) 

Обе функции делают одно и то же, но yield использует три строки вместо пяти и имеет на одну переменную меньше, о которой нужно беспокоиться.

Это результат из кода:

2019

ответ дан Tom Fuller 10 сент. '16 в 14:37 2016-09-10 14:37

Ключевое слово yield просто собирает возвращаемые результаты. Думайте о yield как о return +=

35
ответ дан Bahtiyar Özdere 18 нояб. '15 в 22:37 2015-11-18 22:37

yield подобен возвращаемому элементу для функции. Разница в том, что элемент yield превращает функцию в генератор. Генератор ведет себя так же, как функция, пока что-то не "уступит". Генератор останавливается до следующего вызова и продолжает работу с той же точки, с которой он был запущен. Вы можете получить последовательность всех "полученных" значений в одном, вызвав list(generator()) .

35
ответ дан Theoremiser 20 мая '15 в 9:19 2015-05-20 09:19

Здесь простой подход, основанный на yield , для вычисления ряда Фибоначчи, объяснил:

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

Когда вы введете это в свой REPL, а затем попытаетесь позвонить, вы получите загадочный результат:

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

Это связано с тем, что присутствие yield сигнализирует Python, что вы хотите создать генератор, то есть объект, который генерирует значения по требованию.

Итак, как вы генерируете эти значения? Это можно сделать либо напрямую, используя next встроенную функцию, либо косвенно, передав ее в конструкцию, которая потребляет значения.

Используя встроенную функцию next() , вы напрямую вызываете .next / __next__ , заставляя генератор __next__ значение:

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

Косвенно, если вы предоставите fib for цикла for , инициализатора list инициализатора tuple или чего-либо еще, что ожидает объект, который генерирует/производит значения, вы будете "потреблять" генератор, пока он не сможет произвести больше значений ( и оно возвращается)

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

Аналогично, с инициализатором tuple :

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

Генератор отличается от функции в том смысле, что он ленив. Это достигается путем поддержания локального состояния и возобновления работы в любое время.

Когда вы впервые вызываете fib , вызывая его:

 f = fib() 

Python компилирует функцию, встречает ключевое слово yield и просто возвращает объект генератора обратно к вам. Не очень полезно, кажется.

Когда вы затем запрашиваете, он генерирует первое значение, прямо или косвенно, он выполняет все найденные операторы, пока не встретит yield , а затем возвращает значение, которое вы указали для yield и делает паузы. Для примера, который лучше демонстрирует это, позвольте использовать некоторые вызовы print (замените на print "text" если на Python 2):

 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 go through it again.") 

Теперь введите в REPL:

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

у вас есть объект генератора, ожидающий команды для его генерации значения. Используйте next и посмотрите, что будет напечатано:

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

Результаты без кавычек - то, что напечатано. Указанный результат - это то, что возвращается из yield . Звоните next снова сейчас:

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

Генератор запоминает, что он был приостановлен на уровне yield value и возобновляет оттуда. Следующее сообщение распечатывается и поиски yield заявления, чтобы сделать паузу в это выполняется снова (из - за в while цикла).

32
ответ дан Jim Fasarakis Hilliard 20 февр. '16 в 20:41 2016-02-20 20:41

Простой пример того, что это легко объяснить: yield

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

Выход:

 1 2 1 2 1 2 1 2 
28
ответ дан Gavriel Cohen 02 янв. '17 в 15:09 2017-01-02 15:09
  • 1
  • 2

Другие вопросы по меткам или Задайте вопрос