# 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ă).