Un python fermecător care utilizează funcții combinatoriale în modulul itertools

Acest conținut face parte din seria: Charming Python

Aveți grijă de articole noi din această serie.

Explicarea noului concept

Conceptul de iteratori a fost introdus în Python versiunea 2.2. Deși acest lucru nu este în întregime adevărat; sugestiile despre această idee au fost deja prezente în funcția anterioară xrange () și în metoda fișierului xreadlines (). Python 2.2 a generalizat acest concept în majoritatea implementărilor sale interne și a simplificat foarte mult programarea iteratorilor definiți de utilizator introducând cuvântul cheie de randament (apariția randamentului transformă funcția într-un generator, care la rândul său returnează un iterator).

Atrativitatea iteratorilor este explicată prin două motive. Lucrul cu datele ca secvențe este adesea cea mai simplă abordare, iar o secvență care este prelucrată în ordine liniară, de fapt, de multe ori nu trebuie să existe în același timp.

Avertismentele x * () oferă exemple evidente ale acestor principii. Dacă doriți să faceți ceva de un miliard de ori, programul dvs. va fi probabil executat pentru ce oră, însă, în general, nu trebuie să aveți nevoie de o mulțime de memorie pentru el. În mod similar, pentru multe tipuri de fișiere, prelucrarea poate fi efectuată liniar și nu este nevoie să se memoreze întregul fișier în memorie. Tot felul de alte secvențe poate fi cel mai bine amânat la calcul; ei s-ar putea baza pe datele care au apărut sub formă de incremente pe canal sau pe calculele efectuate pas cu pas.

De cele mai multe ori, iteratorul este folosit în buclă pentru ca exact secvența. Iteratoarele oferă o metodă .next () () care poate fi rulată în mod explicit, dar 99% din timp ce vedeți este ceva de genul:

Această buclă se termină atunci când un apel din spatele scenei către iterator.next () ridică excepția StopIteration. Apropo, adevărata secvență poate fi transformată într-un iterator prin apelarea iter (seq) - nu salvează deloc memoria, dar poate fi utilă în funcțiile discutate mai jos.

Python progresiv divizat personalitate

În ceea ce privește Python, există ceva schizofrenic cu privire la programarea funcțională. Pe de o parte, mulți dezvoltatori Python subestimează funcțiile tradiționale ale programării funcționale: map (). filtru (). și reduce () - de obicei, recomandându-vă să utilizați comprehensiunea listei. Dar întregul modul itertools este compus din funcții de același fel și pur și simplu operează pe "secvențe amânate" (iteratori), mai degrabă decât pe secvențe complete (liste, tupluri). Mai mult, în Python 2.3 nu există nicio sintaxă pentru "comprehensiunea iterator" care pare să aibă aceleași motive ca includerea listelor.

Cred că Python va dezvolta în cele din urmă o formă de includeri iteratoare, dar depinde de găsirea sintaxei naturale adecvate pentru ei. Între timp, avem un număr de funcții combinatoriale convenabile în modulul itertools. În general, fiecare dintre aceste funcții necesită anumiți parametri (de obicei includeți iteratori de bază) și returnează un nou iterator. De exemplu, funcțiile ifilter (). imap () și izip () sunt pe deplin echivalente cu funcțiile încorporate corespunzătoare care nu au un i inițial.

Lipsesc echivalente

În ireduce () nu există itertools. deși acest lucru ar putea părea natural; posibila realizare Python este după cum urmează:

Listing 1. Exemplu de implementare ireduce ()

Cazul de utilizare a ireduce () este similar cu varianta cu reduce () (). De exemplu, să presupunem că doriți să rezumați o listă de numere care sunt într-un fișier mare, dar să se oprească atunci când condiția este îndeplinită. Ați putea controla totalul curent cu:

Listarea 2. Adăugarea unei liste de numere și rezumare

Un exemplu mai realist este, probabil, ceva de genul aplicării unui flux de evenimente unui obiect care menține o stare internă, cum ar fi un control GUI. Dar chiar și exemplul simplu de mai sus arată combinatorii iterativi în stilul programării funcționale.

Uzine de iteratori de bază

Toate funcțiile din modulul itertools pot fi ușor implementate în Python pur ca generatoare. Scopul principal al acestui modul în Python 2.3+ este de a oferi comportamente și nume standard pentru câteva funcții utile. Deși programatorii puteau să-și scrie propria versiune, practic toți ar crea variante ușor incompatibile. În plus, un alt motiv este implementarea eficientă a combinatorilor iterativi în C. Utilizarea funcțiilor itertools va fi semnificativ mai rapidă decât scrierea combinatorilor proprii. Documentația standard prezintă implementări echivalente în Python pur pentru fiecare funcție itertools. astfel încât nu este nevoie să le repetați în acest articol.

Funcțiile din modulul itertools sunt atât de elementare - și destul de bine numite - încât este probabil logic să importați toate numele din acest modul. Funcția enumerate (). de exemplu, ar putea exista bine în itertools. dar este o funcție încorporată în Python 2.3+. În special, puteți exprima cu ușurință enumerate () din punct de vedere al funcțiilor itertools:

Să ne uităm la mai multe funcții ale itertoolurilor. care nu folosesc alte iteratori ca bază, ci doar să creeze iteratori de la zero. times () returnează un iterator care returnează un obiect identic de mai multe ori; caracteristica însăși este destul de la îndemână, dar este foarte bine pentru utilizarea redundantă a lui xrange () și a unei variabile index pentru o simplă repetare a unei acțiuni. Aceasta este, în loc de:

Acum puteți folosi mai neutru:

Dacă al doilea argument nu este transmis în momente (). pur și simplu re-emite Nimeni. Funcția repeat () este similară cu timpul (). dar returnează nelimitat același obiect. Acest iterator este util sau dacă bucla are o condiție de pauză independentă. sau în combinatori, cum ar fi izip () și imap ().

Funcția count () este similară cu o cruce între repeat () și xrange (). count () returnează numere întregi consecutive (începând cu un argument opțional). Cu toate acestea, având în vedere faptul că count () nu suportă corect conversia automată la lungă la depășire, puteți utiliza în continuare xrange (n, sys.maxint) cu succes egal, nu este literalmente nelimitat, dar pentru majoritatea obiective duce la același. Ca și repetare (). count () este utilă în special în cazul altor combinații de iteratori.

Funcțiile combinatoriale

Mai multe funcții combinatoriale reale în itertooluri au fost deja menționate ocazional. ifilter (). izip () și imap () se comportă exact cum v-ați aștepta de la funcțiile corespunzătoare deasupra secvențelor. ifilterfalse () este o funcție auxiliară, deci nu este nevoie să inversați funcția predicatelor la lambda și def (și acest lucru economisește o mulțime de cheltuieli generale la apelarea funcției). Dar, din punct de vedere funcțional, ați putea defini ifilterfalse () (aproximativ, ignorând predicatul Nimic):

Funcțiile dropping () și takewhile () partajează un iterator cu un predicat. Primul ignoră elementele returnate până când predicatul este executat, iar cel de-al doilea se întoarce, în timp ce predicatul este executat. () omite un număr nedefinit de elemente iteratoare inițiale astfel încât să poată începe iterațiile numai după o întârziere. takewhile () este pornit imediat, dar iteratorul se termină dacă predicatul transmis devine adevărat.

Funcția islice () () este în esență doar o versiune iterativă a felie a listei. Puteți specifica startul, oprirea și trecerea, ca și în cazul secțiunilor obișnuite. Dacă este specificat un început, un număr de elemente sunt aruncate până când iteratorul pe care îl trece ajunge la elementul necesar. Acesta este un alt caz în care, după cum cred eu, este posibil să se îmbunătățească Python - cel mai bun lucru pentru iteratori ar fi să recunoască pur și simplu felii, așa cum fac listele (ca sinonim pentru ceea ce face doeslice).

Ultima funcție a starmap () este o variație ușoară a lui imap () (). Dacă o funcție care este transmisă ca argument ia un set de argumente, iteratorul trecut trebuie să emită tuplurile de mărime corespunzătoare. În esență, aceasta este aceeași cu imap () cu mai multe argumente iterate, doar cu un set de argumente iterate precombined cu izip ().

Mai mult decât elementele de bază

Funcțiile incluse în itertools sunt un angajament al unui bun început. Cel puțin, ei încurajează programatorii din Python să folosească și să combine iteratorii. În general, folosirea extensivă a iteratorilor este cu siguranță importantă pentru viitorul Python. Dar, pe lângă cele deja incluse, există și alte funcții pe care le recomand pentru edițiile viitoare ale acestui modul. Ele pot fi folosite imediat - desigur, dacă sunt incluse, unele nume și interfețe pot fi diferite.

pentru argumentele iterate obișnuite, ați putea folosi:

Realizarea lui Python este după cum urmează:

Listarea 3. Exemplu lanț de implementare ()

De asemenea, puteți combina iteratorii, intercalându-i. Sintaxa încorporată de a face același lucru cu șiruri de caractere lipsește, dar weave () însăși funcționează excelent pentru secvențe finite. O implementare posibilă este prezentată mai jos (Magnus Lie Hetland citează o funcție similară pe comp.lang.python):

Listing 4. Exemplu de implementare a țesei ()

Permiteți-mi să ilustrez comportamentul țesăturii (). pentru că nu poate fi evidentă imediat din această realizare:

Chiar și după ce unele iteratoare sunt epuizate, cele rămase continuă să producă valori, până când totul este disponibil.

Voi sugera o altă funcție posibilă a itertoolurilor. Abordarea problemei este în mare parte împrumutată din programarea funcțională. icompose () are o anumită simetrie cu ireduce (). Cu toate acestea, dacă ireduce () trece o secvență de valori la funcție (amânată) și produce fiecare rezultat, icompose () aplică o secvență de funcții la valoarea returnată a fiecărei funcții precedente. Utilizarea probabilă a ireduce () este de a transmite o secvență de evenimente unui obiect de lungă durată. icompose () poate în schimb să treacă succesiv un obiect la funcții mutator, fiecare returnând un obiect nou. Dacă prima este o metodă destul de tradițională pentru programarea orientată pe obiecte pentru a reprezenta evenimentele, aceasta din urmă este mai tipică pentru abordările programării funcționale.

Mai jos este posibila implementare a icompose ():

Listing 5. Exemplu de implementare a icompose ():

concluzie

Iteratoarele, reprezentate ca secvențe în așteptare, sunt un concept puternic care deschide noi stiluri de programare Python. Deși există o diferență subtilă între reprezentarea iteratorului ca sursă de date și prezentarea sa într-o manieră secvențială. Nici o modalitate de reprezentare nu este mai corectă decât cealaltă, dar cea de-a doua deschide calea spre reducerea combinatorică pentru manipularea evenimentelor programate. Funcțiile combinatoriale în itertool (și mai ales pe unele pe care le poate dezvolta, cum ar fi cele pe care le propun) se potrivesc îndeaproape stilului declarativ de programare. În opinia mea, aceste stiluri declarative sunt mai puțin predispuse la erori și mai puternice.

Descărcați resurse

Subiecte conexe