4. Mai multe unelte de control al fluxului¶
Pe lângă instrucțiunea de control while
introdusă anterior, Python-ul utilizează alte câteva asemenea instrucțiuni cu care ne vom întâlni în acest capitol.
4.1. Instrucțiuni if
¶
Probabil că cel mai cunoscut tip de instrucțiune de control este instrucțiunea if
. Ca, de exemplu:
>>> x = int(input("Vă rugăm să introduceți un număr întreg: "))
Vă rugăm să introduceți un număr întreg: 42
>>> if x < 0:
... x = 0
... print('Număr negativ, pe care îl transformăm în zero')
... elif x == 0:
... print('Numărul zero')
... elif x == 1:
... print('Numărul unu')
... else:
... print('Număr mai mare ca unu')
...
Număr mai mare ca unu
Putem avea zero sau mai multe clauze elif
, în timp ce clauza else
este opțională. Cuvântul-cheie «elif
» este prescurtarea lui «else if» și ne permite să evităm indentarea excesivă. O secvență if
… elif
… elif
… servește de substitut pentru instrucțiuni precum switch
sau case
disponibile în alte limbaje de programare.
Dacă aveți de comparat o valoare cu mai multe constante, ori dacă verificați anumite tipuri de date sau atribute, s-ar putea să vă fie utilă instrucțiunea de control match
. Pentru mai multe detalii, vedeți Instrucțiuni match.
4.2. Instrucțiuni for
¶
Instrucțiunea for
din Python diferă puțin de cele cu care poate că sunteți obișnuit din C ori din Pascal. În loc să itereze mereu după elementele numerice ale unei progresii aritmetice (precum în Pascal), ori să-i dea utilizatorului posibilitatea de a defini atât un pas de iterație cât și o condiție de oprire (ca în C), instrucțiunea for
a Python-ului iterează după itemii oricărei secvențe (fie ea listă sau șir de caractere), în ordinea în care acești itemi apar în secvență. De exemplu (fără să facem vreun joc de cuvinte) :
>>> # Să evaluăm câteva șiruri de caractere:
>>> cuvinte = ['pisică', 'fereastră', 'defenestrare']
>>> for c in cuvinte:
... print(c, len(c))
...
pisică 6
fereastră 9
defenestrare 12
Codul care să modifice o colecție în timp ce iterează după itemii acelei colecții este greu de scris din prima încercare. În schimb, se dovedesc mai ușor de realizat atât o ciclare după itemii unei copii a colecției în cauză cât și crearea unei colecții noi:
# Crearea unei colecții eșantion
utilizatori = {'Hans': 'activ', 'Éléonore': 'inactiv', '景太郎': 'activ'}
# Strategie: să iterăm după itemii unei copii
for utilizatorul, starea in utilizatori.copy().items():
if starea == 'inactiv':
del utilizatori[utilizatorul]
# Strategie: să creăm o nouă colecție
utilizatori_activi = {}
for utilizatorul, starea in utilizatori.items():
if starea == 'activ':
utilizatori_activi[utilizatorul] = starea
4.3. Funcția range()
¶
Dacă aveți de iterat după termenii unui șir de numere, atunci funcția predefinită range()
vă este la îndemână. Ea generează progresii aritmetice:
>>> for i in range(5):
... print(i)
...
0
1
2
3
4
Valoarea de final precizată nu va face niciodată parte din secvența generată; range(10)
generează 10 valori, mai precis indecșii permiși ai itemilor unei secvențe de lungime 10. Este posibil să începem plaja de valori și de la alt număr, ori să specificăm un increment diferit (inclusiv negativ; uneori, el este numit «pasul»):
>>> list(range(5, 10))
[5, 6, 7, 8, 9]
>>> list(range(0, 10, 3))
[0, 3, 6, 9]
>>> list(range(-10, -100, -30))
[-10, -40, -70]
Pentru a itera după indicii unei secvențe, puteți combina range()
și len()
după cum urmează. Exemplul face referire la un cântec de grădiniță american:
>>> a = ['Maria', 'avea', 'un', 'mieluț']
>>> for i in range(len(a)):
... print(i, a[i])
...
0 Maria
1 avea
2 un
3 mieluț
Pe de altă parte, în majoritatea unor asemenea cazuri, este convenabil să utilizați funcția enumerate()
, a se vedea Tehnici de iterare.
Ceva neobișnuit se va petrece atunci când printați o plajă de valori de-a dreptul:
>>> range(10)
range(0, 10)
În multe privințe, obiectul returnat de range()
se comportă de parcă ar fi o listă, însă el nu este o listă. Este doar un obiect care întoarce itemii consecutivi ai secvenței care vă interesează atunci când iterați după itemii acesteia, însă el nu construiește cu adevărat o listă și prin aceasta economisește spațiu.
Spunem despre un asemenea obiect că este iterable, adică potrivit ca țintă pentru funcțiile și constructele care se așteaptă la ceva din care să extragă itemi succesivi până ce rezervorul acestor itemi se va goli. Am văzut deja că instrucțiunea for
este un asemenea construct, în timp ce o funcție care poate avea un iterabil drept argument este sum()
:
>>> sum(range(4)) # 0 + 1 + 2 + 3
6
Vom vedea ulterior și alte funcții care întorc iterabili și care primesc iterabili ca argumente. În capitolul Structuri de date vom discuta mai în detaliu despre list()
.
4.4. Instrucțiunile break
și continue
¶
Instrucțiunea break
stopează cea mai din interior instrucțiune de ciclare for
sau while
care o include:
>>> for n in range(2, 10):
... for x in range(2, n):
... if n % x == 0:
... print(f"{n} este egal cu {x} * {n//x}")
... break
...
4 este egal cu 2 * 2
6 este egal cu 2 * 3
8 este egal cu 2 * 4
9 este egal cu 3 * 3
Instrucțiunea continue
trece la următoarea iterație a ciclului:
>>> for num in range(2, 10):
... if num % 2 == 0:
... print(f"Găsit un număr par {num}")
... continue
... print(f"Găsit un număr impar {num}")
...
Găsit un număr par 2
Găsit un număr impar 3
Găsit un număr par 4
Găsit un număr impar 5
Găsit un număr par 6
Găsit un număr impar 7
Găsit un număr par 8
Găsit un număr impar 9
4.5. Clauzele else
ale ciclurilor¶
Într-un ciclu for
sau while
, instrucțiunea break
poate fi utilizată în tandem cu clauza else
. Astfel, dacă ciclul se încheie fără să fi fost executat break
-ul, atunci va fi executată clauza else
.
Într-un ciclu for
, clauza else
se execută după ce bucla își încheie iterația finală, adică dacă nu a intervenit nicio stopare pe parcurs.
Într-un ciclu while
, clauza se va executa după ce condiția buclei va deveni falsă.
Pentru ambele tipuri de ciclare, clauza else
nu se va executa dacă ciclul în cauză s-a terminat cu un break
. Desigur, și alte cazuri de oprire timpurie a ciclării, precum un return
sau lansarea (de la englezescul to raise) unei excepții, vor anula execuția clauzei else
.
Toate acestea se exemplifică în următorul ciclu for
, cu care căutăm numere prime:
>>> for n in range(2, 10):
... for x in range(2, n):
... if n % x == 0:
... print(n, 'este egal cu', x, '*', n//x)
... break
... else:
... # cade până aici fără să fi găsit vreun divizor
... print(n, 'este număr prim')
...
2 este număr prim
3 este număr prim
4 este egal cu 2 * 2
5 este număr prim
6 este egal cu 2 * 3
7 este număr prim
8 este egal cu 2 * 4
9 este egal cu 3 * 3
(Da, acesta este codul corect. Uitați-vă îndeaproape: clauza else
ține de bucla for
și nu de instrucțiunea if
.)
Este bine, atunci când vă gândiți la clauza else, să o socotiți ca făcând pereche cu if
-ul din buclă. Pe măsură ce bucla este executată, ea va rula o secvență de tipul if/if/if/else. Acest if
este în interiorul buclei și va fi întâlnit de un număr oarecare de ori. Dacă condiția asociată lui va avea valoarea logică adevărat la un moment dat, atunci se va produce un break
. În schimb, dacă valoarea logică a condiției nu va fi niciodată adevărat, atunci se va executa clauza else
din afara buclei.
Atunci când este folosită cu o instrucțiune de ciclare, clauza else
seamănă mai degrabă cu clauza else
a unei instrucțiuni try
decât cu cea a unor instrucțiuni if
: clauza else
a unei instrucțiuni try
va rula numai atunci când nu survine nicio excepție, pe când clauza else
a unei bucle va rula atunci când nu intervine niciun break
. Pentru detalii privind instrucțiunea try
și excepțiile, vedeți Tratarea excepțiilor.
4.6. Instrucțiuni pass
¶
Instrucțiunea pass
nu realizează nimic. Ea poate fi folosită atunci când sintaxa limbajului cere introducerea unei instrucțiuni dar programul propriu-zis nu are nimic de executat. De exemplu:
>>> while True:
... pass # Ocupat-se așteaptă o întrerupere de la tastatură (Ctrl+C)
...
Se folosește în mod uzual la crearea unor clase minimale:
>>> class ClasaMeaVidă:
... pass
...
Altă întrebuințare pentru pass
este ca înlocuitor al corpului unei funcții sau al celui al unei instrucțiuni de selecție atunci când lucrați la un program nou, permițându-vă să gândiți programul la nivel abstract. Instrucțiunea pass
este ignorată în mod silențios:
>>> def initlog(*args):
... pass # Nu uitați să o implementați!
...
4.7. Instrucțiuni match
¶
O instrucțiune match
preia o expresie și îi compară valoarea cu modelele date de blocuri case de cod succesive. Aceasta o face să semene, la prima vedere, cu o instrucțiune switch din C, Java sau JavaScript (și din multe alte limbaje), doar că ea este mai degrabă similară cu potrivirea după model (de la englezescul pattern matching) din limbaje precum Rust ori Haskell. Va fi executat numai primul model care se potrivește și tot atunci vor putea fi salvate în variabile diverse componente (cum ar fi itemi de secvență ori atribute de obiect) ale valorii expresiei.
În cea mai simplă formă, ea compară valoarea unei mărimi cu valorile uneia sau mai multor date literale. În exemplul care urmează este introdus un cod de răspuns HTTP (418) folosit ca păcăleală de întâi aprilie:
def http_error(stare):
match stare:
case 400:
return "Cerere incorectă"
case 404:
return "Nu a fost găsită"
case 418:
return "Eu sunt un ceainic"
case _:
return "S-a întâmplat ceva cu conexiunea internet"
Observați ultimul bloc: „numele de variabilă” _
acționează ca un caracter de înlocuire (de la englezescul wildcard) care se va potrivi cu toate valorile. Dacă niciunul dintre case-uri nu se potrivește, niciuna din ramurile de cod nu se va executa.
Puteți combina mai mulți literali într-un singur model folosind |
(„sau”):
case 401 | 403 | 404:
return "Nepermis"
Modelele pot semăna cu atribuirile cu despachetare (de la englezescul unpacking assignment), putând fi folosite la legarea variabilelor:
# punctul este un tuplu (x, y)
match punctul:
case (0, 0):
print("Originea")
case (0, y):
print(f"Y={y}")
case (x, 0):
print(f"X={x}")
case (x, y):
print(f"X={x}, Y={y}")
case _:
raise ValueError("Nu este punct")
Studiați exemplul cu atenție! Primul model are doi literali, și poate fi considerat ca o extensie a modelului literal prezentat înainte. Următoarele două modele, în schimb, combină o dată literală cu o variabilă, iar de variabilă se leagă o valoare din mărime (punctul
). Cel de-al patrulea model captează două valori, ceea ce îl face conceptual echivalent atribuirii cu despachetare (x, y) = punctul
.
Dacă folosiți clase pentru a vă structura datele în program, atunci veți putea utiliza numele clasei urmat de o listă de argumente asemănătoare celei a unui constructor, doar că având proprietatea de a salva atribute în variabile:
class Punctul:
def __init__(self, x, y):
self.x = x
self.y = y
def unde_este(punctul):
match punctul:
case Punctul(x=0, y=0):
print("Originea")
case Punctul(x=0, y=y):
print(f"Y={y}")
case Punctul(x=x, y=0):
print(f"X={x}")
case Punctul():
print("Altundeva")
case _:
print("Nu e punct")
Puteți utiliza argumente poziționale la unele din clasele predefinite, cele care oferă o ordonare a atributelor lor (precum clasele-de-date). De asemenea, puteți defini poziții specifice pentru atributele din modele prin setarea atributului special __match_args__
pentru clasele dumneavoastră. Dacă acesta este setat la („x”, „y”), atunci următoarele modele sunt cu toatele echivalente (și toate leagă atributul y
de variabila var
):
Punctul(1, var)
Punctul(1, y=var)
Punctul(x=1, y=var)
Punctul(y=var, x=1)
Atunci când citim un model este recomandat să-l privim ca pe forma extinsă a ceea ce se pune la stânga unei atribuiri, respectiv să înțelegem ce variabilă va fi setată la ce valoare. O instrucțiune match poate face atribuiri numai pentru numele de sine stătătoare (precum var
-ul de deasupra). Numelor-cu-punct (de la englezescul dotted name; ca, de exemplu, foo.bar
, o expresie intraductibilă, vedeți foobar), numelor de atribute (x=
și y=
de mai sus) ori numelor de clase (pe care le recunoaștem după „(…)”-ul așezat după ele, ca în cazul lui Punctul
de deasupra) nu li se fac niciodată atribuiri.
Modelele pot fi imbricate de câte ori dorim. De exemplu, dacă avem o listă scurtă de Puncte, căreia i-am adăugat __match_args__
, îi putem aplica un match astfel:
class Punctul:
__match_args__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
match puncte:
case []:
print("Niciun punct")
case [Punctul(0, 0)]:
print("Originea")
case [Punctul(x, y)]:
print(f"Un singur punct {x}, {y}")
case [Punctul(0, y1), Punctul(0, y2)]:
print(f"Două puncte pe axa Y la {y1}, {y2}")
case _:
print("Altceva")
Îi putem adăuga o clauză if
, numită „gardă” (de la englezescul guard), unui model. Dacă valoarea logică a gărzii este fals, atunci match
-ul trece la următorul bloc case. Captarea valorilor, trebuie remarcat, se va petrece înainte de evaluarea gărzii:
match punct:
case Punctul(x, y) if x == y:
print(f"Y=X la {x}")
case Punctul(x, y):
print(f"Nu e pe diagonală")
Alte caracteristici-cheie ale acestei instrucțiuni:
În calitate de atribuiri cu despachetare, tuplurile și modelele de liste au aceeași semnificație și reușesc să se potrivească unor secvențe arbitrare. O excepție importantă este aceea că ele nu fac potriviri pentru iteratori sau șiruri de caractere.
Modelele de secvențe suportă despachetări extinse:
[x, y, *restul]
și(x, y, *restul)
se comportă similar cu atribuirile cu despachetare. Numele de după*
poate fi inclusiv_
, astfel că(x, y, *_)
se va potrivi unei secvențe de cel puțin doi itemi fără a mai lega itemii rămași.Modele de mapare:
{"lățime_de_bandă": b, "latență": l}
capturează valorile lui"lățime_de_bandă"
și"latență"
dintr-un dicționar. Spre deosebire de cazul modelelor de secvențe, cheile suplimentare vor fi ignorate. Este suportată și o despachetare de tipul**restul
. (Dat fiind că despachetarea**_
ar fi redundantă, ea nu este permisă.)Submodelele pot fi capturate folosind cuvântul-cheie
as
:case (Punctul(x1, y1), Punctul(x2, y2) as p2): ...
va captura cel de-al doilea element al inputului ca
p2
(numai dacă inputul este o secvență de două puncte)Majoritatea datelor literale sunt comparate folosind egalitatea, totuși valorile unice (de la englezescul singleton)
True
,False
șiNone
se compară folosind identitatea.Modelele pot folosi constante cu nume. Acestea trebuie să fie nume-cu-punct pentru a se evita interpretarea lor drept variabile de captare:
from enum import Enum class Culoare(Enum): ROȘU = 'roșu' VERDE = 'verde' ALBASTRU = 'albastru' culoare = Culoare( \ input("Alegeți între 'roșu', 'albastru' sau 'verde': ")) match culoare: case Culoare.ROȘU: print("Văd roșu!") case Culoare.VERDE: print("Iarba este verde") case Culoare.ALBASTRU: print("Cântă-mi de inimă albastră :(")
Pentru explicații detaliate și alte exemple, puteți răsfoi PEP 636 care este redactat sub formă de tutorial.
4.8. Definirea funcțiilor¶
Puteți construi o funcție care să scrie toți termenii șirului lui Fibonacci care sunt mici decât o valoare dată:
>>> def fib(n): # scriem termenii șirului lui Fibonacci mai mici ca n
... """Afișez termenii șirului lui Fibonacci mai mici ca n."""
... a, b = 0, 1
... while a < n:
... print(a, end=' ')
... a, b = b, a+b
... print()
...
>>> # Acum apelăm funcția proaspăt definită:
>>> fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
Cuvântul-cheie def
introduce definiția unei funcții. El trebuie urmat de numele funcției și de lista cuprinsă între paranteze a parametrilor formali ai funcției. Instrucțiunile care alcătuiesc corpul funcției vor începe de pe linia următoare, și vor trebui scrise ca alineate.
Prima instrucțiune din corpul funcției poate fi o dată literală, opțională, sub forma unui șir de caractere; acest literal de tip șir de caractere este șirul de caractere al documentației funcției respective, așa-numitul docstring. (Mai multe despre docstring-uri se pot afla în secțiunea Șiruri de documentație.) Există unelte informatice care folosesc docstring-urile pentru a produce în mod automat documentație, atât online cât și tipărită, ori pentru a-i permite utilizatorului să răsfoiască, interactiv, codul-sursă; este o bună practică să inserați docstring-uri în codul pe care îl construiți, așadar, făceți-vă un obicei din aceasta.
La execuția unei funcții se construiește o nouă tabelă de simboluri ce va fi folosită pentru variabilele locale ale funcției. Mai precis, toate atribuirile făcute unor variabile în corpul unei funcții stochează valorile respective în tabela de simboluri locală; în ce le privește, referințele la variabile vor căuta mai întâi în tabela de simboluri locală, apoi în tabelele de simboluri locale ale funcțiilor în care este imbricată funcția, după care în tabela de simboluri globală și, în final, în tabela de nume predefinite. Astfel, nici variabilelor globale și nici variabilelor din funcțiile în care este imbricată funcția în cauză nu li se pot atribui în mod direct valori prin codul funcției (cu excepția, pentru variabilele globale, celor numite într-o instrucțiune global
ori, pentru variabilele din funcțiile în care este imbricată funcția, a celor numite într-o instrucțiune nonlocal
), chiar dacă pot exista referințe la ele.
Parametrii actuali (argumentele) dintr-un apel de funcție sunt introduși în tabela de simboluri locală a funcției apelate la apelul acesteia; astfel, argumentele sunt transmise prin valoare (unde valoarea este întotdeauna referința la un obiect, niciodată valoarea obiectului). [1] Când o funcție apelează altă funcție, ori se apelează recursiv pe ea-însăși, o tabelă de simboluri locale nouă va fi creată la apel.
Definiția unei funcții asociază numele funcției cu obiectul-funcție în tabela de simboluri curentă. Interpretorul recunoaște obiectul către care țintește numele ca fiind o funcție definită-de-utilizator. Și alte nume pot ținti către același obiect-funcție și pot fi, de aceea, utilizate la accesarea funcției:
>>> fib
<function fib at 10042ed0>
>>> f = fib
>>> f(100)
0 1 1 2 3 5 8 13 21 34 55 89
Dacă sunteți obișnuit cu alte limbaje de programare, atunci este posibil să considerați că fib
nu poate fi funcție, ci procedură, deoarece ea nu returnează nicio valoare. În fapt, chiar și funcțiile care nu au nicio instrucțiune return
returnează o valoare, numai că valoarea respectivă nu înseamnă mai nimic. Această valoare se numește None
(adică Nimic
, un nume predefinit). Afișarea valorii None
este suprimată, în mod obișnuit, de către interpretor în cazul în care ea ar fi singura valoare afișată. O puteți vedea, dacă țineți neapărat, folosind print()
:
>>> fib(0)
>>> print(fib(0))
None
Este mai ușor să scrieți o funcție care returnează o listă de numere din șirul lui Fibonaci în loc de una care să afișeze aceste numere:
>>> def fib2(n): # întoarce șirul lui Fibonacci de până la n
... """Întoarce o listă conținând termenii din șirul lui
... Fibonacci mai mici decât n."""
... rezultat = []
... a, b = 0, 1
... while a < n:
... rezultat.append(a) # vezi mai jos
... a, b = b, a+b
... return rezultat
...
>>> f100 = fib2(100) # apelul funcției
>>> f100 # afișarea rezultatului
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
Acest exemplu, ca și până acum, probează câteva caracteristici noi ale Python-ului:
Instrucțiunea
return
întoarce o valoare a funcției.return
neurmat de vreo expresie ca argument va întoarceNone
. De asemeni, la terminarea corpului de instrucțiuni al unei funcții se întoarceNone
.Intrucțiunea
rezultat.append(a)
apelează o metodă a obiectului listărezultat
. Metoda este acea funcție care îi «aparține» unui obiect și care poartă numele deobiect.nume_de_metodă
, în careobiect
desemnează un obiect oarecare (poate fi inclusiv o expresie) iarnume_de_metodă
este numele unei metode definite de către tipul obiectului. Unor tipuri diferite le corespund metode diferite. Metodele unor tipuri diferite pot avea același nume fără a da naștere la ambiguități. (Aveți posibilitatea să vă definiți propriile tipuri de obiecte și propriile metode, cu ajutorul claselor, vezi Classes.) Metodaappend()
, folosită în exemplu, se definește pentru obiectele listă; ea adaugă un element nou la sfârșitul listei. În exemplul de față, această adăugare este echivalentă curezultat = rezultat + [a]
, doar că se realizează mai eficient.
4.9. Mai multe despre definirea funcțiilor¶
Este posibil să definim funcții care să aibă număr variabil de argumente. Aceasta poate fi realizată în trei feluri, care pot fi combinate.
4.9.1. Valori implicite pentru argumente¶
Modalitatea cea mai utilă este să specificăm valori prestabilite pentru unul sau mai multe argumente. Se creează o funcție ce va putea fi apelată cu mai puține argumente decât au fost folosite la definirea ei. Cum ar fi:
def cere_permisiunea(promptul, încercări=4, \
atenționare='Încercați din nou, vă rog!'):
while True:
răspuns = input(promptul)
if răspuns in {'d', 'mda', 'da'}:
return True
if răspuns in {'n', 'nu', 'nț', 'nici pomeneală'}:
return False
încercări = încercări - 1
if încercări < 0:
raise ValueError('utilizatorul răspunde greșit')
print(atenționare)
Această funcție poate fi apelată în mai multe moduri:
precizând numai argumentul obligatoriu:
cere_permisiunea('Chiar vreți să ieșiți?')
oferind și unul dintre argumentele opționale:
cere_permisiunea('De acord cu suprascrierea fișierului?', 2)
ori introducând absolut toate argumentele:
cere_permisiunea('De acord cu suprascrierea fișierului?', 2, 'Haideți, vă rog, numai da sau nu!')
Exemplul de față introduce și cuvântul-cheie in
. El verifică dacă o secvență conține sau nu o anumită valoare.
Valorile prestabilite sunt evaluate chiar acolo unde se definește funcția în domeniul de valabilitate (de la englezescul scope) al definiției, așa că:
i = 5
def f(arg=i):
print(arg)
i = 6
f()
va afișa 5
.
Avertizare importantă: Valoarea prestabilită se evaluează o singură dată. Acest fapt contează atunci când valoarea prestabilită este un obiect mutabil, precum o listă, un dicționar ori instanțe ale majorității claselor. De exemplu, funcția următoare pune laolaltă argumentele transmise ei în apeluri succesive:
def f(a, L=[]):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
Se va afișa
[1]
[1, 2]
[1, 2, 3]
Dacă nu doriți ca valoarea prestabilită să se păstreze între apeluri succesive, atunci puteți rescrie funcția după cum urmează:
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
4.9.2. Argumentele cuvânt-cheie¶
Funcțiile mai pot fi apelate și folosind argumente cuvânt-cheie de forma arg_cvc=valoare
. De exemplu, funcția următoare, care face referire la un scheci Monty Python:
def papagal(voltaj, stare='țeapăn', acțiune='fâl-fâl', \
tip='Albastrul de Norvegia'):
print("-- Papagalul acesta n-are cum să", acțiune, end=' ')
print("dacă băgați", voltaj, "de volți în el.")
print("-- Ce mai penaj, ", tip)
print("-- Doar că-i", stare, "!")
are un argument obligatoriu (voltaj
) și trei argumente opționale (stare
, acțiune
și tip
). Funcția poate fi apelată în oricare din modurile care urmează:
papagal(1000) # 1 argument pozițional
papagal(voltaj=1000) # 1 argument cuvânt-cheie
papagal(voltaj=1000000, acțiune='FÂÂÂL-FÂÂÂL') # 2 argumente cuvânt-cheie
papagal(acțiune='FÂÂÂL-FÂÂÂL', voltaj=1000000) # 2 argumente cuvânt-cheie
papagal('un milion', 'lipsit de viață', 'sară') # 3 argumente poziționale
papagal('o mie', stare='pământ de flori') # 1 pozițional, 1 cuvânt-cheie
în schimb, următoarele apeluri vor fi invalidate:
papagal() # lipsește argumentul obligatoriu
papagal(voltaj=5.0, 'mort') # argument care nu e cuvânt-cheie
# după un argument cuvânt-cheie
papagal(110, voltaj=220) # valoare duplicată a unui argument
papagal(actor='John Cleese') # argument cuvânt-cheie necunoscut
Într-un apel de funcție, argumentele cuvânt-cheie le urmează argumentelor poziționale. Orice argument cuvânt-cheie trebuie să-i corespundă unuia din argumentele acceptate de funcție (de exemplu, actor
nu este un argument valid pentru funcția papagal
), ordinea acestor argumente nefiind importantă. Cerința privește și argumentele neopționale (astfel, papagal(voltaj=1000)
este o exprimare validă). Niciunui argument nu îi va fi atribuită valoarea de mai multe ori. Iată un exemplu de formulare neacceptată din cauza acestei restricții:
>>> def funcție(a):
... pass
...
>>> funcție(0, a=0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: funcție() got multiple values for argument 'a'
Atunci când parametrul formal final este de forma **nume
, lui i se va atribui un dicționar (vezi Mapping Types — dict) care să cuprindă toate argumentele cuvânt-cheie cu excepția celor corespunzând unui parametru formal. Acest fapt poate fi combinat cu un parametru formal exprimat ca *nume
(care va fi descris în subsecțiunea următoare) și căruia i se atribuie un tuplu ce conține toate argumentele poziționale de după lista parametrilor formali (*nume
trebuie să apară înaintea lui **nume
.) De exemplu, să definim o funcție astfel:
def magazin_de_brânzeturi(fel, *argumente, **cuvinte_cheie):
print("-- Aveți cumva", fel, "?")
print("-- Scuze, am rămas fără", fel)
for arg in argumente:
print(arg)
print("-" * 40)
for cvc in cuvinte_cheie:
print(cvc, ":", cuvinte_cheie[cvc])
Funcția poate fi apelată astfel:
magazin_de_brânzeturi("Limburger", "E tare apoasă, domnule.",
"Realmente, e tare, TARE apoasă, domnule.",
proprietar="Michael Palin",
client="John Cleese",
scheci="Cheese Shop Sketch")
și, firește, va afișa:
-- Aveți cumva Limburger ?
-- Scuze, am rămas fără Limburger
E tare apoasă, domnule.
Realmente, e tare, TARE apoasă, domnule.
----------------------------------------
proprietar : Michael Palin
client : John Cleese
scheci : Cheese Shop Sketch
Remarcați că ordinea în care argumentele cuvânt-cheie sunt afișate este garantată ca urmând ordinea în care aceste argumente cuvânt-cheie au fost inserate în apelul funcției.
4.9.3. Parametri speciali¶
Tipic, argumentele i se transmit unei funcții în Python fie prin poziție fie prin cuvinte-cheie date în mod explicit. Din motive de lizibilitate și performanță, modurile de transmitere a argumentelor au fost restrânse astfel încât unui dezvoltator de cod să-i fie suficient să se uite la definiția unei funcții pentru a ști dacă un argument trebuie transmis numai prin poziție, prin poziție sau prin cuvânt-cheie ori numai prin cuvânt-cheie.
Definiția unei funcții poate arăta ca aici:
def f(poz1, poz2, /, poz_sau_cvc, *, cvc1, cvc2):
---------- ----------- ----------
| | |
| Pozițional |
| sau |
| cuvânt-cheie |
| - Numai cuvânt-cheie
-- Numai pozițional
unde /
și *
sunt opționale. Dacă le folosiți, atunci aceste simboluri vor indica tipul de parametru prin chiar modul cum le sunt transmise argumentele funcției: numai pozițional, pozițional-sau-cuvânt-cheie ori numai cuvânt-cheie. Despre parametrii cuvânt-cheie spunem că sunt și parametri-cu-nume.
4.9.3.1. Argumente pozițional-sau-cuvânt-cheie¶
Dacă nici /
, nici *
nu sunt prezente în definiția funcției, atunci argumentele îi pot fi transmise acesteia atât prin poziție cât și prin cuvinte-cheie.
4.9.3.2. Parametri numai poziționali¶
Intrând puțin în detalii, să observăm că este posibil să marcăm anumiți parametri cu numai pozițional. Atunci când aceștia sunt numai poziționali, ordinea parametrilor contează și parametrii nu pot fi transmiși prin cuvinte-cheie. Parametrii numai poziționali sunt plasați înaintea lui /
(bara oblică). /
-ul este folosit pentru a separa din punct de vedere logic parametrii numai poziționali de restul parametrilor. Dacă nu există niciun /
în definiția funcției, atunci nu există nici parametri numai poziționali ai acesteia.
Parametrii care îi urmează lui /
pot fi poziționali-sau-cuvinte-cheie ori numai cuvinte-cheie.
4.9.3.3. Argumente numai cuvinte-cheie¶
Ca să marcăm parametrii cu numai cuvânt-cheie, adică să indicăm că acești parametri trebuie transmiși prin argumente cuvinte-cheie, vom plasa un *
în lista argumentelor, chiar înainte de primul parametru numai cuvânt-cheie.
4.9.3.4. Exemple de funcții¶
Să luăm în considerare următoarele definiții de funcții demonstrative, cu grijă la marcajele /
și *
:
>>> def argumente_uzuale(arg):
... print(arg)
...
>>> def argumente_numai_poziționale(arg, /):
... print(arg)
...
>>> def argumente_numai_cuvinte_cheie(*, arg):
... print(arg)
...
>>> def exemplu_de_combinații(numai_poz, /, tipice, *, numai_cvc):
... print(numai_poz, tipice, numai_cvc)
Prima definiție de funcție, argumente_uzuale
, cu forma cea mai des întâlnită, nu impune nicio restricție asupra felului în care o apelăm pe aceasta iar argumentele funcției pot fi transmise atât prin poziție cât și prin cuvânte-cheie:
>>> argumente_uzuale(2)
2
>>> argumente_uzuale(arg=2)
2
Cea de-a doua funcție, argumente_numai_poziționale
, este restrânsă la utilizarea doar a parametrilor numai poziționali deoarece în definiția funcției apare un /
:
>>> argumente_numai_poziționale(1)
1
>>> argumente_numai_poziționale(arg=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: argumente_numai_poziționale() got some positional-only arguments passed as keyword arguments: 'arg'
A treia funcție, argumente_numai_cuvinte_cheie
, permite doar argumente cuvinte-cheie, după cum ne indică acel *
din definiția funcției:
>>> argumente_numai_cuvinte_cheie(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: argumente_numai_cuvinte_cheie() takes 0 positional arguments but 1 was given
>>> argumente_numai_cuvinte_cheie(arg=3)
3
Iar ultimul exemplu întrebuințează toate cele trei convenții de apel în aceeași definiție de funcție:
>>> exemplu_de_combinații(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: exemplu_de_combinații() takes 2 positional arguments but 3 were given
>>> exemplu_de_combinații(1, 2, numai_cvc=3)
1 2 3
>>> exemplu_de_combinații(1, tipice=2, numai_cvc=3)
1 2 3
>>> exemplu_de_combinații(numai_poz=1, tipice=2, numai_cvc=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: exemplu_de_combinații() got some positional-only arguments passed as keyword arguments: 'pos_only'
În sfârșit, să considerăm o definiție de funcție care poate provoca o coliziune de nume între argumentul pozițional nume
și argumentul **cuvinte-cheie
atunci când acesta din urmă va include nume
pe post de cheie:
def foo(nume, **cuvinte_cheie):
return 'nume' in cuvinte_cheie
Nu putem realiza niciun apel al funcției care să întoarcă True
deoarece cuvântul-cheie 'nume'
se va lega întotdeauna la primul parametru. De exemplu:
>>> foo(1, **{'nume': 2})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() got multiple values for argument 'nume'
>>>
Însă, folosind /
(plasat după argumentele numai poziționale), un asemenea apel este posibil deoarece acum putem distinge între nume
ca parametru numai pozițional și cheia 'nume'
a unui argument cuvânt-cheie:
>>> def foo(nume, /, **cuvinte_cheie):
... return 'nume' in cuvinte_cheie
...
>>> foo(1, **{'nume': 2})
True
Altfel spus, numele parametrilor numai poziționali vor putea fi folosite în **cuvinte_cheie
fără nicio ambiguitate.
4.9.3.5. De reținut¶
Modul de întrebuințare determină ce parametri să folosim la definiția unei funcții:
def f(poz1, poz2, /, poz_sau_cvc, *, cvc1, cvc2):
Recomandări:
Folosiți argumente numai poziționale dacă doriți ca numele parametrilor să nu le fie accesibile utilizatorului. Această întrebuințare ne este de folos atunci când numele parametrilor nu au nicio semnificație pentru program, atunci când dorim să impunem o anumită ordine a argumentelor la apelul funcției sau atunci când este nevoie să utilizăm câțiva parametri numai poziționali alături de niște cuvinte cheie arbitrare.
Folosiți argumente numai cuvinte-cheie atunci când numele au semnificație pentru program iar definiția funcției se va înțelege mai ușor dacă conține nume ori atunci când doriți să-i împiedicați pe utilizatori să se bazeze pe pozițiile argumentelor ce trebuie transmise.
În cazul unui API, folosiți argumente numai poziționale pentru a evita funcționarea defectuoasă a viitoarelor transformări ale API-ului atunci când numele vreunui parametru va fi modificat.
4.9.4. Liste arbitrare de argumente¶
La final, cea mai puțin utilizată variantă de definiție este cea în care precizăm că o funcție poate fi apelată cu un număr variabil de argumente. Aceste argumente vor fi încadrate într-un tuplu (vezi Tupluri și secvențe).
def înscrie_mai_mulți_itemi(fișier, despărțitor, *argumente):
fișier.write(despărțitor.join(argumente))
După cum e de așteptat, aceste argumente variadice vor fi așezate la sfârșitul listei de parametri formali, dat fiind că ele vor cuprinde tot restul datelor de intrare care trebuie transmise funcției. Acei parametri formali care sunt poziționați după parametrul *argumente
sunt argumente «numai cuvânt-cheie», ceea ce înseamnă că vor putea fi folosiți doar pe post de cuvinte-cheie și nu ca argumente poziționale.
>>> def concatenează(*argumente, despărțitor="/"):
... return despărțitor.join(argumente)
...
>>> concatenează("Pământ", "Marte", "Venus")
'Pământ/Marte/Venus'
>>> concatenează("Pământ", "Marte", "Venus", despărțitor=".")
'Pământ.Marte.Venus'
4.9.5. Despachetarea listelor de argumente¶
Situația inversă va fi întâlnită atunci când argumentele care ne interesează ne vor fi date sub formă fie de listă, fie de tuplu și va trebui să le despachetăm pentru a fi folosite într-un apel de funcție care necesită argumente poziționale introduse separat. De exemplu, funcția predefinită range()
necesită argumente start și stop transmise separat. Dacă acestea nu sunt disponibile separat, atunci vom înscrie în apelul funcției operatorul *
pentru a despacheta argumentele fie din listă fie din tuplu:
>>> list(range(3, 6)) # apel tipic, cu argumente date separat
[3, 4, 5]
>>> argumente = [3, 6]
>>> list(range(*argumente)) # apel cu argumente despachetate dintr-o listă
[3, 4, 5]
La fel, dicționarele de date pot transmite argumente cuvinte-cheie cu ajutorul operatorului **
:
>>> def papagal(voltaj, stare='țeapăn', acțiune='fâl-fâl'):
... print("-- Papagalul acesta n-are cum să", acțiune, end=' ')
... print("dacă băgați", voltaj, "de volți în el.", end=' ')
... print("Zău că-i", stare, "!")
...
>>> d = {"voltaj": "patru milioane", "stare": "al naibii de dus", "acțiune": "FÂL-FÂL"}
>>> papagal(**d)
-- Papagalul acesta n-are cum să FÂL-FÂL dacă băgați patru milioane de volți în el. Zău că-i al naibii de dus !
4.9.6. Expresii lambda¶
Funcții anonime, cu cod de dimensiuni reduse, pot fi create folosind cuvântul-cheie lambda
. Funcția care urmează va returna suma celor două argumente ale ei: lambda a, b: a+b
. Funcțiile lambda pot fi utilizate oriunde se cer obiecte funcție. Ele sunt restrânse dpdv. sintactic la o singură expresie. Dpdv. semantic, ele sunt doar zahăr sintactic pudrat peste o definiție obișnuită de funcție. Aidoma definițiilor imbricate de funcții, funcțiile lambda pot face referire la variabile dintr-un domeniu de valabilitate care include definițiile funcțiilor lambda în cauză:
>>> def construiește_un_incrementor(n):
... return lambda x: x + n
...
>>> f = construiește_un_incrementor(42)
>>> f(0)
42
>>> f(1)
43
Exemplul de deasupra întrebuințează o expresie lambda pentru a returna o funcție. Altă utilizare este dată de transmiterea unei funcții cu cod redus pe post de argument. Astfel, list.sort()
folosește ca valoare a cheii key o funcție de sortare iar aceasta poate fi inclusiv o funcție lambda:
>>> perechi = [(1, 'unu'), (2, 'doi'), (3, 'trei'), (4, 'patru')]
>>> perechi.sort(key=lambda pereche: pereche[1])
>>> perechi
[(2, 'doi'), (4, 'patru'), (3, 'trei'), (1, 'unu')]
4.9.7. Șiruri de documentație¶
Iată câteva convenții privind conținutul și formatul șirurilor de documentație (în englezește, ca jargon, docstring).
Primul rând trebuie să fie întotdeauna un sumar restrâns, concis al scopului pe care îl are obiectul în cauză. Pentru concizie, el nu trebuie să menționeze nici numele și nici tipul obiectului, dat fiind că acestea vor putea fi aflate prin alte mijloace (excepție face cazul când numele este chiar verbul care descrie modul de operare al funcției). Ar fi bine ca acest rând să înceapă cu o majusculă și să se încheie cu punct.
Dacă șirul de documentație conține mai multe rânduri, atunci cel de-al doilea rând este bine să fie gol, separând vizual sumarul de restul descrierii. Rândurile care urmează, în șirul de documentație, ar trebui grupate într-unul sau mai multe paragrafe referitoare la convențiile de apel ale obiectului, la diversele complicații date de utilizarea sa șamd.
Parserul Python-ului nu elimină indentația dintr-o dată literală de tip șir de caractere care se întinde pe mai multe rânduri de program Python, așa că uneltele informatice care vor procesa documentația trebuie să elimine ele-însele indentația, dacă acest lucru este dorit. Eliminarea se pregătește folosind următoarea convenție. Primul rând care nu este gol de după primul rând al datei literale va determina lățimea indentației pentru întregul șir de documentație. (Nu putem întrebuința în această privință primul rând deoarece el este adiacent, în general, ghilimelor din deschiderea șirului astfel că indentația acestui rând nu se observă în cuprinsul șirului literal.) Un spațiu gol „echivalent” cu lățimea acestei indentații va fi eliminat de la începutul tuturor rândurilor datei literale. Rânduri care să fie indentate cu o indentație mai scurtă nu ar trebui să existe în șirul de documentație, dar dacă există, atunci întregul spațiu gol de la începutul lor este bine să fie eliminat. Lățimea spațiului gol trebuie testată folosind expansiunea tabulatorului (de 8 spații, în mod normal).
Iată un exemplu de docstring pe mai multe rânduri:
>>> def funcția_mea():
... """Nu face nimica, dar precizează asta.
...
... Zău, realmente, nu face nimica.
... """
... pass
...
>>> print(funcția_mea.__doc__)
Nu face nimica, dar precizează asta.
Zău, realmente, nu face nimica.
4.9.8. Adnotarea funcțiilor¶
Adnotarea funcțiilor reprezintă informația opțională, de meta-date, care se referă la tipurile datelor întrebuințate de către funcțiile definite de utilizator (vezi PEP 3107 și PEP 484 pentru mai multe detalii).
Adnotările sunt memorate în atributul __annotations__
al funcției sub formă de dicționar de date și nu au niciun efect asupra celorlalte componente ale funcției. Adnotările parametrilor se marchează prin două puncte așezate după numele parametrului, semnul de punctuație fiind urmat de o expresie a cărei evaluare va da valoarea adnotării. Adnotarea mărimilor returnate de funcție se definește prin ansamblul dat de constanta literală ->
urmată de o expresie de evaluat, ansamblu ce va fi poziționat între lista parametrilor funcției și semnul de punctuație două puncte ce denotă finalul instrucțiunii def
.
>>> def f(șuncă: str, ouă: str = 'ouă') -> str:
... print("Adnotări:", f.__annotations__)
... print("Argumente:", șuncă, ouă)
... return șuncă + ' și ' + ouă
...
>>> f('carne presată')
Adnotări: {'șuncă': <class 'str'>, 'return': <class 'str'>, 'ouă': <class 'str'>}
Argumente: carne presată ouă
'carne presată și ouă'
4.10. Intermezzo: stilul în care scriem cod¶
Acum, când puteți începe să scrieți fragmente mai lungi și mai complexe de programe în Python, este momentul potrivit să discutăm despre stilul de scris cod. Majoritatea limbajelor permite redactarea (ori, mai pe scurt, formatarea) în stiluri diferite; unele sunt mai lizibile ca altele. A ușura citirea de către alții a codului dumneavoastră este întotdeauna o idee bună, iar adoptarea unui stil de scriere prezentabil se dovedește de mare ajutor în acest scop.
În cazul Python-ului, PEP 8 s-a impus ca ghidul stilistic la care a aderat majoritatea proiectelor; acesta promovează un stil de scriere a codului ușor citibil și plăcut ochiului. Orice programator în Python ar trebui să îl citească la un moment dat; iată un extras al celor mai importante elemente:
Folosiți indentări de 4 spații goale, nu apelați la tabulator.
4 spații goale constituie un compromis bun între indentarea îngustă (care permite imbricarea pe mai multe niveluri) și indentarea lată (mai ușor de citit).
Desfășurați rândurile de cod astfel încât ele să nu depășească 79 de caractere.
Aceasta le servește utilizatorilor cu terminale de dimensiuni mici și permite așezarea unul lângă celălalt a mai multor fișiere cu cod pe un ecran mai lat.
Folosiți rânduri goale pentru a separa funcțiile și clasele, precum și blocurile mai mari de cod din corpul funcțiilor.
Atunci când este posibil, plasați comentariile pe rânduri dedicate lor.
Utilizați docstring-uri.
Inserați spații goale în jurul operatorilor și după virgule, dar nu și direct în constructele cu paranteze:
a = f(1, 2) + g(3, 4)
.Denumiți-vă clasele și funcțiile într-un mod sistematic; convenția este de a folosi
ScriereaCămilăCareÎncepeCuMajusculă
pentru clase șilitere_mici_despărțite_de_bara_jos
pentru funcții și metode. Întrebuințați întotdeaunaself
pe post de nume pentru cel dintâi argument al unei metode (vezi A First Look at Classes pentru mai multe informații despre clase și metode).Nu utilizați codificări de caractere sofisticate dacă programul pe care îl scrieți este destinat utilizării în medii internaționale. Codificarea prestabilită a Python-ului, UTF-8, ori chiar și simpla codificare ASCII, funcționează de minune în aproape orice caz.
De asemeni, nu întrebuințați caractere non-ASCII în identificatori dacă există o oricât de neînsemnată șansă ca persoane care nu vă vorbesc limba să aibă de citit ori de întreținut codul pe care îl scrieți.
Note de sunsol