Programare funcțională în Python. Introducere în programarea funcțională în Python

  • Traducere

Când vorbesc despre programare funcțională, oamenii încep adesea să arunce o grămadă de caracteristici „funcționale”. Date imuabile, funcții de primă clasă și optimizare a recursiunii de coadă. Acestea sunt proprietăți ale limbajului care vă ajută să scrieți programe functionale. Ei menționează funcții de cartografiere, curry și utilizarea de ordin superior. Acestea sunt tehnici de programare utilizate pentru a scrie cod funcțional. Ei menționează paralelizarea, evaluarea leneșă și determinismul. Acestea sunt beneficiile programelor funcționale.

Punctează-l. Codul funcțional are o singură proprietate: absența efectelor secundare. Nu se bazează pe date din afara funcției curente și nu modifică datele din afara funcției. Toate celelalte „proprietăți” pot fi deduse din aceasta.

Funcție nefuncțională:

A = 0 def increment1(): global a a += 1

Caracteristica funcțională:

Def increment2(a): returnează a + 1

În loc să iterați printr-o listă, utilizați harta și reduceți

Hartă

Acceptă o funcție și un set de date. Creează o nouă colecție, execută funcția pe fiecare poziție de date și adaugă valoarea returnată la noua colecție. Returnează o nouă colecție.

O hartă simplă care ia o listă de nume și returnează o listă de lungimi:

Name_lengths = hartă(len, ["Masha", "Petya", "Vasya")]) print name_lengths # =>

Această hartă pătrată fiecare element:

Squares = map(lambda x: x * x, ) print squares # =>

Nu acceptă o funcție numită, dar ia una anonimă definită prin lambda. Parametrii lambda sunt definiți în stânga colonului. Corpul funcției este în dreapta. Rezultatul este returnat implicit.

Codul nefuncțional din exemplul următor preia o listă de nume și le înlocuiește cu porecle aleatorii.

Importă nume aleatorii = ["Masha", "Petya", "Vasya"] code_names = ["Shpuntik", "Vintik", "Funtik"] pentru i în interval(len(nume)): names[i] = aleatoriu. choice(code_names) print names # => ["Shpuntik", "Vintik", "Shpuntik"]

Algoritmul poate atribui aceleași porecle diferiților agenți secreti. Să sperăm că acest lucru nu va cauza probleme în timpul misiunii secrete.

Să rescriem asta folosind harta:

Importă nume aleatorii = ["Masha", "Petya", "Vasya"] secret_names = map(lambda x: random.choice(["Shpuntik", "Vintik", "Funtik"), nume)

Exercitiul 1. Încercați să rescrieți următorul cod folosind harta. Este nevoie de o listă de nume reale și le înlocuiește cu porecle folosind o metodă mai fiabilă.

Nume = ["Masha", "Petya", "Vasya"] pentru i în interval(len(nume)): nume[i] = hash(nume[i]) nume tipărite # =>

Decizia mea:

nume = ["Masha", "Petya", "Vasya"] secret_names = hartă(hash, nume)

Reduce

Reducere are o funcție și un set de elemente. Returnează valoarea obținută prin combinarea tuturor elementelor.

Un exemplu de reducere simplă. Returnează suma tuturor elementelor dintr-un set:

Suma = reduce(lambda a, x: a + x, ) print suma # => 10

X este elementul curent și este bateria. Aceasta este valoarea care este returnată prin executarea lambda la punctul anterior. reduce() iterează prin toate valorile și rulează un lambda pentru fiecare pe valorile curente a și x și returnează rezultatul în a pentru următoarea iterație.

Care este valoarea lui a în prima iterație? Este egal cu primul element al colecției, iar reduce() începe să lucreze de la al doilea element. Adică, primul x va fi egal cu al doilea element al setului.

Următorul exemplu numără cât de des apare cuvântul „căpitan” într-o listă de șiruri de caractere:

Propoziții = [„Capitan Jack Sparrow”, „căpitan de mare”, „barca ta este gata, căpitane”] cap_count = 0 pentru propoziție în propoziții: cap_count += sentence.count(„căpitan”) print cap_count # => 3

Același cod folosind reduce:

Propoziții = ["căpitan Jack Sparrow", "căpitan de mare", "barca ta este gata, căpitane"] cap_count = reduce(lambda a, x: a + x.count("căpitan"), fraze, 0)

De unde vine valoarea inițială a de aici? Nu se poate calcula din numărul de repetări din prima linie. Prin urmare, este dat ca al treilea argument al funcției reduce().

De ce harta și reducerea sunt mai bune?

În primul rând, de obicei se potrivesc pe o singură linie.

În al doilea rând, părțile importante ale iterației - colecția, operația și valoarea returnată - sunt întotdeauna în același loc, mapează și reduc.

În al treilea rând, codul într-o buclă poate modifica valoarea variabilelor definite anterior sau poate afecta codul după acesta. Prin convenție, map și reduce sunt funcționale.

În al patrulea rând, maparea și reducerea sunt operații elementare. În loc să citească bucle linie cu linie, este mai ușor pentru cititor să perceapă harta și să reducă încorporat în algoritmi complecși.

În al cincilea rând, au mulți prieteni care permit un comportament util, ușor modificat al acestor funcții. De exemplu, filtrați, toate, orice și găsiți.

Exercițiul 2: Rescrie următorul cod folosind harta, reducerea și filtrarea. Filtrul acceptă o funcție și o colecție. Returnează o colecție de acele lucruri pentru care funcția returnează True.

Oameni = [(„nume”: „Masha”, „înălțime”: 160), (“înălțime”: „Sasha”, „înălțime”: 80), (“nume”: „Pașa”)] height_total = 0 height_count = 0 pentru persoană în persoane: dacă „înălțime” în persoană: înălțime_total += persoană[„înălțime”] număr_înălțime += 1 dacă număr_înălțime > 0: înălțime_medie = înălțime_totală / număr_înălțime imprimați înălțime_medie # => 120

Decizia mea:

oameni = [("nume": "Masha", "înălțime": 160), ("înălțime": "Sasha", "înălțime": 80), ("nume": "Pașa")] înălțimi = hartă(lambda x: x[„înălțime”], filter(lambda x: „înălțime” în x, oameni)) dacă len(înălțimi) > 0: din importul operatorului adăugați înălțime_medie = reduce(adăugați, înălțimi) / len(înălțimi)

Scrieți declarativ, nu imperativ.

Următorul program emulează o cursă cu trei mașini. În fiecare moment, mașina fie merge înainte, fie nu. De fiecare dată programul afișează distanța parcursă de mașini. După cinci intervale de timp, cursa se încheie.

Exemple de ieșire:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Textul programului:

De la import aleatoriu timp aleatoriu = 5 poziții_mașină = în timp: # scade timpul de timp -= 1 tipărire "" pentru i în interval(len(poziții_mașină)): # mutați mașina dacă random() > 0.3: pozițiile_mașinii[i] += 1 # desenați imprimarea mașinii „-” * car_positions[i]

Codul este imperativ. Versiunea funcțională ar fi declarativă - ar descrie ceea ce trebuie făcut, nu cum ar trebui făcut.

Utilizarea funcțiilor

Declarativitatea poate fi realizată prin inserarea codului în funcții:

Din import aleator aleator def move_cars(): pentru i, _ in enumerate(car_positions): if random() > 0.3: car_positions[i] += 1 def draw_car(car_position): print "-" * car_position def run_step_of_race(): oră globală timp -= 1 move_cars() def draw(): tipăriți „" pentru poziția mașinii în pozițiile_mașinii: draw_car(poziție_mașină) timp = 5 poziții_mașini = în timp ce timpul: run_step_of_race() draw()

Pentru a înțelege programul, cititorul se uită la bucla principală. „Dacă mai rămâne timp, vom trece printr-o etapă a cursei și vom afișa rezultatul. Să verificăm din nou ora.” Dacă cititorul trebuie să înțeleagă cum funcționează pasul cursei, poate citi codul separat.

Nu sunt necesare comentarii, se explică codul.

Împărțirea codului în funcții face codul mai ușor de citit. Această tehnică folosește funcții, dar numai ca subrutine. Ei împachetează codul, dar nu îl fac funcțional. Funcțiile afectează codul din jurul lor și modifică variabilele globale, mai degrabă decât să returneze valori. Dacă cititorul întâlnește o variabilă, va trebui să găsească de unde provine.

Iată versiunea funcțională a acestui program:

Din import aleator aleator def move_cars(car_positions): return map(lambda x: x + 1 if random() > 0.3 else x, car_positions) def output_car(car_position): return "-" * car_position def run_step_of_race(state): return ( "time": state["time"] - 1, "car_positions": move_cars(state["car_positions"])) def draw(state): print "" print "\n".join(map(output_car, state[) "poziții_mașinii"])) def cursă(state): draw(state) if stat["time"]: cursă(run_step_of_race(state)) race(("time": 5, "car_positions": ))

Codul este acum împărțit în funcții funcționale. Există trei semne ale acestui lucru. Prima este că nu există variabile partajate. time și car_positions sunt transmise direct la race(). În al doilea rând, funcțiile preiau parametri. În al treilea rând, variabilele nu se modifică în interiorul funcțiilor; De fiecare dată când run_step_of_race() efectuează următorul pas, acesta este trecut înapoi la următorul.

Iată două funcții zero() și unu():

Def zero(i): dacă s == "0": returnează s def one(i): dacă s == "1": returnează s

Zero() preia șirul s. Dacă primul caracter este 0, atunci returnează restul șirului. Dacă nu, atunci Niciunul. one() face același lucru dacă primul caracter este 1.

Să ne imaginăm funcția rule_sequence(). Este nevoie de un șir și o listă de funcții de regulă, constând din funcțiile zero și unu. Apelează prima regulă, trecându-i un șir. Dacă se returnează None, atunci ia valoarea returnată și apelează următoarea regulă. Și așa mai departe. Dacă se returnează None, rule_sequence() se oprește și returnează None. În caz contrar – sensul ultimei reguli.

Exemple de date de intrare și de ieșire:

Print rule_sequence("0101", ) # => 1 tipar rule_sequence("0101", ) # => Niciuna

Versiunea imperativă a rule_sequence():

Def rule_sequence(s, rules): pentru regula din reguli: s = rule(e) if s == Niciunul: break return s

Exercițiul 3. Acest cod folosește o buclă. Rescrieți-l declarativ folosind recursiunea.

Decizia mea:

def rule_sequence(e, reguli): if s == Niciuna sau nu reguli: return s else: return rule_sequence(reguli(e), reguli)

Folosiți conducte

Acum să rescriem un alt tip de buclă folosind o tehnică numită conductă.

Următoarea buclă modifică dicționarele care conțin numele, țara de origine incorectă și statutul unor grupuri.

Benzi = [(„nume”: „sunset rubdown”, „țara”: „Marea Britanie”, „activ”: Fals), („nume”: „femei”, „țara”: „Germania”, „activ”: Fals ), ("nume": "a silver mt. zion", "țara": "Spania", "activ": Adevărat)] def format_bands(benzi): pentru trupa în trupe: band["țara"] = "Canada " band["nume"] = band["nume"].înlocuiește(".", "") band["nume"] = band["nume"].title() format_benzi(benzi) tipăriți benzi # => [(„nume”: „Sunset Rubdown”, „activ”: fals, „țară”: „Canada”), # („nume”: „Femei”, „activ”: fals, „țară”: „Canada” ) , # („nume”: „A Silver Mt Zion”, „activ”: Adevărat, „țară”: „Canada”)]

Numele funcției „format” este prea general. În general, codul provoacă unele îngrijorări. Trei lucruri diferite se întâmplă într-un singur ciclu. Valoarea cheii „țară” este schimbată în „Canada”. Punctele sunt eliminate și prima literă a numelui este schimbată în majuscule. Este greu de înțeles ce ar trebui să facă codul și greu de spus dacă o face. Este dificil de utilizat, testat și paralelizat.

Comparaţie:

Tipăriți pipeline_each(benzi, )

E simplu. Funcțiile de ajutor par funcționale, deoarece sunt legate între ele. Ieșirea celui precedent este intrarea celui următor. Sunt ușor de testat, reutilizat, validat și paralelizat.

Pipeline_each() iterează prin grupuri unul câte unul și le transmite funcțiilor de conversie precum set_canada_as_country(). După aplicarea funcției tuturor grupurilor, pipeline_each() face o listă a acestora și le transmite următorului.

Să ne uităm la funcțiile de transformare.

Def assoc(_d, cheie, valoare): din copie import deepcopy d = deepcopy(_d) d = returnare valoare d def set_canada_as_country(band): return assoc(band, "country", "Canada") def strip_punctuation_from_name(band): return asoc(banda, "nume", band["nume"].inlocuire(".", "")) def capitalize_names(banda): return asoc(banda, "nume", band["nume"].title( ))

Fiecare asociază o cheie de grup cu o nouă valoare. Acest lucru este greu de făcut fără modificarea datelor originale, așa că rezolvăm acest lucru cu asoc(). Utilizează deepcopy() pentru a crea o copie a dicționarului trecut. Fiecare funcție transformă o copie și returnează acea copie.

Totul pare să fie bine. Datele originale sunt protejate de modificări. Dar există două locuri potențiale în cod pentru modificările datelor. În strip_punctuation_from_name() un nume fără puncte este creat apelând apelând replace() cu numele original. capitalize_names() creează un nume cu prima literă majusculă pe baza titlului() și a numelui original. Dacă înlocuirea și ora nu sunt funcționale, atunci strip_punctuation_from_name() și capitalize_names() nu sunt funcționale.

Din fericire, sunt funcționale. În Python, șirurile sunt imuabile. Aceste funcții funcționează cu copii de șiruri. Uf, slavă Domnului.

Acest contrast între șiruri de caractere și dicționare (natura lor mutabilă) în Python demonstrează avantajele limbilor precum Clojure. Acolo, programatorul nu trebuie să se gândească dacă va schimba datele. Nu se va schimba.

Exercițiul 4. Încercați să creați o funcție pipeline_each. Gândiți-vă la succesiunea operațiilor. Grupurile sunt într-o matrice, trecute pe rând la prima funcție de conversie. Apoi, matricea rezultată este trecută o bucată la un moment dat la cea de-a doua funcție și așa mai departe.

Decizia mea:

def pipeline_each(data, fns): return reduce(lambda a, x: map(x, a), fns, data)

Toate cele trei funcții de transformare au ca rezultat schimbarea unui câmp specific pentru grup. call() poate fi folosit pentru a crea o abstractizare pentru aceasta. Acceptă o funcție și cheia la care va fi aplicată.

Set_canada_as_country = call(lambda x: "Canada", "country") strip_punctuation_from_name = call(lambda x: x.replace(".", ""), "name") capitalize_names = call(str.title, "name") print pipeline_each(benzi, )

Sau, sacrificând lizibilitatea:

Tipăriți pipeline_each(benzi, )

Cod pentru apel():

Def assoc(_d, cheie, valoare): din copie import deepcopy d = deepcopy(_d) d = returnare valoare d def call(fn, key): def apply_fn(record): return assoc(record, cheie, fn(record. get(cheie))) return apply_fn

Ce se petrece aici?

Unu. call este o funcție de ordin superior, deoarece ia o altă funcție ca argument și returnează funcția.

Două. apply_fn() este similar cu funcțiile de conversie. Obține intrarea (grupul). Caută înregistrarea valorii. Apeluri fn. Atribuie rezultatul unei copii a înregistrării și îl returnează.

Trei. apelul în sine nu face nimic. apply_fn() face toată treaba. În exemplul pipeline_each(), o instanță de apply_fn() setează „țara” la „Canada”. Celălalt scrie prima literă cu majuscule.

Patru. Când executați o instanță de apply_fn(), funcțiile fn și key nu vor fi disponibile în domeniu. Acestea nu sunt argumente pentru apply_fn() sau variabile locale. Dar va exista acces la ele. Când o funcție este definită, ea păstrează referințe la variabilele pe care le închide - cele care au fost definite în afara funcției și utilizate intern. Când se rulează o funcție, variabilele sunt căutate printre cele locale, apoi printre argumente și apoi printre referințele la închideri. Acolo veți găsi fn și key.

Cinci. Nu există nicio mențiune despre grupuri în apel. Acest lucru se datorează faptului că apelul poate fi folosit pentru a crea orice conducte, indiferent de conținutul acestora. Programarea funcțională, în special, construiește o bibliotecă funcții generale, potrivit pentru compoziții și reutilizare.

Bine făcut. Închideri, funcții de ordin superior și domeniul de aplicare - toate în câteva paragrafe. Puteți bea și ceai și prăjituri.

Mai rămâne o prelucrare a datelor de grup. Eliminați totul, cu excepția numelui și a țării. funcția extract_name_and_country():

Def extract_name_and_country(band): plucked_band = () plucked_band["nume"] = band["nume"] plucked_band["țara"] = band["țara"] return plucked_band print pipeline_each(benzi, ) # => [(" nume": "Sunset Rubdown", "țara": "Canada"), # ("nume": "Femei", "țara": "Canada"), # ("nume": "A Silver Mt Zion", " tara": "Canada")]

Extract_name_and_country() ar putea fi scris într-o formă generică numită pluck(). Ar fi folosit astfel:

Tipăriți pipeline_each(benzi, )])

Exercițiul 5. pluck acceptă o listă de chei de extras din înregistrări. Încearcă să-l scrii. Aceasta va fi o funcție de ordin superior.

3.2.3 Înțelegerile dicționarului

Să presupunem că avem un dicționar ale cărui chei sunt caractere și ale căror valori sunt asociate cu numărul de ori când acel caracter apare într-un text. În prezent, dicționarul face distincția între caracterele mari și mici.

Avem nevoie de un dicționar în care aparițiile caracterelor majuscule și minuscule sunt combinate:

dct = ( „a” : 10 , „b” : 34 , „A” : 7 , „Z” : 3 )

frecvență = ( k . inferior ( ) : dct . get ( k . inferior ( ) , 0 ) + dct . get ( k . superior ( ) , 0 )

pentru k în dct . chei())

frecvența de tipărire #("a": 17, "z": 3, "b": 34)

Python acceptă crearea de funcții anonime (adică funcții care nu sunt legat la un nume) în timpul execuției, folosind un construct numit „lambda”. Acesta nu este exact același cu lambda în limbajele de programare funcționale, dar este un concept foarte puternic care este bine integrat în Python și este adesea folosit împreună cu concepte funcționale tipice precum filter() , map() și reduce() .

Funcțiile anonime sub forma unei expresii pot fi create folosind lambda
afirmație:

args este o listă de argumente separate prin virgulă, iar expresia este o expresie care implică acele argumente. Această bucată de cod arată diferența dintre o definiție de funcție normală și o funcție lambda:

funcția def (x):

returnează x * x

funcția de imprimare (2) #4

#-----------------------#

functie = lambda x : x * x

funcția de imprimare (2) #4

După cum puteți vedea, ambele funcții () fac exact la fel și pot fi utilizate în aceleași moduri. Rețineți că definiția lambda nu include o instrucțiune „return” - conține întotdeauna o expresie care este returnată. De asemenea, rețineți că puteți pune o definiție lambda oriunde este așteptată o funcție și nu trebuie deloc să o alocați unei variabile.

Următoarele fragmente de cod demonstrează utilizarea funcțiilor lambda.

def increment (n) :

întoarce lambda x : x + n

imprimare increment(2) # la 0x022B9530>

increment de imprimare (2) (20) #22

Codul de mai sus definește o creștere a funcției care creează o funcție anonimă din mers și o returnează. Funcția returnată își incrementează argumentul cu valoarea care a fost specificată când a fost creată.

Acum puteți crea mai multe funcții de incrementare diferite și le puteți atribui variabilelor, apoi le puteți utiliza independent unele de altele. După cum demonstrează ultima declarație, nici măcar nu trebuie să alocați funcția nicăieri - o puteți folosi instantaneu și o uitați când nu mai este necesară.

Q3. La ce este bun lambda?
Ans.
Raspunsul este:

  • Nu avem nevoie de lambda, ne-am putea înțelege bine fără ea. Dar…
  • există anumite situații în care este convenabil - face scrierea codului puțin mai ușoară, iar codul scris puțin mai curat.

Î4. Ce fel de situații?

Ei bine, situații în care avem nevoie de o funcție simplă unică: o funcție care va fi folosită o singură dată.

În mod normal, funcțiile sunt create pentru unul din două scopuri: (a) pentru a reduce duplicarea codului sau (b) pentru a modulariza codul.

  • Dacă aplicația dvs. conține bucăți duplicate de cod în diferite locuri, atunci puteți pune o copie a acelui cod într-o funcție, puteți da un nume funcției și apoi - folosind numele funcției - o puteți apela din diferite locuri din codul dvs.
  • Dacă aveți o bucată de cod care efectuează o operație bine definită - dar este cu adevărat lungă și noduroasă și întrerupe fluxul altfel lizibil al programului dvs. - atunci puteți extrage acel cod lung și noduros și îl puteți pune într-o funcție de la sine.

Dar să presupunem că trebuie să creați o funcție care va fi folosită o singură dată - apelată de la unul singur plasați în aplicația dvs. Ei bine, în primul rând, nu trebuie să dai un nume funcției. Poate fi „anonim”. Și îl puteți defini chiar în locul în care doriți să îl utilizați. Acolo este utilă lambda.

De obicei, lambda este utilizat în contextul unei alte operațiuni, cum ar fi sortarea sau reducerea datelor:

nume = [ „David Beazley” , „Brian Jones” , „Raymond Hettinger” , „Ned Batchelder” ]

tipărit sortat (nume , cheie = nume lambda : nume . split () [ - 1 ] . inferior () )

# [„Ned Batchelder”, „David Beazley”, „Raymond Hettinger”, „Brian Jones”]

Deși lambda vă permite să definiți o funcție simplă, utilizarea acesteia este foarte restricționată. În
în special, poate fi specificată doar o singură expresie, al cărei rezultat este returnarea
valoare. Aceasta înseamnă că nu pot fi incluse alte caracteristici ale limbii, inclusiv declarații multiple, condiționale, iterații și gestionarea excepțiilor.
Puteți scrie cu bucurie mult cod Python fără a utiliza vreodată lambda. In orice caz,
o veți întâlni ocazional în programele în care cineva scrie o mulțime de minuscule
funcții care evaluează diverse expresii sau în programe care necesită furnizarea de utilizatori
funcții de apel invers.

Ați definit o funcție anonimă folosind lambda, dar trebuie și să capturați
valorile anumitor variabile la momentul definirii.

>>> x = 10

>>> a = lambda y : x + y

>>> x = 20

>>> b = lambda y : x + y

Acum pune-ți o întrebare. Care sunt valorile lui a(10) și b(10)? Dacă crezi că
rezultatele ar putea fi 20 și 30, ați greși:

Problema aici este că valoarea lui x folosită în expresia lambda este o variabilă liberă
care devine legat în timpul execuției, nu în timpul definiției. Astfel, valoarea lui x în lambda
expresii este orice valoare a variabilei x se întâmplă să fie în momentul execuției.
De exemplu:

Dacă doriți ca o funcție anonimă să capteze o valoare în punctul de definiție și
păstrați-o, includeți valoarea ca valoare implicită, astfel:

Problema abordată aici este ceva care tinde să apară în codul respectiv
încearcă să fie puțin prea deștept cu utilizarea funcțiilor lambda. De exemplu,
crearea unei liste de expresii lambda folosind o listă de înțelegere sau într-o buclă de un fel și așteptarea ca funcțiile lambda să-și amintească variabila de iterație în momentul definiției. De exemplu:

>>> funcs = [ lambda x : x + n pentru n în intervalul (5 ) ]

Există mai multe paradigme în programare, de exemplu, OOP, funcțional, imperativ, logic și multe dintre ele. Vom vorbi despre programarea funcțională.

Condițiile preliminare pentru programarea funcțională cu drepturi depline în Python sunt: ​​funcții de ordin superior, instrumente dezvoltate de procesare a listelor, recursivitate și capacitatea de a organiza calcule leneșe.

Astăzi ne vom familiariza cu elemente simple, iar modelele complexe vor fi în alte lecții.

Teorie în teorie

Ca și în cazul programării OOP și funcționale, încercăm să evităm definițiile. Totuși, este dificil să dai o definiție clară, așa că nu va exista o definiție clară aici. In orice caz! Să evidențiem dorințele pentru un limbaj funcțional:

  • Funcții de ordin superior
  • Funcții pure
  • Date imuabile

Nu este lista plina, dar chiar și asta este suficient pentru a o face „frumoasă”. Dacă cititorul dorește mai mult, iată o listă extinsă:

  • Funcții de ordin superior
  • Funcții pure
  • Date imuabile
  • Închideri
  • Lene
  • Recursie coadă
  • Tipuri de date algebrice
  • Potrivire de model

Să luăm în considerare treptat toate aceste puncte și cum să le folosim în Python.

Și astăzi, pe scurt, ce este pe prima listă.

Funcții pure

Funcțiile pure nu produc efecte secundare observabile, doar returnează un rezultat. Ele nu modifică variabilele globale, nu trimit sau imprimă nimic nicăieri, nu ating obiecte și așa mai departe. Acceptă date, calculează ceva, ținând cont doar de argumente și returnează date noi.

  • Codul este mai ușor de citit și de înțeles
  • Mai ușor de testat (nu este nevoie să creați „condiții”)
  • Mai de încredere pentru că nu depind de „vreme” și de starea mediului, ci doar de argumente
  • Poate fi rulat în paralel, rezultatele pot fi stocate în cache

Date imuabile

Structurile de date imuabile sunt colecții care nu pot fi modificate. Aproape ca numerele. Numărul doar există, nu poate fi schimbat. De asemenea, o matrice imuabilă este modul în care a fost creat și așa va fi întotdeauna. Dacă trebuie să adăugați un element, va trebui să creați o nouă matrice.

Avantajele structurilor imuabile:

  • Partajați în siguranță referința între fire
  • Ușor de testat
  • Ușor de urmărit ciclu de viață(corespunde fluxului de date)

Funcții de ordin superior

Se apelează o funcție care ia o altă funcție ca argument și/sau returnează o altă funcție funcție de ordin superior:

Def f(x): returnează x + 3 def g(funcție, x): returnează funcția(x) * function(x) print(g(f, 7))

După ce am luat în considerare teoria, să începem să trecem la practică, de la simplu la complex.

Incluziuni de liste sau generator de liste

Să ne uităm la un design de limbă care va ajuta la reducerea numărului de linii de cod. Nu este neobișnuit să determinați nivelul unui programator Python folosind acest construct.

Exemplu de cod:

Pentru x din xrange(5, 10): dacă x % 2 == 0: x =* 2 altfel: x += 1

Un ciclu cu o afecțiune ca aceasta nu este neobișnuit. Acum să încercăm să transformăm aceste 5 linii într-una singură:

>>>

Nu e rău, 5 rânduri sau 1. Mai mult, expresivitatea a crescut și un astfel de cod este mai ușor de înțeles - un comentariu poate fi adăugat pentru orice eventualitate.

ÎN vedere generala acest design este astfel:

Merită să înțelegeți că, dacă codul nu este deloc lizibil, atunci este mai bine să abandonați un astfel de design.

Funcții anonime sau lambda

Continuăm să reducem cantitatea de cod.

Def calc(x, y): returnează x**2 + y**2

Funcția este scurtă, dar au fost irosite cel puțin 2 linii. Este posibil să scurtați funcții atât de mici? Sau poate nu îl formatați ca funcții? La urma urmei, nu doriți întotdeauna să creați funcții inutile într-un modul. Și dacă funcția ocupă o linie, atunci cu atât mai mult. Prin urmare, în limbajele de programare există funcții anonime care nu au un nume.

Funcțiile anonime din Python sunt implementate folosind calculul lambda și arată ca expresii lambda:

>>> lambda x, y: x**2 + y**2 la 0x7fb6e34ce5f0>

Pentru un programator, acestea sunt aceleași funcții și puteți lucra și cu ele.

Pentru a accesa funcții anonime de mai multe ori, le atribuim unei variabile și le folosim în avantajul nostru.

>>> (lambda x, y: x**2 + y**2)(1, 4) 17 >>> >>> func = lambda x, y: x**2 + y**2 >>> funcția(1, 4) 17

Funcțiile lambda pot acționa ca un argument. Chiar și pentru alte lambda:

Multiplicator = lambda n: lambda k: n*k

Folosind lambda

Am învățat cum să creăm funcții fără nume, dar acum vom afla unde să le folosim. Biblioteca standard oferă mai multe funcții care pot lua o funcție ca argument - map(), filter(), reduce(), apply().

Hartă()

Funcția map() procesează una sau mai multe secvențe folosind funcția dată.

>>> lista1 = >>> lista2 = [-1, 1, -5, 4, 6] >>> lista(harta(lambda x, y: x*y, lista1, lista2)) [-7, 2, -15, 40, 72]

Ne-am familiarizat deja cu generatorul de liste, să-l folosim dacă lungimea listei este aceeași):

>>> [-7, 2, -15, 40, 72]

Deci, se observă că utilizarea incluziunilor de listă este mai scurtă, dar lambda sunt mai flexibile. Să mergem mai departe.

filtru()

Funcția filter() vă permite să filtrați valorile unei secvențe. Lista rezultată conține numai acele valori pentru care valoarea funcției pentru element este adevărată:

>>> numere = >>> list(filtru(lambda x: x< 5, numbers)) # В результат попадают только те элементы x, для которых x < 5 истинно

Același lucru cu expresiile din listă:

>>> numere => >>>

reduce()

Pentru a organiza calculele în lanț într-o listă, puteți utiliza funcția reduce(). De exemplu, produsul elementelor unei liste poate fi calculat astfel (Python 2):

>>> numere = >>> reduce(lambda res, x: res*x, numere, 1) 720

Calculele au loc în următoarea comandă:

((((1*2)*3)*4)*5)*6

Lanțul de apeluri este legat folosind un rezultat intermediar (res). Dacă lista este goală, al treilea parametru este pur și simplu utilizat (în cazul unui produs de factori zero, acesta este 1):

>>> reduce(lambda res, x: res*x, , 1) 1

Desigur, rezultatul intermediar nu este neapărat un număr. Acesta poate fi orice alt tip de date, inclusiv o listă. Următorul exemplu arată reversul unei liste:

>>> reduce(lambda res, x: [x]+res, , )

Python are funcții încorporate pentru cele mai comune operațiuni:

>>> numere = >>> sumă(numere) 15 >>> listă(inversată(numere))

Python 3 nu are o funcție reduce() încorporată, dar poate fi găsită în modulul functools.

aplica()

O funcție pentru a aplica o altă funcție argumentelor poziționale și numite, având o listă și, respectiv, un dicționar (Python 2):

>>> def f(x, y, z, a=Niciuna, b=Niciuna): ... print x, y, z, a, b ... >>> aplica(f, , ("a": 4, „b”: 5)) 1 2 3 4 5

În Python 3, ar trebui să utilizați o sintaxă specială în loc de funcția apply():

>>> def f(x, y, z, a=Niciuna, b=Niciuna): ... print(x, y, z, a, b) ... >>> f(*, **(" a": 4, "b": 5)) 1 2 3 4 5

Să încheiem recenzia cu această funcție încorporată. bibliotecă standardși să trecem la ultima abordare funcțională pentru astăzi.

Închideri

Funcțiile definite în cadrul altor funcții sunt închideri. De ce este necesar acest lucru? Să ne uităm la un exemplu pentru a explica:

Cod (fictiv):

Def processing(element, type_filter, all_data_size): filters = Filter(all_data_size, type_filter).get_all() for filt in filters: element = filt.filter(element) def main(): data = DataStorage().get_all_data() for x în date: procesare(x, „toate”, len(date))

Ce puteți observa în cod: în acest cod există variabile care în esență trăiesc permanent (adică identice), dar în același timp încărcăm sau inițializam de mai multe ori. Ca rezultat, ajungem la înțelegerea că inițializarea variabilelor ocupă cea mai mare parte a timpului în acest proces, se întâmplă că chiar și încărcarea variabilelor în domeniu reduce performanța. Pentru a reduce închiderile de utilizare deasupra capului.

Închiderea inițializează variabilele o dată, care pot fi apoi utilizate fără suprasarcină.

Să învățăm cum să creăm închideri:

Def multiplicator(n): „multiplicator(n) returnează o funcție care se înmulțește cu n” def mul(k): return n*k return mul # același efect poate fi obținut cu # multiplicator = lambda n: lambda k: n* k mul2 = multiplicator(2) # mul2 - funcție care se înmulțește cu 2, de exemplu, mul2(5) == 10

Concluzie

În lecția la care ne-am uitat Noțiuni de bază FP și, de asemenea, a întocmit o listă de mecanisme care vor fi discutate în lecțiile următoare. Am vorbit despre modalități de a reduce cantitatea de cod, cum ar fi incluziunile de liste (generator de liste), funcțiile lamda și utilizarea lor și, în sfârșit, au fost câteva cuvinte despre închideri și pentru ce sunt acestea.

Nu degeaba limbajul Python este popular printre programatorii Google și editorii Hacker în același timp :). Acest limbaj cu adevărat puternic vă permite să scrieți cod urmând mai multe paradigme, iar astăzi vom încerca să ne dăm seama care este diferența dintre ele și care este mai bine să o urmați.

Ce paradigme?! Hai să codificăm!

Când trebuie să scrieți ceva, ultimul lucru de care vă faceți griji probabil este ce paradigmă de programare să alegeți. Mai degrabă, fie alegeți limba cea mai potrivită, fie începeți imediat să codați în preferatul, preferat și dovedit de-a lungul anilor. E adevărat, lasă ideologii să se gândească la ideologie, treaba noastră este să programăm :). Și totuși, când programezi, urmezi neapărat un fel de paradigmă. Să ne uităm la un exemplu. Să încercăm să scriem ceva simplu... ei bine, de exemplu, să calculăm aria unui cerc.

O poti scrie asa:

Zona unui cerc (opțiunea unu)

zona_dubla_a_cercului(r dublu) (
returnează M_PI*pow(r,2);
}
int main() (
dublu r = 5;
cout<< "Площадь: "<< area_of_circle(r)<< endl;
}

Sau poți face asta:

Aria unui cerc (opțiunea a doua)

Cercul clasei (
r dublu;
public:
Cerc(r dublu) (acest->r = r; )
zonă dublă() ( returnează M_PI*pow(this->r,2); )
void print_area() (
cout<< "Площадь: "<< this->zonă()<< endl;
}
};
int main() ((new Circle(5))->print_area();)

Se poate face altfel... dar indiferent cât de mult ai încerca, codul va fi fie imperativ (ca în primul caz), fie orientat pe obiecte (ca în al doilea).
Acest lucru nu se datorează lipsei de imaginație, ci pur și simplu pentru că C++ este adaptat acestor paradigme.

Și cel mai bun (sau cel mai rău, în funcție de dreptatea mâinilor tale) pe care îl poți face cu el este să amesteci mai multe paradigme.

Paradigme

După cum probabil ați ghicit, puteți scrie în aceeași limbă urmând mai multe paradigme, uneori chiar mai multe deodată. Să ne uităm la reprezentanții lor principali, pentru că fără aceste cunoștințe nu te vei putea considera niciodată un programator profesionist și, cel mai probabil, va trebui să uiți de lucrul în echipă.

Programare imperativă

„Mai întâi facem asta, apoi asta, apoi asta”

Limbi: aproape toate

O paradigmă absolut pe înțelesul oricărui programator: „O persoană dă un set de instrucțiuni unei mașini.”
Toată lumea începe să învețe/înțeleagă programarea din paradigma imperativă.

Programare functionala

„Calculăm expresia și folosim rezultatul pentru altceva.”

Limbi: Haskell, Erlang, F#

O paradigmă absolut de neînțeles pentru un programator începător. Descriem nu o succesiune de stări (ca în paradigma imperativă), ci o secvență de acțiuni.

Programare orientată pe obiecte

„Facem schimb de mesaje între obiecte, simulând interacțiunile din lumea reală.”

Limbi: aproape toate

Odată cu apariția sa, paradigma orientată pe obiecte a intrat ferm în viața noastră.
Aproape toate procesele de afaceri moderne sunt construite pe OOP.

Programare logica

„Răspundem la întrebare găsind o soluție.”

Limbi: Prolog

Programarea logică este un lucru destul de specific, dar în același timp, interesant și intuitiv.
Un exemplu simplu va fi suficient:

(stabiliți regulile)
vrăjitoare (X)<= burns(X) and female(X).
arsuri (X)<= wooden(X).
lemn (X)<= floats(X).
plutește (X)<= sameweight(duck, X).
(setati observatii)
femeie(fată).
aceeași greutate (răță, fată).
(pune o intrebare)
? vrăjitoare (fată).

În timp ce fiecare programator este, prin definiție, familiarizat cu programarea imperativă și orientată pe obiecte, rareori întâlnim programarea funcțională în forma sa pură.

Programarea funcțională este în contrast cu programarea imperativă.

Programarea imperativă implică o succesiune de modificări ale stării unui program, iar variabilele sunt folosite pentru a stoca această stare.

Programarea funcțională, dimpotrivă, presupune o succesiune de acțiuni asupra datelor. Acest lucru este asemănător cu matematica - scriem formula f(x) pe tablă pentru o lungă perioadă de timp, apoi înlocuim x și obținem rezultatul.

Și scopul programării funcționale este că aici formula este un instrument pe care îl aplicăm lui X.

Piton cu două fețe

Nu există o teorie mai bună decât practica, așa că haideți să scriem deja ceva. Mai bine, scrie-l în Python :).
Să calculăm suma pătratelor elementelor matricei „date” în mod imperativ și funcțional:

Imperativ Python

date = [...]
suma = 0
pentru elementul din a:
suma += element ** 2
tipăriți suma

Python funcțional

date = [...]
sq = lambda x: x**2
suma = lambda x,y: x+y
print reduce (sumă, hartă (mp, date))

Ambele exemple sunt în Python, deși nu l-am inclus în lista de limbaje funcționale. Acesta nu este un accident, deoarece un limbaj complet funcțional este un lucru destul de specific și rar folosit. Primul limbaj funcțional a fost Lisp, dar nici măcar nu era complet funcțional (derutant, nu-i așa?). Limbile complet funcționale sunt folosite pentru tot felul de aplicații științifice și nu sunt încă utilizate pe scară largă.

Dar dacă „funcționalitățile” în sine nu s-au răspândit, anumite idei au migrat din ele către limbaje de programare (și nu numai).
S-a dovedit că nu este absolut necesar să scrieți cod complet funcțional, este suficient să decorați codul imperativ cu elemente de cod funcțional.

Python în acțiune

Se pare că conceptele de FP sunt implementate în Python mai mult decât elegant. Să le aruncăm o privire mai atentă.

?-calcul

Calculul lambda este un concept matematic care implică faptul că funcțiile pot lua drept argumente și pot returna alte funcții.
Astfel de funcții sunt numite funcții de ordin superior. ?-calcul se bazează pe două operații: aplicare și abstractizare.
Am dat deja un exemplu de aplicație în lista anterioară. Funcțiile map și reducere sunt aceleași funcții de ordin superior care „se aplică”, sau se aplică, funcția transmisă ca argument fiecărui element din listă (pentru hartă) sau fiecărei perechi consecutive de elemente din listă (pentru reducere).

În ceea ce privește abstractizarea, este invers: funcțiile creează funcții noi pe baza argumentelor lor.

Abstracția lambda

def add(n):
întoarce lambda x: x + n

adaugă =

Aici am creat o listă de funcții, fiecare dintre ele adaugă un anumit număr la argument.
Acest mic exemplu conține, de asemenea, câteva definiții mai interesante ale programării funcționale - închidere și curry.

O închidere este definiția unei funcții care depinde de starea internă a unei alte funcții. În exemplul nostru, acesta este lambda x. Cu această tehnică, facem ceva similar cu utilizarea variabilelor globale, doar la nivel local.

Purtarea este transformarea unei funcții care ia o pereche de argumente într-o funcție care își ia argumentele pe rând. Aceasta este ceea ce am făcut în exemplu, doar că am ajuns cu o serie de astfel de funcții.

Astfel putem scrie cod care funcționează nu numai cu variabile, ci și cu funcții, ceea ce ne oferă câteva „grade de libertate” în plus.

Funcții pure și un compilator leneș

Funcțiile imperative pot schimba variabile externe (globale), ceea ce înseamnă că o funcție poate returna valori diferite pentru aceleași valori de argument în diferite etape ale execuției programului.

Această afirmație nu este deloc potrivită pentru paradigma funcțională. Aici, funcțiile sunt considerate matematice, depind doar de argumente și alte funcții, motiv pentru care sunt supranumite „funcții pure”.

După cum am aflat deja, în paradigma funcțională puteți gestiona funcțiile după cum doriți. Dar obținem cel mai mare beneficiu atunci când scriem „funcții pure”. O funcție pură este o funcție fără efecte secundare, ceea ce înseamnă că nu depinde de mediul său și nu își schimbă starea.

Utilizarea funcțiilor pure ne oferă o serie de avantaje:

  • În primul rând, dacă funcțiile nu depind de variabilele de mediu, atunci reducem numărul de erori asociate cu valorile nedorite ale acestor variabile. Odată cu numărul de erori, reducem și timpul necesar pentru depanarea programului și este mult mai ușor să depanați astfel de funcții.
  • În al doilea rând, dacă funcțiile sunt independente, atunci compilatorul are spațiu de roaming. Dacă o funcție depinde doar de argumente, atunci poate fi evaluată o singură dată. Data viitoare puteți utiliza valoarea din cache. De asemenea, dacă funcțiile nu depind unele de altele, acestea pot fi schimbate și chiar paralelizate automat.

Pentru a crește performanța, FP folosește și evaluarea leneșă. Un exemplu izbitor:

lungime de imprimare()

În teorie, ar trebui să obținem o eroare de împărțire cu zero la ieșire. Dar un compilator Python leneș pur și simplu nu va calcula valorile fiecărui element din listă, deoarece nu i s-a cerut să facă acest lucru. Aveți nevoie de lungimea listei - vă rog!
Aceleași principii se aplică și altor constructe ale limbajului.

Drept urmare, nu numai programatorul, ci și compilatorul primesc mai multe „grade de libertate”.

Enumerați expresii și declarații condiționale

Pentru ca viața (și programarea) să nu ți se pară miere, dezvoltatorii Python au venit cu o sintaxă specială „îndulcitoare”, pe care burghezia o numește „zahăr sintactic”.
Vă permite să scăpați de instrucțiunile și buclele condiționate... ei bine, dacă nu scăpați de ele, atunci cu siguranță reduceți-le la minimum.

În principiu, l-ați văzut deja în exemplul anterior - adaugă = . Aici creăm și inițializam imediat lista cu valorile funcției. Convenabil, nu?
Există, de asemenea, operatorii și și sau, care vă permit să faceți fără constructe greoaie precum if-elif-else.

Astfel, folosind setul de instrumente Python, puteți transforma o bucată de cod imperativă greoaie într-una funcțională frumoasă.

Cod imperativ

L=
pentru x în xrange(10):
dacă x % 2 == 0:
dacă x**2>=50:
L.apend(x)
altceva:
L.apend(-x)
imprima L

Codul funcției

imprimare

Rezultate

După cum ați înțeles deja, nu este necesar să urmați complet paradigma funcțională, este suficient să o folosiți cu pricepere în combinație cu cea imperativă pentru a vă simplifica viața. Totuși, am tot vorbit despre paradigma imperativă... și nu am spus nimic despre OOP și FP.

Ei bine, OOP este, de fapt, o suprastructură peste paradigma imperativă, iar dacă ați trecut de la IP la OOP, atunci următorul pas ar trebui să fie aplicarea FP în OOP. În concluzie, voi spune câteva cuvinte despre nivelul de abstractizare. Deci, cu cât este mai mare, cu atât mai bine și combinația dintre OOP și FP este cea care ne oferă acest nivel.

CD

Pe disc am pus noi distribuții Python pentru utilizatorii de Windows. Oamenii Linux nu au nevoie de ajutor :).

WWW

Câteva resurse bune pentru cei care doresc să învețe mai multe:

INFO

Dacă nu vă place Python, nu vă faceți griji - puteți aplica cu succes idei de programare funcțională în alte limbaje de nivel înalt.