Interaktiivinen Python-kielen opetusohjelma. Toiminnot ohjelmoinnissa

Kun aloin kirjoittaa lukua OOP:sta, tajusin, että olin täysin unohtanut kattaa niin suuren ja tarpeellisen osan Pythonista funktioina. Tämä aihe on laaja ja laaja, joten en venytä oppituntien välistä taukoa liikaa, päätin jakaa sen kahteen osaan. Ensin kerron sinulle perusasiat ja sitten Pythonin funktiosuunnittelun perusteelliset ominaisuudet.

Pythonissa funktioita ei ilmoiteta yksinkertaisesti, vaan hyvin yksinkertaisesti. Tässä on esimerkki yksinkertaisimmasta:

def empty_func(): pass
Ilmoitus alkaa avainsanalla def, joka, kuten saatat arvata, on lyhenne sanoista define. Sitä seuraa funktion nimi. Nimen jälkeen suluissa on lista parametreista, jotka puuttuvat tässä tapauksessa.
Funktion runko sisennetään seuraavalta riviltä. Huomaa, että funktiot, joissa on tyhjä runko, ovat kiellettyjä Pythonissa, joten "tyhjä operaattori" -passia käytetään yllä olevan funktion rungona.
Katsotaanpa nyt vakavampaa esimerkkiä.

Oletusarvo äiti___er!"
Tässä esimerkissä on useita innovaatioita. Ensimmäinen asia, joka kiinnittää huomiosi, on dokumentaatiorivi (docstring), joka tulee välittömästi funktion rungon jälkeen.
Yleensä tämä rivi vie useamman kuin yhden rivin lähdetekstiä (anteeksi sanaleikki), ja siksi se määritellään kolminkertaisissa lainausmerkeissä. Se on tarkoitettu kuvaamaan toimintoa, sen tarkoitusta, parametreja jne. Kaikki hyvät IDE:t voivat toimia tämän linjan kanssa. Voit käyttää sitä myös itse ohjelmasta __doc__-ominaisuuden avulla:

Tulosta safe_div.__doc__
Tätä ominaisuutta (kyllä, kyllä, täsmälleen ominaisuus, Pythonissa jopa funktiot ovat itse asiassa luokkia) on kätevä käyttää interaktiivisen konsolin istuntojen aikana.
>>> ftplibistä tuo FTP >>> tulosta FTP.__doc__ FTP-asiakasluokka. Luo yhteys kutsumalla luokkaa käyttämällä näitä argumentteja: host, user, passwd, acct Nämä ovat kaikki merkkijonoja ja niillä on oletusarvo "". Käytä sitten self.connect() valinnaisen isäntä- ja porttiargumentin kanssa. # loput olen tehnyt minä :-)
Palataan alkuperäiseen toimintoomme. Sen olemus on hyvin yksinkertainen, se vaatii 2 parametria: x ja y. Jos y ei ole 0, se jakaa x:n y:llä, tulostaa tuloksen näytölle ja palauttaa osamääränsä tuloksena. Funktion tulos palautetaan return-komennolla. Viime oppitunnilla kuvatun monikkomekanismin ansiosta Pythonin funktiot voivat palauttaa useita objekteja samanaikaisesti.
Jos jakaja on edelleen nolla, funktio näyttää virheilmoituksen. Olisi väärin olettaa, että tässä tapauksessa funktio ei palauta mitään. Olisi oikeampaa sanoa, että funktio palauttaa "ei mitään" :) Toisin sanoen, jos funktiolla ei ole return-lausetta tai sitä kutsutaan ilman parametreja, niin funktio palauttaa erikoisarvon None. Voit tarkistaa tämän helposti kutsumalla jotain, kuten print safe_div(10, 0).

Tässä on hieman monimutkaisempi esimerkki, joka on otettu Guido van Rossumin esittelyraportista.

Def gcd(a, b): "Etsitään GCD:tä", kun taas a != 0: a,b = b%a,a # rinnakkaismäärittely return b
Tämä funktio löytää kahden luvun suurimman yhteisen jakajan.

Muista, että Python-funktioiden parametrit välitetään viitteellä. Toinen, ehkä ei-triviaali tosiasia, johon sinun on totuttava, on se, että funktiot itsessään ovat arvo, joka voidaan määrittää. Jos käytämme safe_div-funktiota lisäkokeiluihin, voimme kirjoittaa seuraavan koodin.

Mystic_function = safe_div tulosta mystic_function(10, 4)
Siinä se tällä kertaa, Pythonin funktioiden määrittelyssä on vielä monia näkökohtia, jotka on jätetty "yli laidan", jotka käsitellään ensi kerralla.

Harjoituksia testaukseen.
1. Kirjoita olemassa olevan GCM:n etsimisfunktion perusteella funktio kahden luvun LCM:n löytämiseksi.
2. Kirjoita taulukointirutiini argumenttina välitetylle funktiolle. Argumentit määrittelevät myös alkuarvon, lopullisen arvon ja taulukointivaiheen.

PS muuten, mikä on "oppitunnin" optimaalinen pituus? Mikä on parempi - suuret luvut julkaistaan ​​harvemmin vai "vähemmän on parempi, useammin?"

Muistutus funktioparametrien (argumenttien) käyttämisestä Pythonissa.

Paikkaparametrit

Huomautuksia:

  • Kaikki on kuten tavallista.
  • Kun soitat, muista määrittää kaikki sijaintiargumentit.

Mielivaltainen määrä argumentteja

Huomautuksia:

  • Ilmaistu tähdellä ennen argumenttia - *args
  • Funktion sisäpuoli näyttää monikkoiselta, funktion elementit on järjestetty samaan järjestykseen kuin kutsuttaessa määritetyt funktion argumentit.
  • Voit välittää listan argumenttien joukkona funktiota kutsuessasi lisäämällä asteriski listan nimeämiseen eteen

Esimerkki 1. Funktion määrittäminen vaihtelevalla määrällä argumentteja:

Esimerkki 2. Listan välittäminen funktiolle argumenttijoukona:

Kommentit esimerkiksi 2:

  • Rivi 8: Vaihtuva määrä argumentteja
  • Rivi 9: Luettelo (hyväksytty yhtenä argumenttina)
  • Rivi 10: Lista tähdellä (hyväksytty muuttuvana lukumääränä argumentteja)

Nimetty Argumentit

Huomautuksia:

  • Soitettaessa ei tarvitse ilmoittaa. Jos niitä ei ole määritetty, niille on määritetty oletusarvot.

Huomautuksia:

  • Kun ohjelma ajetaan, arvot määritetään nimetyille parametreille kerran paikassa, jossa toiminto on määritelty. Jos määritetty objekti on muuttuva ja muuttunut funktion toiminnan aikana, sillä ei ole myöhemmissä kutsuissa funktiomäärittelyssä arvoksi määritettyä arvoa, vaan se, joka on annettu edellisen kutsun aikana.

Huomioitava esimerkki:

Mielivaltainen määrä nimettyjä argumentteja

Huomautuksia:

  • Merkitään kahdella tähdellä ennen argumenttia - **kwargs
  • Funktion sisällä se näyttää sanakirjalta, jonka avaimet vastaavat funktiota kutsuttaessa määritettyjen argumenttien nimiä.
  • Voit välittää sanakirjan nimettyjen argumenttien joukkona funktiota kutsuessasi lisäämällä kaksi tähteä sanakirjan nimeämisen eteen. Esimerkiksi näin: **kwargs

Esimerkki 1. Funktion määrittäminen mielivaltaisella määrällä nimettyjä argumentteja:

Esimerkki 2. Sanakirjan välittäminen funktiolle mielivaltaisena lukumääränä nimettyjä argumentteja:

Kommentit esimerkiksi 2:

  • Rivi 9: Sanakirja välitetään yhtenä nimettynä argumenttina.
  • Rivi 10: Sanakirja välitetään mielivaltaisena lukumääränä nimettyjä argumentteja.

Kaikenlaisia ​​parametreja yhdessä

Parametrin välittäminen viitteellä (parametrin muuttaminen funktion sisällä)

Jos funktiota kutsuttaessa korvaamme muuttujan argumentin arvoksi ja funktion rungossa muutamme argumentin arvoa, niin mitä tapahtuu, riippuu siitä, mihin arvoon muuttujamme liittyy. Jos muuttuja liittyy muuttumattomaan arvoon, esimerkiksi int, str, tulpe, tämä arvo ei luonnollisesti muutu. Mutta jos muuttuja liittyy luetteloon, sanakirjaan tai luokkaan, muuttujaan liittyvän objektin arvo muuttuu.

Ei muutu:

Muutokset:

Seuraus 1: jos meidän on "välitettävä parametri viitteellä", kuten C:ssä, meidän ei pitäisi tehdä tätä. Parametrin viittauksen välittämisen tarkoitus C:ssä on palauttaa useita arvoja funktiosta muuttamalla arvoja viitteellä funktion sisällä. Python antaa sinun palauttaa monikon, joten jos sinun on palautettava useita arvoja, sinun on palautettava monikko ja määritettävä sen arvot muuttujille.

Seuraus 2: "sivuvaikutusten" välttämiseksi on parempi käyttää oletusarvoina muuttumattomia tyyppejä (ei mitään, int, str, tulpe)

Kommentit:

Nimi: Igor

Kiitos, oli hyötyä


Nimi: Den

Erityiset kiitokset a(**d).


Nimi: testata

Nimi: alex

Nimi: Serg

Kiitos selkeistä selityksistä!


Nimi: Vlad2030

def fprintf(*args,**kwargs):
tulosta kwargs...
tulosta args...

KP = 2
N_elem =
c="privet"

KP = 2
N_elem =
c = "hei"

fprintf(KP, N_elem, c)

tulos:
KP = 2
N_elem =
c = privet


Nimi: seyalek

>>Kuinka kirjoittaa funktio niin, että se tulostaa muuttujiensa nimet ja arvot??

def fprintf(**kwargs):
k, v:lle kwargs.items():
tulosta "%s = %s" % (k, v)

ja soita perverssisesti:
fprintf(KP=KP, N_elem=N_elem, c=c)


Nimi: Aleksanteri

Harmi, että niin suurta määrää esimerkkejä ei voida käyttää CPP:ssä, koska en tunne phytonkia. Mutta aihe on mielenkiintoinen ja haluaisin todella tietää kuinka soveltaa sitä käytännössä, eikä vain 1,2,3. Jos esimerkiksi on tehtävä idea kääntää idea atmega8:ksi srr:ssä [tämä on taulukkoindeksi] = (Hei maailma) =)
index++ ja tulosta jokainen sykli kirjaimelta.


Nimi: Denis

Kun ohjelma ajetaan, arvot määritetään nimetyille parametreille kerran paikassa, jossa toiminto on määritelty. Jos määritetty objekti on muuttuva ja muuttunut funktion toiminnan aikana, sillä ei ole myöhemmissä kutsuissa sitä arvoa, joka on funktiomäärittelyssä arvoksi määritetty, vaan se, joka on annettu edellisen kutsun aikana.

Mainitsemasi pätee vain listoihin.
Koska määrittämällä luettelo parametrille, sekä parametri että luettelo luodaan samaan soluun ja niillä on sama tunnus. Kun muutat parametria, luettelo muuttuu ja kaikki tallennetaan samaan soluun.

Yleisesti ottaen jotain tuollaista!


Nimi: Asya

def parillinen(x):
jos x%2==0:
tulosta ("Kyllä")
____?
tulosta ("Ei")

Mitä laittaa toiseksi viimeiseen


Nimi: Asya

def parillinen(x):
jos x%2==0:
tulosta ("Kyllä")
____?
tulosta ("Ei")

Mitä laittaa toiseksi viimeiselle riville???


Nimi: Paskaa

Nimi: aaa

Nimi: Alyosha

Asya, tyhmä!


Nimi: Pavlo

Muista, että matematiikassa luvun n faktoraali määritellään n:ksi! = 1 ⋅ 2 ⋅ ... ⋅ n. Esimerkiksi 5! = 1 ⋅ 2 ⋅ 3 ⋅ 4 ⋅ 5 = 120. On selvää, että faktoriaali voidaan helposti laskea for-silmukalla. Kuvitellaan, että meidän täytyy laskea eri lukujen faktoraali useita kertoja ohjelmassamme (tai eri paikoissa koodissa). Tietenkin voit kirjoittaa tekijälaskelman kerran ja liittää sen sitten sinne, missä tarvitset sitä Copy-Paste-toiminnolla.

# Lasketaan 3! res = 1 i:lle alueella(1, 4): res *= i print(res) # laske 5! res = 1 i:lle alueella (1, 6): res *= i print(res)

Jos kuitenkin teemme virheen kerran alkuperäisessä koodissa, tämä virhe näkyy koodissa kaikissa paikoissa, joihin kopioimme kertoimen laskennan. Ja yleensä koodi vie enemmän tilaa kuin se voisi. Ohjelmointikielillä on toimintoja, jotta vältytään kirjoittamasta samaa logiikkaa uudestaan ​​​​ja uudestaan.

Funktiot ovat koodin osia, jotka on eristetty muusta ohjelmasta ja jotka suoritetaan vain, kun niitä kutsutaan. Olet jo nähnyt funktiot sqrt(), len() ja print(). Niillä kaikilla on yhteinen ominaisuus: ne voivat ottaa parametreja (nolla, yksi tai useampi) ja ne voivat palauttaa arvon (vaikka eivät välttämättä). Esimerkiksi sqrt()-funktio ottaa yhden parametrin ja palauttaa arvon (luvun juuren). Print()-funktio ottaa muuttuvan määrän parametreja eikä palauta mitään.

Osoitetaan, kuinka kirjoitetaan funktio factorial(), joka ottaa yhden parametrin - luvun, ja palauttaa arvon - tämän luvun kertoimen.

Def factorial(n): res = 1 i:lle alueella(1, n + 1): res *= i return res print(factorial(3)) print(factorial(5))

Annetaan muutama selitys. Ensinnäkin funktiokoodi on sijoitettava ohjelman alkuun, tai pikemminkin ennen paikkaa, jossa haluamme käyttää factorial()-funktiota. Tämän esimerkin ensimmäinen rivi on kuvaus toiminnastamme. factorial on tunniste, eli funktiomme nimi. Suluissa olevan tunnuksen jälkeen on luettelo parametreista, jotka funktiomme vastaanottaa. Luettelo koostuu pilkuilla erotetuista parametrien tunnisteista. Meidän tapauksessamme lista koostuu yhdestä arvosta n. Kaksoispiste asetetaan rivin loppuun.

Seuraavaksi tulee funktion runko, joka on suunniteltu lohkoksi, eli sisennetty. Funktion sisällä lasketaan n:n tekijäarvo ja se tallennetaan res-muuttujaan. Funktio päättyy return res -lauseeseen, joka päättää funktion ja palauttaa res-muuttujan arvon.

Paluukäsky voi esiintyä missä tahansa funktiossa sen suorittaminen lopettaa funktion ja palauttaa määritetyn arvon paikkaan, jossa sitä kutsuttiin. Jos funktio ei palauta arvoa, käytetään return-lausetta ilman palautusarvoa. Funktioilla, joiden ei tarvitse palauttaa arvoa, ei välttämättä ole return-lausetta.

Otetaan toinen esimerkki. Kirjoitetaan funktio max(), joka ottaa kaksi lukua ja palauttaa niistä suurimman (itse asiassa tällainen funktio on jo sisäänrakennettu Pythonissa).

10 20 def max(a, b): jos a > b: return a else: return b print(max(3, 5)) print(max(5, 3)) print(max(int(input()), int(syöttö())))

Nyt voimme kirjoittaa funktion max3(), joka ottaa kolme numeroa ja palauttaa niistä suurimman.

Def max(a, b): jos a > b: palauttaa a else: return b def max3(a, b, c): return max(max(a, b), c) print(max3(3, 5, 4) ))

Pythonin sisäänrakennettu max()-funktio voi ottaa vaihtelevan määrän argumentteja ja palauttaa niistä suurimman. Annetaan esimerkki siitä, kuinka tällainen funktio voidaan kirjoittaa.

Def max(*a): res = a arvolle a: if val > res: res = val return res print(max(3, 5, 4))

Kaikki tälle funktiolle välitetyt parametrit kerätään yhteen monikkoon nimeltä a, kuten funktion määritysrivillä on asteriski.

2. Paikalliset ja globaalit muuttujat

Voit käyttää funktion sisällä muuttujia, jotka on ilmoitettu funktion ulkopuolella.

Def f(): tulosta(a) a = 1 f()

Tässä muuttujalle a annetaan arvo 1, ja f()-funktio tulostaa tämän arvon, vaikka muuttujaa ei alusteta ennen kuin f on ilmoitettu. Kun f():tä kutsutaan, a:lle on jo annettu arvo, joten f() voi näyttää sen näytöllä.

Tällaisia ​​muuttujia (ilmoitetaan funktion ulkopuolella, mutta jotka ovat käytettävissä funktion sisällä) kutsutaan maailmanlaajuisesti.

Mutta jos alustat muuttujan funktion sisällä, et voi käyttää tätä muuttujaa funktion ulkopuolella. Esimerkiksi:

Oletus f(): a = 1 f() print(a)

Saamme virheilmoituksen NameError: nimi "a" ei ole määritelty. Tällaisia ​​funktion sisällä ilmoitettuja muuttujia kutsutaan paikallinen. Nämä muuttujat eivät ole käytettävissä funktion sulkemisen jälkeen.

Tulos on mielenkiintoinen, jos yrität muuttaa globaalin muuttujan arvoa funktion sisällä:

Oletus f(): a = 1 tulosta(a) a = 0 f() tulosta(a)

Numerot 1 ja 0 tulostuvat, vaikka muuttujan a arvo on muuttunut funktion sisällä, funktion ulkopuolella se pysyy samana! Tämä tehdään globaalien muuttujien "suojaamiseksi" funktion vahingossa tapahtuvilta muutoksilta. Esimerkiksi jos funktiota kutsutaan silmukasta muuttujalla i ja tässä funktiossa myös muuttujaa i käytetään silmukan järjestämiseen, näiden muuttujien on oltava erilaisia. Jos et ymmärrä viimeistä lausetta, katso seuraavaa koodia ja mieti, miten se toimisi, jos muuttuja i muutetaan funktion sisällä.

Def factorial(n): res = 1 i:lle alueella(1, n + 1): res *= i palauttaa res i:lle alueella(1, 6): print(i, "! = ", factorial(i) , syys="")

Jos globaalia muuttujaa i muutetaan funktion sisällä, saamme tämän:

5! = 1 5! = 2 5! = 6 5! = 24 5! = 120

Eli jos jonkin muuttujan arvoa muutetaan funktion sisällä, niin samannimisestä muuttujasta tulee paikallinen muuttuja, eikä sen muokkaus muuta samannimistä globaalia muuttujaa.

Muodollisemmin: Python-tulkki pitää tietyn funktion muuttujaa paikallisena, jos sen koodi sisältää vähintään yhden muuttujan arvoa muokkaavan käskyn, tätä muuttujaa pidetään paikallisena eikä sitä voida käyttää ennen alustusta. Muuttujan arvoa muuttavat käskyt ovat operaattorit = , += , sekä muuttujan käyttö for-silmukan parametrina. Lisäksi, vaikka muuttujaa muokkaavaa käskyä ei koskaan suoritettaisi, tulkki ei voi tarkistaa tätä, ja muuttujaa pidetään silti paikallisena. Esimerkki:

Def f(): tulosta(a), jos epätosi: a = 0 a = 1 f()

Tapahtuu virhe: UnboundLocalError: paikallinen muuttuja "a", johon viitattiin ennen määritystä . Nimittäin f()-funktiossa tunniste a tulee paikalliseksi muuttujaksi, koska funktio sisältää komennon, joka muuttaa muuttujaa a, vaikka sitä ei koskaan suoritettaisikaan (mutta tulkki ei voi seurata tätä). Siksi muuttujan a tulostaminen johtaa alustamattomaan paikalliseen muuttujaan.

Jotta funktio voisi muuttaa globaalin muuttujan arvoa, tämä muuttuja on määritettävä funktion sisällä globaaliksi yleisellä avainsanalla:

Oletus f(): globaali a a = 1 print(a) a = 0 f() print(a)

Tässä esimerkissä näytöllä näkyy 1 1, koska muuttuja a on ilmoitettu globaaliksi ja sen muuttaminen funktion sisällä johtaa siihen, että muuttuja on käytettävissä funktion ulkopuolella.

On kuitenkin parempi olla muuttamatta globaalien muuttujien arvoja funktion sisällä. Jos funktiosi täytyy muuttaa jotain muuttujaa, olisi parempi, jos se palauttaa tämän arvon, ja annat itse nimenomaisesti tämän arvon muuttujalle kutsuessasi funktiota. Jos noudatat näitä sääntöjä, funktiot ovat koodista riippumattomia ja ne voidaan helposti kopioida ohjelmasta toiseen.

Oletetaan esimerkiksi, että ohjelmasi tarvitsee laskea syötenumeron kertoimen, jonka haluat sitten tallentaa muuttujaan f. Näin on ei ole sen arvoinen tehdä:

5 def factorial(n): globaali f res = 1 i:lle alueella(2, n + 1): res *= i f = res n = int(input()) factorial(n) # lisätoiminnot muuttujalla f

Tämä koodi on kirjoitettu huonosti, koska sitä on vaikea käyttää uudelleen. Jos huomenna joudut käyttämään tekijäfunktiota toisessa ohjelmassa, et voi yksinkertaisesti kopioida tätä funktiota täältä ja liittää sitä uuteen ohjelmaan. Sinun on muutettava tapaa, jolla se palauttaa lasketun arvon.

On paljon parempi kirjoittaa tämä esimerkki uudelleen näin:

5 # koodinpalan alku, joka voidaan kopioida ohjelmasta ohjelmaan def factorial(n): res = 1 for i in range(2, n + 1): res *= i return res # kappaleen loppu koodista n = int(input( )) f = factorial(n) # edelleen kaikenlaisia ​​toimia muuttujalla f

Jos tarvitset funktion palauttamaan yhden arvon sijaan kaksi tai useampia, funktio voi tätä tarkoitusta varten palauttaa luettelon kahdesta tai useammasta arvosta:

Sitten funktiokutsun tulosta voidaan käyttää useassa tehtävässä:

3. Rekursio

Def short_story(): print("Papilla oli koira, hän rakasti häntä.") print("Hän söi lihan, hän tappoi hänet,") print("Hän hautasi sen maahan ja kirjoitti kirjoituksen: ") novelli()

Kuten yllä näimme, funktio voi kutsua toista funktiota. Mutta funktio voi myös kutsua itseään! Tarkastellaan tätä käyttämällä esimerkkinä kertoimien laskentafunktiota. On hyvin tunnettua, että 0!=1, 1!=1. Kuinka laskea n:n arvo! isolle n? Jos pystyisimme laskemaan arvon (n-1)!, voimme helposti laskea n!:n, koska n!=n⋅(n-1)!. Mutta miten lasketaan (n-1)!? Jos laskemme (n-2)!, voimme laskea myös (n-1)!=(n-1)⋅(n-2)!. Kuinka laskea (n-2)!? Jos... Lopulta päästään arvoon 0!, joka on yhtä suuri kuin 1. Voimme siis käyttää kertoimen arvoa pienemmän luvun laskentaan. Tämä voidaan tehdä myös Python-ohjelmassa:

Def factorial(n): jos n == 0: return 1 else: return n * factorial(n - 1) print(factorial(5))

Tätä tekniikkaa (itseään kutsuvaa toimintoa) kutsutaan rekursioksi, ja itse funktiota kutsutaan rekursiiviseksi.

Rekursiiviset funktiot ovat tehokas ohjelmointimekanismi. Valitettavasti ne eivät aina ole tehokkaita. Myös rekursion käyttö johtaa usein virheisiin. Yleisin näistä virheistä on ääretön rekursio, jossa funktiokutsujen ketju ei koskaan pääty ja jatkuu, kunnes tietokoneen vapaa muisti loppuu. Esimerkki äärettömästä rekursiosta on tämän osan epigrafiassa. Kaksi yleisintä syytä äärettömään rekursioon ovat:

  1. Väärä poistuminen rekursiosta. Jos esimerkiksi unohdamme tarkistaa, onko n == 0 kertolaskuohjelmassa, niin factorial(0) kutsuu factorial(-1), joka kutsuu factorial(-2) jne.
  2. Rekursiivinen puhelu virheellisillä parametreilla. Esimerkiksi jos funktio factorial(n) kutsuu factorial(n) , se johtaa myös äärettömään ketjuun.

Siksi rekursiivista funktiota kehitettäessä sinun on ensin formalisoitava rekursion päättymisen ehdot ja mietittävä, miksi rekursio koskaan päättyy.

On olemassa suuri määrä julkaisuja, jotka on omistettu funktionaalisten ohjelmointikonseptien toteuttamiseen Pythonissa, mutta suurimman osan näistä materiaaleista on kirjoittanut yksi kirjoittaja - David Mertz. Lisäksi monet näistä artikkeleista ovat jo vanhentuneita ja hajallaan eri verkkoresursseissa. Tässä artikkelissa yritämme palata tähän aiheeseen päivittääksemme ja järjestääksemme saatavilla olevat tiedot, etenkin kun otetaan huomioon Pythonin rivin 2 ja 3 versioiden väliset suuret erot.

Toiminnot Pythonissa

Pythonissa funktiot määritellään kahdella tavalla: määritelmän kautta def tai anonyymin kuvauksen kautta lambda. Molemmat määrittelymenetelmät ovat saatavilla vaihtelevissa määrin joissakin muissa ohjelmointikielissä. Pythonin erikoisuus on, että funktio on nimetty objekti, aivan kuten mikä tahansa muu jonkin tietotyypin objekti, esimerkiksi kokonaislukumuuttuja. Lista 1 näyttää yksinkertaisimman esimerkin (tiedosto func.py arkistosta python_functional.tgz

Listaus 1. Toimintojen määritelmät
#!/usr/bin/python # -*- koodaus: utf-8 -*- tuonti sys def show(fun, arg): print("() : ()".format(type(fun), fun)) print("arg=() => hauska(arg)=()". muoto(arg, hauska(arg))) if len(sys.argv) > 1: n = float(sys.argv[ 1 ]) else : n = float(input("numero?: ")) def pow3(n): # 1. funktion määritelmä return n * n * n show(pow3, n) pow3 = lambda n: n * n * n # 2 -th samannimisen funktion määritelmä show(pow3, n) show((lambda n: n * n * n), n) # 3., anonyymin funktion määritelmän käyttö

Kun kutsumme kaikkia kolmea funktiooliota, saamme saman tuloksen:

$ python func.py 1.3 : arg=1,3 => hauska(arg)=2,197 : at 0xb7662bc4> arg=1.3 => fun(arg)=2.197 : at 0xb7662844> arg=1.3 => fun(arg)=2.197

Tämä näkyy vielä selvemmin Python-versiossa 3, jossa kaikki on luokkaa (mukaan lukien kokonaislukumuuttuja) ja funktiot ovat ohjelmaobjekteja, jotka kuuluvat luokkaan toiminto:

$python3 func.py 1.3 : arg=1.3 => hauska(arg)=2.197000000000000005 : at 0xb745432c> arg=1.3 => fun(arg)=2.19700000000000005 : 0xb74542ec> arg=1.3 => hauska(arg)=2.19700000000000005

Huomautus. On 2 muuta objektityyppiä, jotka sallivat funktiokutsun - funktionaalisen luokan menetelmä ja funktionaali, joista puhumme myöhemmin.

Jos Python-funktioobjektit ovat objekteja kuten muutkin tietoobjektit, voit tehdä niillä kaiken, mitä voit tehdä millä tahansa tiedolla:

  • dynaamisesti muuttaa käynnissä;
  • upottaa monimutkaisempiin tietorakenteisiin (kokoelmiin);
  • passi parametreina ja palautusarvoina jne.

Tämä (manipulointi toiminnallisilla objekteilla tietoobjekteina) on se, mihin toiminnallinen ohjelmointi perustuu. Python ei tietenkään ole todellinen toiminnallinen ohjelmointikieli, esimerkiksi täysin toimivaan ohjelmointiin on olemassa erityisiä kieliä: Lisp, Planner ja uudemmat: Scala, Haskell. Ocaml, ... Mutta Pythonissa voit "upottaa" toiminnallisia ohjelmointitekniikoita yleiseen pakottavan (komento) koodin virtaan, esimerkiksi käyttää täysimittaisista toiminnallisista kielistä lainattuja menetelmiä. Nuo. "taittaa" yksittäisiä imperatiivisen koodin fragmentteja (joskus melko suuria) toiminnallisiksi lausekkeiksi.

Joskus ihmiset kysyvät: "Mitä etuja yksittäisten fragmenttien kirjoittamisen toiminnallisesta tyylistä on ohjelmoijalle?" Funktionaalisen ohjelmoinnin tärkein etu on, että sellaisen fragmentin virheenkorjauksen jälkeen kerran, myöhempi toistuva käyttö ei aiheuta virheitä tehtävien ja nimiristiriitojen sivuvaikutuksista.

Melko usein Pythonilla ohjelmoitaessa käytetään tyypillisiä funktionaalisen ohjelmoinnin alan rakenteita, esim.

tulosta ([ (x,y) x:lle (1, 2, 3, 4, 5) \ y:lle (20, 15, 10) \ jos x * y > 25 ja x + y< 25 ])

Juoksun tuloksena saamme:

$ python funcp.py [(2,20), (2,15), (3,20), (3,15), (3,10), (4,20), (4,15), (4 ,10), (5,15), (5,10)]

Toimii objekteina

Kun luot funktioobjektia operaattorilla lambda, kuten listassa 1 näkyy, voit sitoa luodun funktioobjektin nimeen pow3 täsmälleen samalla tavalla kuin tähän nimeen voisi liittää numeron 123 tai merkkijonoa "Hei!". Tämä esimerkki vahvistaa funktioiden tilan ensimmäisen luokan objekteina Pythonissa. Pythonin funktio on vain toinen arvo, jolla voit tehdä jotain.

Yleisin ensiluokkaisten funktioobjektien kanssa tehty toiminto on niiden välittäminen korkeamman asteen sisäänrakennetuille funktioille: kartta(), vähentää() Ja suodattaa(). Jokainen näistä funktioista ottaa funktioobjektin ensimmäiseksi argumentiksi.

  • kartta() käyttää hyväksytyn funktion jokaiseen elementtiin hyväksytyssä luettelossa ja palauttaa tulosluettelon (sama ulottuvuus kuin syöte);
  • vähentää() soveltaa hyväksyttyä funktiota jokaiseen listan arvoon ja sisäiseen tuloskerääjään, esim. vähennä(lambda n,m: n * m, alue(1, 10)) tarkoittaa 10! (factorial);
  • suodattaa() soveltaa hyväksytyn funktion jokaiseen luettelon elementtiin ja palauttaa luettelon niistä alkuperäisen luettelon elementeistä, joille hyväksytty funktio palautti tosi.

Yhdistämällä nämä kolme ominaisuutta voit toteuttaa yllättävän laajan valikoiman ohjausvirtaoperaatioita turvautumatta pakollisiin lauseisiin, vaan käyttämällä vain funktionaalisia lausekkeita, kuten Listing 2 (tiedosto funcH.py arkistosta python_functional.tgz

Listaus 2. Pythonin korkeamman asteen funktiot
#!/usr/bin/python # -*- koodaus: utf-8 -*- tuonti sys def input_arg(): globaali arg arg = (lambda: (len(sys.argv) > 1 ja int(sys.argv[ 1 ])) tai \ int(input("numero?: ")))() return arg print("argumentti = ()".format(input_arg())) print(list(map(lambda x: x + 1) , range(arg))) print(list(filter(lambda x: x > 4, range(arg)))) import functools print("()! = ()".format(arg, functools.reduce(lambda x , y: x * y, alue(1, arg))))

Huomautus. Tämä koodi on hieman monimutkaisempi kuin edellinen esimerkki seuraavien Python-versioiden 2 ja 3 välisten yhteensopivuusongelmien vuoksi:

  • Toiminto vähentää() Python 2:ssa sisäänrakennettu, siirrettiin Python 3:n moduuliin toiminnalliset työkalut ja kutsuminen suoraan nimellä tekee poikkeuksen NameError, jotta se toimisi oikein, puhelu on muotoiltava esimerkin mukaisesti tai sisältää rivi: Functoolsin tuonnista *
  • Toiminnot kartta() Ja suodattaa() Python 3:ssa ne eivät palauta luetteloa (kuten jo osoitettiin versioeroista puhuttaessa), vaan muodon iteraattoriobjekteja:

Funktiota kutsutaan saadaksesi koko arvoluettelon heille lista().

Siksi tämä koodi voi toimia molemmissa Python-versioissa:

$python3 funcH.py 7 argumentti = 7 7! = 720

Jos koodin siirrettävyyttä eri versioiden välillä ei vaadita, tällaiset fragmentit voidaan sulkea pois, mikä mahdollistaa koodin yksinkertaistamisen jonkin verran.

Rekursio

Funktionaalisessa ohjelmoinnissa rekursio on perusmekanismi, joka on samanlainen kuin iteratiivisen ohjelmoinnin silmukat.

Joissakin Python-keskusteluissa olen toistuvasti törmännyt väitteisiin, että Pythonissa rekursion syvyyttä rajoittaa "laitteisto", joten joitain toimintoja ei periaatteessa voida toteuttaa. Pythonilla on oletusarvoinen rekursion syvyysraja 1000, mutta tämä on numeerinen asetus, joka voidaan aina ohittaa listassa 3 esitetyllä tavalla (katso täydellinen esimerkkikoodi tiedostosta fact2.py arkistosta python_functional.tgz

Listaus 3. Faktoriaalin laskeminen mielivaltaisella rekursiosyvyydellä
#!/usr/bin/python # -*- koodaus: utf-8 -*- tuonti sys arg = lambda: (len(sys.argv) > 1 ja int(sys.argv[ 1 ])) tai \ int( input("numero?: ")) factorial = lambda x: ((x == 1) ja 1) tai x * factorial(x - 1) n = arg() m = sys.getrecursionlimit() jos n >= m - 1: sys.setrecursionlimit(n + 2) print("rekursion syvyys ylittää järjestelmässä määritetyn (), reset to ()".\ format(m, sys.getrecursionlimit())) print("n=( ) => n!=()".format(n, factorial(n))) if sys.getrecursionlimit() > m: print("rekursion syvyys palautettu ()".format(m)) sys.setrecursionlimit(m )

Tältä tämän esimerkin suoritus näyttää Python 3:ssa ja Python2:ssa (vaikka todellisuudessa tuloksena oleva numero ei todennäköisesti mahdu yhdelle konsolin päätenäytölle):

$ python3 fact2.py 1001 rekursion syvyys ylittää järjestelmäjoukon 1000, palauta arvoon 1003 n=1001 => n!=4027........................ . ......................... 0000000000000 rekursion syvyys palautettu 1000

Muutama yksinkertainen esimerkki

Suoritetaan useita yksinkertaisia ​​muunnoksia tavallisesta pakottavasta koodista (komento, operaattori) sen yksittäisten fragmenttien muuntamiseksi toiminnallisiksi. Korvataan ensin haarautumisoperaattorit loogisilla ehdoilla, jotka "lykättyjen" (laiskojen, laiskojen) laskutoimitusten vuoksi antavat meille mahdollisuuden hallita yksittäisten koodihaarojen suorittamista tai suorittamatta jättämistä. Joten pakollinen rakennus:

jos<условие>: <выражение 1>muu:<выражение 2>

Vastaa täysin seuraavaa toiminnallista fragmenttia (loogisten operaattoreiden "lykättyjen" ominaisuuksien vuoksi ja Ja tai):

# toiminto ilman parametreja: lambda: (<условие>ja<выражение 1>) tai (<выражение 2>)

Käytettynä esimerkkinä taas kertolaskua. Lista 4 näyttää funktionaalisen koodin kertoimen laskemiseksi (tiedosto fakta1.py arkistossa python_functional.tgz"Lataa materiaalit" -osiossa):

Listaus 4. Faktoriaalin operaattori (pakollinen) määritelmä
#!/usr/bin/python # -*- koodaus: utf-8 -*- tuonti sys def factorial(n): if n == 1: return 1 else: return n * factorial(n - 1) if len( sys.argv) > 1: n = int(sys.argv[ 1 ]) else: n = int(input("numero?: ")) print("n=() => n!=()". muoto (n, tekijä(n)))

Laskettava argumentti otetaan komentoriviparametrin arvosta (jos sellainen on) tai syötetään päätteestä. Ensimmäinen yllä näkyvä muutos on jo otettu käyttöön listassa 2, jossa funktiolausekkeet on korvattu:

  • Tekijäfunktion määritelmä: tekijä = lambda x: ((x == 1) ja 1) tai x * tekijä(x - 1)
  • pyyntö argumentin arvon syöttämiseksi päätekonsolista: arg = lambda: (len(sys.argv) > 1 ja int(sys.argv[ 1 ])) tai \ int(input("numero?: ")) n = arg()

Tiedostossa fact3.py toinen funktion määritelmä tulee näkyviin korkeamman asteen funktion kautta vähentää():

tekijä = tekijä = lambda z: vähennä(lambda x, y: x * y, alue(1, z + 1))

Tässä yksinkertaistamme myös lauseketta for n, pelkistämällä sen yhdeksi kutsuksi nimettömään (ei nimettyyn) funktioon:

n = (lambda: (len(sys.argv) > 1 ja int(sys.argv[ 1 ])) tai \ int(input("numero?: ")))()

Lopuksi voit huomata, että arvon määrittäminen muuttujalle n tarvitaan vain sen käyttöä varten puhelussa Tulosta() tulostaaksesi tämän arvon. Jos hylkäämme tämän rajoituksen, koko sovellus rappeutuu yhdeksi toimivaksi operaattoriksi (katso tiedosto fact4.py arkistossa python_functional.tgz"Lataa materiaalit" -osiossa):

from sys import * from functools tuonti vähentää print("laskettu tekijä = ()".format(\ (lambda z: vähentää(lambda x, y: x * y, range(1, z + 1))) \ ((lambda : (len(argv) > 1 ja int(argv[ 1 ])) tai \ int(tulo("numero?: ")))())))

Tämä yksittäinen puhelu funktion sisällä Tulosta() ja edustaa koko sovellusta sen toiminnallisessa muodossa:

$python3 fact4.py numero?: 5 laskettu tekijä = 120

Lukeeko tämä koodi (tiedosto fact4.py) paremmin kuin pakottava merkintä (tiedosto fact1.py)? Todennäköisemmin ei kuin kyllä. Mikä sen arvo sitten on? Tosiasia on, että milloin minkä tahansa muutokset ympäröivään koodiin, normaali toiminta Tämä fragmentti säilyy, koska käytettyjen muuttujien arvojen muutoksista ei aiheudu sivuvaikutusten riskiä.

Korkeamman asteen toiminnot

Toiminnallisessa ohjelmointityylissä vakiokäytäntö on dynaaminen sukupolvi toiminnallinen objekti koodin suorittamisen aikana, ja sen myöhempi kutsu samassa koodissa. On monia aloja, joilla tällainen tekniikka voi olla hyödyllinen.

Päättäminen

Yksi toiminnallisen ohjelmoinnin mielenkiintoisista käsitteistä on sulkemiset(päättäminen). Tämä idea osoittautui niin houkuttelevaksi monille kehittäjille, että se toteutettiin jopa joissakin ei-toimivissa ohjelmointikielissä (Perl). David Mertz antaa seuraavan määritelmän sulkemiselle: "Sulkeminen on menettely yhdessä siihen sidotun datajoukon kanssa" (toisin kuin objektiohjelmoinnin objekteja, kuten "data yhdessä siihen sidotun joukon proseduureja") .

Sulkemisen merkitys on, että funktion määritelmä "jäädyttää" ympäröivän kontekstin päättäväisyyden hetki. Tämä voidaan tehdä useilla tavoilla, esimerkiksi parametroimalla funktion luonti, kuten näkyy listassa 5 (tiedosto clos1.py arkistossa python_functional.tgz"Lataa materiaalit" -osiossa):

Listaus 5. Sulkemisen luominen
# -*- koodaus: utf-8 -*- def kerroin(n): # kerroin palauttaa funktion, joka kertoo n:llä def mul(k): return n * k return mul mul3 = kertoja(3) # mul3 on funktio joka kertoo 3:lla tulosta(mul3(3), mul3(5))

Näin dynaamisesti määritelty funktio toimii:

$ python clos1.py (9, 15) $ python3 clos1.py 9 15

Toinen tapa luoda sulkeminen on käyttää parametrin oletusarvoa funktion määrityspisteessä, kuten listassa 6 (tiedosto clos3.py arkistosta python_functional.tgz"Lataa materiaalit" -osiossa):

Listaus 6. Toinen tapa sulkea
n = 3 oletus mult(k, mul = n): paluu mul * k n = 7 print(mult(3)) n = 13 print(mult(5)) n = 10 mult = lambda k, mul=n: mul * k print(mult(3))

Mikään myöhempi oletusparametrin määrittäminen ei muuta aiemmin määritettyä funktiota, mutta itse toiminto voidaan ohittaa:

$ python clos3.py 9 15 30

Osatoimintosovellus

Osatoimintosovellus olettaa funktion perusteella N muuttujien määritelmä uudelle funktiolle, jossa on vähemmän muuttujia M < N, kun taas loput N-M muuttujat saavat kiinteitä "jäädytettyjä" arvoja (moduulia käytetään toiminnalliset työkalut). Vastaavaa esimerkkiä käsitellään alla.

Functor

Funktori ei ole funktio, vaan luokkaobjekti, jossa menetelmä kutsuu __puhelu__(). Tässä tapauksessa kutsu voidaan soveltaa tällaisen objektin esiintymään, aivan kuten se tapahtuu funktioille. Listauksessa 7 (tiedosto osa.py arkistosta python_functional.tgz(katso Lataukset-osio) osoittaa, kuinka käyttää sulkemista, osittaista funktion määritelmää ja funktiota saman tuloksen tuottamiseksi.

Listaus 7. Sulkemisen, osittaisen määritelmän ja funktorin vertailu
# -*- koodaus: utf-8 -*- def kerroin(n): # sulkeminen def mul(k): return n * k return mul mul3 = kerroin(3) functoolsista tuo osittainen def mulPart(a, b ): # funktion osittainen sovellus return a * b par3 = partial(mulPart, 3) luokka mulFunctor: # vastaava funktion def __init__(self, arvo1): self.val1 = val1 def __call__(self, val2): return self.val1 * val2 fun3 = mulFunctor(3) print("() . () . ()".format(mul3(5), par3(5), fun3(5)))

Kaikkien kolmen konstruktin kutsuminen argumentille, joka on yhtä suuri kuin 5, tuottaa saman tuloksen, vaikka ne käyttävät täysin erilaisia ​​mekanismeja:

$ python part.py 15 . 15 . 15

Karring

Curring (tai currying) on ​​funktion muuntaminen monista muuttujista funktioksi, joka ottaa argumentit yksi kerrallaan.

Huomautus. Tämän muunnoksen esittelivät M. Sheinfinkel ja G. Frege, ja se nimettiin matemaatikko Haskell Curryn mukaan, jonka mukaan myös Haskell-ohjelmointikieli on nimetty.

Curry ei ole toiminnallisen ohjelmoinnin ainutlaatuinen ominaisuus. Curry-muunnos voidaan kirjoittaa esimerkiksi Perlissä tai C++:ssa. Curry-operaattori on jopa sisäänrakennettu joihinkin ohjelmointikieliin (ML, Haskell), mikä mahdollistaa monipaikan funktioiden johtamisen curry-esitykseen. Mutta kaikki sulkemista tukevat kielet antavat sinun kirjoittaa curry-funktioita, eikä Python ole tässä suhteessa poikkeus.

Listassa 8 on yksinkertainen esimerkki curryingin käytöstä (tiedosto curry1.py arkistossa python_functional.tgz"Lataa materiaalit" -osiossa):

Listaus 8. Curring
# -*- koodaus: utf-8 -*- def roskaposti(x, y): print("param1=(), param2=()".muoto(x, y)) roskaposti1 = lambda x: lambda y: roskaposti (x, y) def roskaposti2(x) : def new_spam(y) : palauta roskaposti(x, y) return new_spam spam1(2)(3) # currying roskaposti2(2)(3)

Tältä näiden kutsujen suorittaminen näyttää:

$ python curry1.py param1=2, param2=3 param1=2, param2=3

Johtopäätös

Tässä artikkelissa esiteltiin joitain Python-kielen ominaisuuksia, joiden avulla voit kirjoittaa ohjelmia käyttämällä toiminnallista ohjelmointityyliä. Joten kuvailimme toiminnallisen ohjelmoinnin perustekniikat ja näytimme esimerkkejä niiden toteutuksesta Pythonissa. Kuten aiemmissa artikkeleissa, koodiesimerkit on kirjoitettu siten, että ne voidaan suorittaa onnistuneesti molemmissa Python-versioissa.

Seuraavassa artikkelissa käsittelemme ongelmia koodin rinnakkaisen suorituksen järjestämisessä Python-ympäristössä.