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("Please enter an integer: "))
   Please enter an integer: 42
   >>> if x < 0:
   ...     x = 0
   ...     print('Negative changed to zero')
   ... elif x == 0:
   ...     print('Zero')
   ... elif x == 1:
   ...     print('Single')
   ... else:
   ...     print('More')
   ...
   More

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) :

   >>> # Measure some strings:
   ... words = ['cat', 'window', 'defenestrate']
   >>> for w in words:
   ...     print(w, len(w))
   ...
   cat 3
   window 6
   defenestrate 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:

   # Create a sample collection
   users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}

   # Strategy:  Iterate over a copy
   for user, status in users.copy().items():
       if status == 'inactive':
           del users[user]

   # Strategy:  Create a new collection
   active_users = {}
   for user, status in users.items():
       if status == 'active':
           active_users[user] = status


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 = ['Mary', 'had', 'a', 'little', 'lamb']
   >>> for i in range(len(a)):
   ...     print(i, a[i])
   ...
   0 Mary
   1 had
   2 a
   3 little
   4 lamb

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 *iterabil*, 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. "break" and "continue" Statements, and "else" Clauses on Loops
===================================================================

The "break" statement breaks out of the innermost enclosing "for" or
"while" loop.

A "for" or "while" loop can include an "else" clause.

In a "for" loop, the "else" clause is executed after the loop reaches
its final iteration.

Într-un ciclu "while", clauza se va executa după ce condiția buclei va
deveni falsă.

In either kind of loop, the "else" clause is **not** executed if the
loop was terminated by a "break".

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, 'equals', x, '*', n//x)
   ...             break
   ...     else:
   ...         # loop fell through without finding a factor
   ...         print(n, 'is a prime number')
   ...
   2 is a prime number
   3 is a prime number
   4 equals 2 * 2
   5 is a prime number
   6 equals 2 * 3
   7 is a prime number
   8 equals 2 * 4
   9 equals 3 * 3

(Yes, this is the correct code.  Look closely: the "else" clause
belongs to the "for" loop, **not** the "if" statement.)

When used with a loop, the "else" clause has more in common with the
"else" clause of a "try" statement than it does with that of "if"
statements: a "try" statement's "else" clause runs when no exception
occurs, and a loop's "else" clause runs when no "break" occurs. For
more on the "try" statement and exceptions, see Tratarea excepțiilor.

The "continue" statement, also borrowed from C, continues with the
next iteration of the loop:

   >>> for num in range(2, 10):
   ...     if num % 2 == 0:
   ...         print("Found an even number", num)
   ...         continue
   ...     print("Found an odd number", num)
   ...
   Found an even number 2
   Found an odd number 3
   Found an even number 4
   Found an odd number 5
   Found an even number 6
   Found an odd number 7
   Found an even number 8
   Found an odd number 9


4.5. 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  # Busy-wait for keyboard interrupt (Ctrl+C)
   ...

Se folosește în mod uzual la crearea unor clase minimale:

   >>> class MyEmptyClass:
   ...     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   # Remember to implement this!
   ...


4.6. 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(status):
       match status:
           case 400:
               return "Bad request"
           case 404:
               return "Not found"
           case 418:
               return "I'm a teapot"
           case _:
               return "Something's wrong with the 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 "Not allowed"

Modelele pot semăna cu atribuirile cu despachetare (de la englezescul
*unpacking assignment*), putând fi folosite la legarea variabilelor:

   # point is an (x, y) tuple
   match point:
       case (0, 0):
           print("Origin")
       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("Not a point")

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 Point:
       def __init__(self, x, y):
           self.x = x
           self.y = y

   def where_is(point):
       match point:
           case Point(x=0, y=0):
               print("Origin")
           case Point(x=0, y=y):
               print(f"Y={y}")
           case Point(x=x, y=0):
               print(f"X={x}")
           case Point():
               print("Somewhere else")
           case _:
               print("Not a point")

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"):

   Point(1, var)
   Point(1, y=var)
   Point(x=1, y=var)
   Point(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 Point:
       __match_args__ = ('x', 'y')
       def __init__(self, x, y):
           self.x = x
           self.y = y

   match points:
       case []:
           print("No points")
       case [Point(0, 0)]:
           print("The origin")
       case [Point(x, y)]:
           print(f"Single point {x}, {y}")
       case [Point(0, y1), Point(0, y2)]:
           print(f"Two on the Y axis at {y1}, {y2}")
       case _:
           print("Something else")

Î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 point:
       case Point(x, y) if x == y:
           print(f"Y=X at {x}")
       case Point(x, y):
           print(f"Not on the 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 (Point(x1, y1), Point(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" și "None" 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 Color(Enum):
         RED = 'red'
         GREEN = 'green'
         BLUE = 'blue'

     color = Color(input("Enter your choice of 'red', 'blue' or 'green': "))

     match color:
         case Color.RED:
             print("I see red!")
         case Color.GREEN:
             print("Grass is green")
         case Color.BLUE:
             print("I'm feeling the blues :(")

Pentru explicații detaliate și alte exemple, puteți răsfoi **PEP 636**
care este redactat sub formă de tutorial.


4.7. 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):    # write Fibonacci series up to n
   ...     """Print a Fibonacci series up to n."""
   ...     a, b = 0, 1
   ...     while a < n:
   ...         print(a, end=' ')
   ...         a, b = b, a+b
   ...     print()
   ...
   >>> # Now call the function we just defined:
   ... 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):  # return Fibonacci series up to n
   ...     """Return a list containing the Fibonacci series up to n."""
   ...     result = []
   ...     a, b = 0, 1
   ...     while a < n:
   ...         result.append(a)    # see below
   ...         a, b = b, a+b
   ...     return result
   ...
   >>> f100 = fib2(100)    # call it
   >>> f100                # write the result
   [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 întoarce "None". De asemeni,
  la terminarea corpului de instrucțiuni al unei funcții se întoarce
  "None".

* The statement "result.append(a)" calls a *method* of the list object
  "result".  A method is a function that 'belongs' to an object and is
  named "obj.methodname", where "obj" is some object (this may be an
  expression), and "methodname" is the name of a method that is
  defined by the object's type. Different types define different
  methods.  Methods of different types may have the same name without
  causing ambiguity.  (It is possible to define your own object types
  and methods, using *classes*, see Clase) The method "append()" shown
  in the example is defined for list objects; it adds a new element at
  the end of the list.  In this example it is equivalent to "result =
  result + [a]", but more efficient.


4.8. 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.8.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 ask_ok(prompt, retries=4, reminder='Please try again!'):
       while True:
           ok = input(prompt)
           if ok in ('y', 'ye', 'yes'):
               return True
           if ok in ('n', 'no', 'nop', 'nope'):
               return False
           retries = retries - 1
           if retries < 0:
               raise ValueError('invalid user response')
           print(reminder)

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.8.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 parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
       print("-- This parrot wouldn't", action, end=' ')
       print("if you put", voltage, "volts through it.")
       print("-- Lovely plumage, the", type)
       print("-- It's", state, "!")

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ă:

   parrot(1000)                                          # 1 positional argument
   parrot(voltage=1000)                                  # 1 keyword argument
   parrot(voltage=1000000, action='VOOOOOM')             # 2 keyword arguments
   parrot(action='VOOOOOM', voltage=1000000)             # 2 keyword arguments
   parrot('a million', 'bereft of life', 'jump')         # 3 positional arguments
   parrot('a thousand', state='pushing up the daisies')  # 1 positional, 1 keyword

în schimb, următoarele apeluri vor fi invalidate:

   parrot()                     # required argument missing
   parrot(voltage=5.0, 'dead')  # non-keyword argument after a keyword argument
   parrot(110, voltage=220)     # duplicate value for the same argument
   parrot(actor='John Cleese')  # unknown keyword argument

Î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 function(a):
   ...     pass
   ...
   >>> function(0, a=0)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: function() 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 Tipuri de asociere -- 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 cheeseshop(kind, *arguments, **keywords):
       print("-- Do you have any", kind, "?")
       print("-- I'm sorry, we're all out of", kind)
       for arg in arguments:
           print(arg)
       print("-" * 40)
       for kw in keywords:
           print(kw, ":", keywords[kw])

Funcția poate fi apelată astfel:

   cheeseshop("Limburger", "It's very runny, sir.",
              "It's really very, VERY runny, sir.",
              shopkeeper="Michael Palin",
              client="John Cleese",
              sketch="Cheese Shop Sketch")

și, firește, va afișa:

   -- Do you have any Limburger ?
   -- I'm sorry, we're all out of Limburger
   It's very runny, sir.
   It's really very, VERY runny, sir.
   ----------------------------------------
   shopkeeper : Michael Palin
   client : John Cleese
   sketch : 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.8.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(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
         -----------    ----------     ----------
           |             |                  |
           |        Positional or keyword   |
           |                                - Keyword only
            -- Positional only

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.8.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.8.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.8.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.8.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 standard_arg(arg):
   ...     print(arg)
   ...
   >>> def pos_only_arg(arg, /):
   ...     print(arg)
   ...
   >>> def kwd_only_arg(*, arg):
   ...     print(arg)
   ...
   >>> def combined_example(pos_only, /, standard, *, kwd_only):
   ...     print(pos_only, standard, kwd_only)

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:

   >>> standard_arg(2)
   2

   >>> standard_arg(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 "/":

   >>> pos_only_arg(1)
   1

   >>> pos_only_arg(arg=1)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: pos_only_arg() got some positional-only arguments passed as keyword arguments: 'arg'

The third function "kwd_only_args" only allows keyword arguments as
indicated by a "*" in the function definition:

   >>> kwd_only_arg(3)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given

   >>> kwd_only_arg(arg=3)
   3

Iar ultimul exemplu întrebuințează toate cele trei convenții de apel
în aceeași definiție de funcție:

   >>> combined_example(1, 2, 3)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: combined_example() takes 2 positional arguments but 3 were given

   >>> combined_example(1, 2, kwd_only=3)
   1 2 3

   >>> combined_example(1, standard=2, kwd_only=3)
   1 2 3

   >>> combined_example(pos_only=1, standard=2, kwd_only=3)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: combined_example() 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(name, **kwds):
       return 'name' in kwds

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, **{'name': 2})
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: foo() got multiple values for argument 'name'
   >>>

Î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(name, /, **kwds):
   ...     return 'name' in kwds
   ...
   >>> foo(1, **{'name': 2})
   True

Altfel spus, numele parametrilor numai poziționali vor putea fi
folosite în "**cuvinte_cheie" fără nicio ambiguitate.


4.8.3.5. De reținut
~~~~~~~~~~~~~~~~~~~

Modul de întrebuințare determină ce parametri să folosim la definiția
unei funcții:

   def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):

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.8.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 write_multiple_items(file, separator, *args):
       file.write(separator.join(args))

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 concat(*args, sep="/"):
   ...     return sep.join(args)
   ...
   >>> concat("earth", "mars", "venus")
   'earth/mars/venus'
   >>> concat("earth", "mars", "venus", sep=".")
   'earth.mars.venus'


4.8.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))            # normal call with separate arguments
   [3, 4, 5]
   >>> args = [3, 6]
   >>> list(range(*args))            # call with arguments unpacked from a list
   [3, 4, 5]

La fel, dicționarele de date pot transmite argumente cuvinte-cheie cu
ajutorul operatorului "**":

   >>> def parrot(voltage, state='a stiff', action='voom'):
   ...     print("-- This parrot wouldn't", action, end=' ')
   ...     print("if you put", voltage, "volts through it.", end=' ')
   ...     print("E's", state, "!")
   ...
   >>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
   >>> parrot(**d)
   -- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !


4.8.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 make_incrementor(n):
   ...     return lambda x: x + n
   ...
   >>> f = make_incrementor(42)
   >>> f(0)
   42
   >>> f(1)
   43

The above example uses a lambda expression to return a function.
Another use is to pass a small function as an argument:

   >>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
   >>> pairs.sort(key=lambda pair: pair[1])
   >>> pairs
   [(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]


4.8.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 my_function():
   ...     """Do nothing, but document it.
   ...
   ...     No, really, it doesn't do anything.
   ...     """
   ...     pass
   ...
   >>> print(my_function.__doc__)
   Do nothing, but document it.

       No, really, it doesn't do anything.


4.8.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(ham: str, eggs: str = 'eggs') -> str:
   ...     print("Annotations:", f.__annotations__)
   ...     print("Arguments:", ham, eggs)
   ...     return ham + ' and ' + eggs
   ...
   >>> f('spam')
   Annotations: {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>}
   Arguments: spam eggs
   'spam and eggs'


4.9. 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
  și "litere_mici_despărțite_de_bara_jos" pentru funcții și metode.
  Întrebuințați întotdeauna "self" pe post de nume pentru cel dintâi
  argument al unei metode (vezi Prima privire aruncată asupra claselor
  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 subsol ]-

[1] De fapt, *apel prin referință la obiect* ar fi o descriere mai
    potrivită, dat fiind că, dacă transmitem un obiect mutabil, atunci
    apelantul va vedea orice modificare făcută de apelat (cum ar fi
    niște itemi noi inserați într-o listă).
