Praktiškai, kokie yra pagrindiniai Python 3.3 sintaksės panaudojimo būdai?

Man sunku perkelti savo smegenis į PEP 380 .

  • Kokios yra situacijos, kai „pelningumas“ yra naudingas?
  • Kas yra klasikinis naudojimo atvejis?
  • Kodėl tai yra palyginti su mikroplokštelėmis?

[atnaujinti]

Dabar suprantu savo sunkumų priežastį. Naudojau generatorius, bet niekada nenaudojau coroutine (pateikė PEP-342 ). Nepaisant kai kurių panašumų, generatoriai ir coroutines iš esmės sudaro dvi skirtingas sąvokas. Supratimas apie naująją sintaksę yra raktų supratimas (ne tik generatoriai).

IMHO coroutines yra labiausiai neaiški Python funkcija , dauguma knygų tampa nenaudinga ir neįdomu.

Dėkojame už puikius atsakymus, bet ypatingai ačiū agf ir jo komentarui, susijusiam su David Basley . Dovydo uolos.

184
14 марта '12 в 22:33 2012-03-14 22:33 Paulo Scardine yra nustatytas kovo 14 d., 12 val. 10:33 2012-03-14 22:33
@ 5 atsakymai

Leiskite man iš pradžių padaryti vieną dalyką. Paaiškinimas, kad yield from g lygiavertis for v in g: yield v net nepradeda pateisinti, kas yra yield from . Nes leiskite jam susidurti su tuo, jei visa yield from tikrųjų išplečia „ for kilpą, tuomet ji negarantuoja yield from pridėjimo į kalbą ir neleidžia „Python 2.x“ įgyvendinti keletą naujų funkcijų.

Pagalvokite apie tai, kaip pateikti skaidrų dvipusį kanalą nuo caller iki sub-generator . Tai apima ir duomenų gavimą, ir duomenų siuntimą į sub-generator . Beje, jei nesate tikri, kas siunčia duomenis į generatorių, tai reiškia, kad reikia viską nuvilkti ir pirmiausia skaityti apie Coroutines. Dave Basley Smalsus kurutinų kursas yra puiki pradžia. Skaitykite 24-33 skaidres, skirtas greitam gruntui.

Duomenų skaitymas iš generatoriaus naudojant išvestį iš

 def reader(): """A generator that fakes a read from a file, socket, etc.""" for i in range(4): yield '<< %s' % i def reader_wrapper(g): # Manually iterate over data produced by reader for v in g: yield v wrap = reader_wrapper(reader()) for i in wrap: print(i) # Result << 0 << 1 << 2 << 3 

Užuot rankiniu būdu kartoję reader() , mes galime tiesiog yield from to gauti.

 def reader_wrapper(g): yield from g 

Tai veikia ir mes pašalinome vieną eilutę. Ir tikriausiai ketinimas yra šiek tiek aiškesnis (ar ne). Bet nieko nekeičia.

Duomenų siuntimas į generatorių (coroutine) naudojant išvestį iš 1 dalies

Dabar darykime kažką įdomesnio. Leiskite sukurti „coroutine“, vadinamą writer kuris priima jai atsiųstus duomenis ir jį įrašo į lizdą, fd ir tt

 def writer(): """A coroutine that writes data *sent* to it to fd, socket, etc.""" while True: w = (yield) print('>> ', w) 

Dabar kyla klausimas, kaip turėtų būti apdorojama vyniojimo funkcija, kad būtų galima siųsti duomenis į įrašą, kad bet kokie duomenys, siunčiami į vyną, būtų skaidriai siunčiami writer() ?

 def writer_wrapper(coro): # TBD pass w = writer() wrap = writer_wrapper(w) wrap.send(None) # "prime" the coroutine for i in range(4): wrap.send(i) # Expected result >> 0 >> 1 >> 2 >> 3 

Apvyniojimas turi priimti duomenis, siunčiamus jai (žinoma), taip pat procesą StopIteration kai „for loop“ yra išnaudota. Akivaizdu, kad tiesiog for x in coro: yield x nebus. Čia yra versija, kuri veikia.

 def writer_wrapper(coro): coro.send(None) # prime the coro while True: try: x = (yield) # Capture the value that sent coro.send(x) # and pass it to the writer except StopIteration: pass 

Arba galėtume tai padaryti.

 def writer_wrapper(coro): yield from coro 

Tai taupo 6 kodų eilutes, daro jį daug lengviau skaitomą ir veikia tik. Magija!

Duomenų siuntimas į generatoriaus išvestį iš - 2 dalies - tvarkymo išimtys

Padarykite sunkiau. Ką daryti, jei mūsų rašytojas turi susidoroti su išimtimis? Tarkime, writer tvarko „ SpamException ir spausdina *** jei jis susiduria su juo.

 class SpamException(Exception): pass def writer(): while True: try: w = (yield) except SpamException: print('***') else: print('>> ', w) 

Ką daryti, jei nepakeisime writer_wrapper ? Ar tai veikia? Pabandykite

 # writer_wrapper same as above w = writer() wrap = writer_wrapper(w) wrap.send(None) # "prime" the coroutine for i in [0, 1, 2, 'spam', 4]: if i == 'spam': wrap.throw(SpamException) else: wrap.send(i) # Expected Result >> 0 >> 1 >> 2 *** >> 4 # Actual Result >> 0 >> 1 >> 2 Traceback (most recent call last): ... redacted ... File ... in writer_wrapper x = (yield) __main__.SpamException 

Um, jis neveikia, nes x = (yield) tiesiog išmeta išimtį, o viskas baigiasi. Leiskite jam veikti, bet ji rankiniu būdu tvarko išimtis ir siunčia juos arba išmeta juos į sub-generatorių ( writer )

 def writer_wrapper(coro): """Works. Manually catches exceptions and throws them""" coro.send(None) # prime the coro while True: try: try: x = (yield) except Exception as e: # This catches the SpamException coro.throw(e) else: coro.send(x) except StopIteration: pass 

Jis veikia.

 # Result >> 0 >> 1 >> 2 *** >> 4 

Bet kaip tai yra!

 def writer_wrapper(coro): yield from coro 

yield from skaidriai tvarko siuntimo vertes arba mesti vertes subgeneratoriui.

Jis vis dar neapima visų kampinių atvejų. Kas atsitiks, jei išorinis generatorius yra uždarytas? Ką apie atvejį, kai subgeneratorius grąžina vertę (taip, 3 pythone, generatoriai gali grąžinti vertes), kaip turėtų būti paskirstyta grąžinimo vertė? Visi kampiniai atvejai, kurie yra skaidrūs, yra tikrai įspūdingi . yield from tik stebuklingai veikia ir tvarko visus šiuos atvejus.

Aš asmeniškai manau, kad yield from yra blogas raktinio žodžio pasirinkimas, nes jis nėra akivaizdus dvišalis. Buvo pasiūlyti kiti raktiniai žodžiai (pvz., delegate , tačiau buvo atmesti, nes naujojo raktinio žodžio įtraukimas į kalbą yra daug sudėtingesnis nei derinant esamus).

Apskritai geriausia galvoti apie yield from kaip transparent two way channel tarp caller ir sub-generator .

Literatūra:

  • PEP 380 - Sintaksė perduoti subgeneratoriui (Ewing) [v3.3, 2009-02-13]
  • PEP 342 - Coroutines per patobulintus generatorius (GvR, Eby) [v2.5, 2005-05-10]
243
30 сент. Atsakymą pateikė Praveen Gollakota 30 sep . 2014-09-30 00:22 '14 ne 0:22 2014-09-30 00:22

Kokios yra situacijos, kai „pelningumas“ yra naudingas?

Bet kokia situacija, kai turite tokį ciklą:

 for x in subgenerator: yield x 

Kaip apibūdina PEP, tai yra gana naivus bandymas naudoti subgeneratorių, jis praleidžia keletą aspektų, ypač teisingą mechanizmų .throw() / .send() / .close() įrašė PEP 342 . Norėdami tai padaryti, jums reikia gana sudėtingo kodo.

Kas yra klasikinis naudojimo atvejis?

Atkreipkite dėmesį, kad norite išgauti informaciją iš rekursinio duomenų struktūros. Sakykime, kad mes norime gauti visus medžio lapų mazgus:

 def traverse_tree(node): if not node.children: yield node for child in node.children: yield from traverse_tree(child) 

Dar svarbiau, kad prieš yield from nebuvo paprasto metodo generatoriaus kodo rekonstravimui. Tarkime, kad turite (nereikalingą) generatorių:

border=0
 def get_list_values(lst): for item in lst: yield int(item) for item in lst: yield str(item) for item in lst: yield float(item) 

Dabar nuspręsite šiuos ciklus suskirstyti į atskirus generatorius. Be yield from jo yra negraži, iki taško, kur jūs manote du kartus, ar norite tai padaryti. Su yield from tikrųjų malonu žiūrėti:

 def get_list_values(lst): for sub in [get_list_values_as_int, get_list_values_as_str, get_list_values_as_float]: yield from sub(lst) 

Kodėl jis yra lyginamas su mikrorajonais?

Manau, kad šiame skyriuje PEP sakoma, kad kiekvienas generatorius turi savo atskirą vykdymo kontekstą. Tačiau vykdymas persijungia tarp iteratoriaus generatoriaus ir skambinančiojo, naudodamas yield ir __next__() , tai yra panašus į temas, kuriose operacinė sistema kartkartėmis persijungia veikiantį srautą, taip pat vykdymo kontekstą (kamino, registrų,. ..).

Tai taip pat palyginama: tiek generatorius-iteratorius, tiek skambinančiojo pažanga vykdymo būsenoje tuo pačiu metu, jų vykdymo pakaitiniai. Pavyzdžiui, jei generatorius atlieka tam tikrą skaičiavimą, o skambintojas spausdina rezultatus, rezultatus pamatysite iškart, kai tik jie bus gauti. Tai yra lygiagretumo forma.

Ši analogija neturi nieko bendro su yield from , nors tai yra gana bendra „Python“ generatorių nuosavybė.

61
14 марта '12 в 22:48 2012-03-14 22:48 atsakymą pateikė Niklas B. Kovo 14 d., 12 val. 10:48 2012-03-14 22:48

Tais atvejais, kai skambinate generatoriumi iš generatoriaus, jums reikia „siurblio“, kad grąžintumėte yield vertes: for v in inner_generator: yield v . Kaip nurodo PEP, daugelis žmonių ignoruoja sudėtingas komplikacijas. Neokalinis srauto valdymas, pvz., throw() , yra vienas iš PEP pateiktų pavyzdžių. Naujasis sintaksės yield from inner_generator naudojamas visur, kur anksčiau yield from inner_generator aiškią kilpą. Tai nėra tik sintaksinis cukrus, bet: jis tvarko visus kampo atvejus, kuriuos ignoruoja „ for loop“. Būdamas „saldus“ skatina žmones jį naudoti ir taip elgtis teisingai.

Šis diskusijų skyriaus pranešimas apie šiuos sunkumus:

Su PEP 342 įdiegtomis papildomomis generatoriaus funkcijomis, tai nėra ilgesnis atvejis: kaip aprašyta Greg PEP, paprastas iteravimas nepalaiko siuntimo () ir mesti () teisingai. Gimnastika yra būtina norint palaikyti siuntimą () ir mesti () ne taip sunku, kai juos sulaužote, bet jie taip pat nėra nereikšmingi.

Negaliu kalbėti apie palyginimą su mikrofonais, išskyrus tai, kad generatoriai yra lygiagretumo tipas. Galite apsvarstyti pakabinamą generatorių kaip srautą, kuris siunčia vertes per yield vartotojų srautui. Tikrasis įgyvendinimas gali būti ne toks (ir faktinis įgyvendinimas aiškiai domina „Python“ kūrėjus), tačiau tai netaikoma vartotojams.

Nauja sintaksės yield from nesuteikia kalbai papildomų funkcijų transliacijos požiūriu, ji tiesiog supaprastina teisingą esamų funkcijų naudojimą. Tiksliau sakant, pradedantiesiems vartotojams lengviau užpildyti sudėtingą vidinį generatorių, kurį parašė ekspertas, kad galėtų pereiti per šį generatorių, netrukdydamas jo sudėtingoms funkcijoms.

23
14 марта '12 в 22:58 2012-03-14 22:58 Atsakymą Ben Jackson pateikė kovo 14 d. 12 val. 10:58 2012-03-14 22:58

Trumpas pavyzdys padės jums suprasti vieną iš variantų, kaip naudoti yield from : gauti vertę iš kito generatoriaus

 def flatten(sequence): """flatten a multi level list or something >>> list(flatten([1, [2], 3])) [1, 2, 3] >>> list(flatten([1, [2], [3, [4]]])) [1, 2, 3, 4] """ for element in sequence: if hasattr(element, '__iter__'): yield from flatten(element) else: yield element print(list(flatten([1, [2], [3, [4]]]))) 
5
27 дек. Atsakymas pateikiamas ospider . 2016-12-27 18:47 '17 at 18:47 2016-12-27 18:47

yield from daugiausia atlieka grandinės iteratorius:

 # chain from itertools: def chain(*iters): for it in iters: for item in it: yield item # with the new keyword def chain(*iters): for it in iters: yield from it 

Kaip matote, jis pašalina vieną švarų „Python“ kilpą. Tai beveik viskas, ką jis daro, tačiau iteratoriai yra gana dažnas pavyzdys Pythone.

Srautai iš esmės yra funkcija, leidžianti peršokti iš funkcijų visiškai atsitiktiniais taškais ir grįžti į kitos funkcijos būseną. Siūlų vadybininkas tai daro labai dažnai, todėl programa, atrodo, pradeda visas šias funkcijas vienu metu. Problema yra ta, kad taškai yra atsitiktiniai, taigi jums reikia naudoti užraktą, kad prižiūrėtojas nesustotų funkcijos problemos taške.

Generatoriai yra labai panašūs į srautus šia prasme: jie leidžia nurodyti konkrečius taškus (kai jie yield ), kur galite patekti ir išeiti. Naudojant šį metodą, generatoriai vadinami coroutine.

Jei norite gauti daugiau informacijos, skaitykite šią puikią „Python“ pamokos pamoką.

2
14 марта '12 в 23:02 2012-03-14 23:02 Atsakymą pateikė Jochen Ritzel kovo 12 d., 23:02 2012-03-14 23:02

Kiti klausimai apie „ žymes arba Užduoti klausimą