# 7. Intrări și ieșiri

Există mai multe moduri de a prezenta rezultatul (sau ieșirea, de la englezescul output) execuției unui program; datele conținute în acest rezultat pot fi afișate într-o formă ușor de citit de către utilizatorii umani (în englezește, ca jargon, human-readable) sau pot fi scrise într-un fișier pentru o întrebuințare ulterioară. În capitolul de față vom discuta despre câteva din aceste feluri de prezentare.

## 7.1. Formatări elegante ale ieșirii

Până acum am întâlnit două modalități de scriere a valorilor: formulările de expresii și funcția `print()`. (O a treia modalitate este să folosim metoda `write()` a obiectelor fișier; fișierul standard de ieșire poate fi accesat ca `sys.stdout`. Vedeți Referința bibliotecii pentru mai multe informații în această privință.)

Adesea, veți dori să aveți mai mult control asupra formatului datelor de afișat decât simpla posibilitate de a le tipări cu câte un spațiu gol între ele. Dispunem de diferite procedee de a formata ieșirile.

- Pentru a folosi șiruri de caractere literale formatate, începeți șirul cu `f` sau cu `F`, poziționate înaintea ghilimelelor duble ori a celor triple de deschidere. În interiorul unui asemenea șir de caractere, puteți scrie o expresie Python încadrată de caracterele `{` și `}`, care să se refere la variabile ori la date literale.

      >>> year = 2016
      >>> event = 'Referendum'
      >>> f'Results of the {year} {event}'
      'Results of the 2016 Referendum'

- The `str.format()` method of strings requires more manual effort. You’ll still use `{` and `}` to mark where a variable will be substituted and can provide detailed formatting directives, but you’ll also need to provide the information to be formatted.

      >>> yes_votes = 42_572_654
      >>> no_votes = 43_132_495
      >>> percentage = yes_votes / (yes_votes + no_votes)
      >>> '{:-9} YES votes  {:2.2%}'.format(yes_votes, percentage)
      ' 42572654 YES votes  49.67%'

- În sfârșit, puteți să vă ocupați chiar dumneavoastră de întreaga manipulare a șirurilor de caractere folosind operațiile de tranșare și de concatenare a șirurilor pentru a crea orice machetă (de la englezescul layout) de afișare vă doriți. Tipul de date șir de caractere posedă diverse metode de spațiere a șirurilor la lățimi de caracter date.

Atunci când nu vă interesează nicio ieșire sofisticată ci, pur și simplu, vreți să afișați rapid valorile câtorva variabile în scopul depanării programului la care lucrați, ați putea converti orice valoare obținută într-un șir de caractere cu ajutorul funcțiilor `repr()` și `str()`.

Funcția `str()` a fost gândită să returneze reprezentări de valori care să fie ușor de citit de către utilizatorii umani, pe când funcția `repr()` a primit rolul de a genera reprezentări de date care să fie citite de către interpretorul de Python (sau să ridice o excepție `SyntaxError` atunci când reprezentările nu se potrivesc niciunui șablon sintactic). În cazul obiectelor care nu posedă nicio reprezentare distinctivă de oferit unui consumator uman, `str()` va întoarce aceeași valoare ca și `repr()`. Multe valori, cum ar fi numerele ori structurile de date de tipul listelor și al dicționarelor, primesc aceeași reprezentare de la amândouă funcțiile. Șirurile de caractere, pe de altă parte, posedă două reprezentări diferite.

Câteva exemple:

    >>> s = 'Hello, world.'
    >>> str(s)
    'Hello, world.'
    >>> repr(s)
    "'Hello, world.'"
    >>> str(1/7)
    '0.14285714285714285'
    >>> x = 10 * 3.25
    >>> y = 200 * 200
    >>> s = 'The value of x is ' + repr(x) + ', and y is ' + repr(y) + '...'
    >>> print(s)
    The value of x is 32.5, and y is 40000...
    >>> # The repr() of a string adds string quotes and backslashes:
    ... hello = 'hello, world\n'
    >>> hellos = repr(hello)
    >>> print(hellos)
    'hello, world\n'
    >>> # The argument to repr() may be any Python object:
    ... repr((x, y, ('spam', 'eggs')))
    "(32.5, 40000, ('spam', 'eggs'))"

Modulul `string` conține o clasă `Template` care oferă încă o metodă de substituire a valorilor în șirurile de caractere, pe baza unor substitute de forma `$x` ce vor fi înlocuite cu valori preluate dintr-un dicționar, însă care furnizează mult mai puțin control asupra formatării.

### 7.1.1. Șiruri de caractere literale formatate

Șirurile de caractere literale formatate (supranumite, pe scurt, f-șiruri) ne permit să introducem valoarea unei expresii Python în interiorul unui șir de caractere prefixând șirul cu `f` sau `F` și scriind expresia în cauză drept `{expresia}`.

Un specificator de format opțional îi poate urma expresiei. Prezența lui oferă un control sporit asupra modulului în care valoarea expresiei este formatată. Exemplul dat în continuare prezintă rotunjirea lui pi la trei cifre după virgulă (punct):

    >>> import math
    >>> print(f'The value of pi is approximately {math.pi:.3f}.')
    The value of pi is approximately 3.142.

Inserarea unui număr întreg imediat după semnul `':'` va avea ca efect printarea unui câmp lat de minim tot atâta caractere cât este valoarea inserată. O atare proprietate se dovedește utilă la alinierea coloanelor într-o afișare.

    >>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
    >>> for name, phone in table.items():
    ...     print(f'{name:10} ==> {phone:10d}')
    ...
    Sjoerd     ==>       4127
    Jack       ==>       4098
    Dcab       ==>       7678

Alți modificatori pot fi întrebuințați la conversia unei valori înainte de formatare. Astfel, `'!a'` îi aplică valorii funcția `ascii()`, `'!s'` o aplică pe `str()` iar `'!r'` pe `repr()`:

    >>> animals = 'eels'
    >>> print(f'My hovercraft is full of {animals}.')
    My hovercraft is full of eels.
    >>> print(f'My hovercraft is full of {animals!r}.')
    My hovercraft is full of 'eels'.

Specificatorul `=` poate fi utilizat la extinderea unei expresii sub formă de text al expresiei, urmat de semnul egal și de reprezentarea evaluării expresiei respective:

    >>> bugs = 'roaches'
    >>> count = 13
    >>> area = 'living room'
    >>> print(f'Debugging {bugs=} {count=} {area=}')
    Debugging bugs='roaches' count=13 area='living room'

A se vedea expresii auto-documentate pentru mai multe informații privind specificatorul `=`. Pentru o descriere cuprinzătoare a tuturor acestor specificații de format, consultați ghidul de referință despre Mini-limbajul specificației de format.

### 7.1.2. Metoda format() a tipului șir de caractere

Utilizarea de bază a metodei `str.format()` se face astfel – apelând, ca de obicei, la o scenetă Monty Python:

    >>> print('We are the {} who say "{}!"'.format('knights', 'Ni'))
    We are the knights who say "Ni!"

Acoladele și caracterele încadrate de ele (care se numesc câmpuri de format) vor fi înlocuite de obiectele transmise metodei `str.format()`. Un număr trecut între acolade poate fi utilizat ca referire la poziția obiectului ce trebuie transmis metodei `str.format()`.

    >>> print('{0} and {1}'.format('spam', 'eggs'))
    spam and eggs
    >>> print('{1} and {0}'.format('spam', 'eggs'))
    eggs and spam

Dacă vom întrebuința argumente cuvânt-cheie la apelul metodei `str.format()`, atunci ne vom referi la valorile acestora prin intermediul numelor argumentelor respective.

    >>> print('This {food} is {adjective}.'.format(
    ...       food='spam', adjective='absolutely horrible'))
    This spam is absolutely horrible.

Argumentele poziționale și cele cuvânt-cheie pot fi combinate în mod arbitrar:

    >>> print('The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred',
    ...                                                    other='Georg'))
    The story of Bill, Manfred, and Georg.

În caz că aveți de manevrat un șir de format realmente lung și țineți neapărat să nu-l partiționați, ar fi util dacă v-ați putea referi la variabilele care trebuie formatate nu cu numărul – indicele poziției – ci cu numele. O atare rezolvare se obține transmițând, pur și simplu, un dicționar de date și folosind parantezele pătrate `'[]'` pentru a-i accesa cheile.

    >>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
    >>> print('Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '
    ...       'Dcab: {0[Dcab]:d}'.format(table))
    Jack: 4098; Sjoerd: 4127; Dcab: 8637678

Același rezultat poate fi obținut transmițând dicționarul de date `tabel` ca pe un argument cuvânt-cheie dat în notația `**`.

    >>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
    >>> print('Jack: {Jack:d}; Sjoerd: {Sjoerd:d}; Dcab: {Dcab:d}'.format(**table))
    Jack: 4098; Sjoerd: 4127; Dcab: 8637678

This is particularly useful in combination with the built-in function `vars()`, which returns a dictionary containing all local variables.

Ca exemplu, liniile de cod următoare produc un set de coloane aliniate compact care afișează numere întregi împreună cu pătratele și cuburile lor:

    >>> for x in range(1, 11):
    ...     print('{0:2d} {1:3d} {2:4d}'.format(x, x*x, x*x*x))
    ...
     1   1    1
     2   4    8
     3   9   27
     4  16   64
     5  25  125
     6  36  216
     7  49  343
     8  64  512
     9  81  729
    10 100 1000

Pentru o dare de seamă elaborată asupra formatării șirurilor de caractere folosind `str.format()`, a se vedea Sintaxa șirurilor de format.

### 7.1.3. Formatare manuală a șirurilor de caractere

Iată același tabel de pătrate și cuburi de numere întregi, însă formatat manual:

    >>> for x in range(1, 11):
    ...     print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')
    ...     # Note use of 'end' on previous line
    ...     print(repr(x*x*x).rjust(4))
    ...
     1   1    1
     2   4    8
     3   9   27
     4  16   64
     5  25  125
     6  36  216
     7  49  343
     8  64  512
     9  81  729
    10 100 1000

(Luați în considerare faptul că un spațiu gol, între două coloane consecutive, i se datorează modului de funcționare al funcției `print()`: aceasta adaugă întotdeauna spații goale între argumentele sale.)

Metoda `str.rjust()` a obiectelor șir de caractere aliniază la dreapta un șir într-un câmp de lățime dată inserând spații goale în stânga șirului. Python-ul dispune de încă două metode similare, și anume `str.ljust()`, respectiv `str.center()`. Ele nu inserează nimic (în vechiul șir), doar întorc un șir nou. Dacă șirul ce trebuie manipulat este prea lung, aceste metode nu-l vor trunchia, ci îl vor returna nemodificat; deși un asemenea comportament vă va afecta dpdv. estetic macheta coloanelor, alternativa ar fi mult mai rea, ea presupunând modificări de valori. (Dacă doriți, cu adevărat, o trunchiere a șirului manipulat, atunci puteți apela la operațiile de tranșare, cum ar fi `x.ljust(n)[:n]`.)

Mai există o metodă, `str.zfill()`, care completează la stânga, cu zerouri, șirurile de numere. Are capacitatea de a distinge cifrele de semnele plus și minus:

    >>> '12'.zfill(5)
    '00012'
    >>> '-3.14'.zfill(7)
    '-003.14'
    >>> '3.14159265359'.zfill(5)
    '3.14159265359'

### 7.1.4. Vechiul stil de formatare al șirurilor de caractere

The % operator (modulo) can also be used for string formatting. Given `'string' % values`, instances of `%` in `string` are replaced with zero or more elements of `values`. This operation is commonly known as string interpolation. For example:

    >>> import math
    >>> print('The value of pi is approximately %5.3f.' % math.pi)
    The value of pi is approximately 3.142.

Mai multe informații se găsesc în secțiunea Formatarea șirurilor de caractere în vechiul stil printf.

## 7.2. Citirea și scrierea fișierelor

`open()` returnează un obiect fișier și este utilizată de obicei cu două argumente poziționale și un argument cuvânt-cheie: `open(nume_de_fișier, mod_de_deschidere, encoding=None)`

    >>> f = open('workfile', 'w', encoding="utf-8")

Primul argument este un șir de caractere desemnând numele fișierului pe care dorim să-l deschidem. Cel de-al doilea argument este tot un șir de caractere, foarte scurt, ce descrie felul în care vom folosi fișierul. mod_de_deschidere va fi `'r'` dacă dorim doar să citim fișierul, `'w'` dacă ne interesează numai să scriem în el (în caz că există fișierul omonim, acesta va fi șters), respectiv `'a'` pentru adăugare la sfârșitul fișierului. `'r+'` va deschide fișierul atât pentru citit cât și pentru scris. Argumentul mod_de_deschidere este opțional; dacă nu-l precizăm, atunci el va fi considerat `'r'`.

De obicei, fișierele se deschid în modul text, ceea ce înseamnă că puteți citi șiruri de caractere din ele, respectiv scrie șiruri de caractere în ele, șiruri care sunt codificate (de la englezescul encoding) conform unei codificări anumite. Atunci când codificarea nu este precizată, codificarea implicită va depinde de platforma de calcul (vedeți `open()`). Dat fiind că UTF-8 constituie, în zilele noastre, standardul de facto în domeniu, este recomandat să folosiți `encoding="utf-8"` dacă nu aveți, realmente, nevoie de vreo codificare specială. Adăugând sufixul `'b'` la modul de deschidere, fișierul în cauză va fi deschis în modul binar. Datele accesate în modul binar sunt citite și scrise sub formă de obiecte `octeți`. Nu vi se permite să specificați care este codificarea dacă deschideți fișierul în modul binar.

Atunci când manevrăm fișierul în modul text, la citire din el, caracterele care marchează sfârșitul de rând se vor transforma implicit din caractere sfârșit de rând specifice platformei de calcul (adică, din `\n` în Unix, respectiv din `\r\n` în Windows) în caracterul `\n`. Invers, la scrierea în fișier, toate aparițiile lui `\n` vor deveni implicit caracterele de sfârșit de rând specifice platformei pe care lucrăm. Deși o astfel de modificare sub capotă (de la englezescul behind-the-scenes) este convenabilă atunci când avem de a face cu fișiere text, ea va corupe datele binare precum cele dintr-un fișier `JPEG` ori `EXE`. Aveți mare grijă să folosiți modul binar atunci când fie citiți din asemenea fișiere fie scrieți în ele.

O bună practică ne este la îndemână prin intermediul cuvântului-cheie `with`, de întrebuințat dacă manevrăm obiecte fișier. Avantajul său constă în aceea că fișierul va fi întotdeauna închis corect la finalul execuției expresiei acestui construct, și aceasta chiar dacă a fost ridicată vreo excepție pe parcurs. În plus, folosirea lui `with` scurtează semnificativ codul care ar trebui scris dacă folosim constructul echivalent dat de blocurile `try`-`finally`:

    >>> with open('workfile', encoding="utf-8") as f:
    ...     read_data = f.read()

    >>> # We can check that the file has been automatically closed.
    >>> f.closed
    True

În caz că nu utilizați cuvântul cheie `with`, atunci va trebui să apelați metoda `f.close()` pentru a închide fișierul și a elibera de îndată eventualele resurse ale sistemului pe care manevrarea fișierului le-a blocat.

Atenționare

Apelul lui `f.write()` fie fără să folosim cuvântul cheie `with` fie fără să-l însoțim de apelul lui `f.close()` poate avea drept consecință o scriere incompletă a argumentelor lui `f.write()` pe disc, și aceasta chiar dacă programul se va încheia corect.

După ce un obiect fișier a fost închis, fie printr-o instrucție `with` fie prin apelul metodei `f.close()`, orice încercare de a-l întrebuința va fi sortită eșecului.

    >>> f.close()
    >>> f.read()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: I/O operation on closed file.

### 7.2.1. Metodele obiectelor fișier

Restul exemplelor din această secțiune presupune că un obiect fișier pe nume `f` a fost deja creat.

Ca să citiți conținutul unui fișier, apelați `f.read(cantitate)`, instrucțiune care citește o anumită cantitate de date și o returnează sub formă de șir de caractere (în modul text) sau sub forma unui obiect bytes (în modul binar). cantitate este un argument numeric opțional. Atunci când cantitate fie este omis fie este număr negativ, întregul conținut al fișierului va fi citit și apoi returnat; treaba dumneavoastră dacă fișierul în cauză se dovedește a fi de două ori mai mare ca memoria mașinii de calcul. Altfel, cel mult un număr de cantitate de caractere (în modul text) vor fi citite și returnate, respectiv un număr de cantitate de biți (în modul binar) vor fi citiți și returnați. Atunci când se va ajunge la finalul fișierului, `f.read()` va întoarce un șir gol (`''`).

    >>> f.read()
    'This is the entire file.\n'
    >>> f.read()
    ''

`f.readline()` citește un singur rând din fișier; caracterul linie-nouă (`\n`) este păstrat la sfârșitul șirului de caractere citit, el fiind omis numai la ultimul rând al fișierului, atunci când fișierul nu se încheie cu o linie nouă (în englezește, ca jargon, newline). Procedeul în cauză face ca valoarea returnată să fie lipsită de ambiguitate; căci, dacă `f.readline()` returnează un șir gol înseamnă că am ajuns la finalul fișierului, în timp ce o linie goală (dintre paragrafe) va fi reprezentată de un `'\n'`, adică de un șir conținând numai un singur caracter, cel de linie nouă.

    >>> f.readline()
    'This is the first line of the file.\n'
    >>> f.readline()
    'Second line of the file\n'
    >>> f.readline()
    ''

Pentru a citi mai multe linii din fișier, puteți itera de-a lungul obiectului fișier. O atare procedură este eficientă dpdv. al consumului de memorie, este rapidă și se bazează pe un cod Python simplu:

    >>> for line in f:
    ...     print(line, end='')
    ...
    This is the first line of the file.
    Second line of the file

Dacă vă interesează să plasați toate rândurile unui fișier într-o listă, atunci ați putea folosi `list(f)` sau `f.readlines()`.

`f.write(șir_de_caractere)` scrie conținutul lui șir_de_caractere în fișier, returnând numărul de caractere scrise.

    >>> f.write('This is a test\n')
    15

Obiectele de alt tip trebuie convertite mai întâi – fie la șiruri de caractere (în modul text) fie la obiecte bytes (în modul binar) – înainte de scrierea în fișier:

    >>> value = ('the answer', 42)
    >>> s = str(value)  # convert the tuple to string
    >>> f.write(s)
    18

`f.tell()` returnează un număr întreg care fie indică poziția curentă în obiectul fișier precizând la câți octeți ne găsim față de începutul fișierului, atunci când fișierul a fost deschis în modul binar, fie este o valoare opacă, atunci când fișierul a fost deschis în modul text.

Pentru a schimba poziția (cursorului) în obiectul fișier, întrebuințați metoda `f.seek(deplasament, sursă)`. Poziția se calculează adunând acest deplasament la punctul de referință; punctul de referință este precizat prin argumentul sursă. Valoarea 0 a lui sursă înseamnă că ne vom raporta la începutul fișierului, valoarea 1 se referă la poziția curentă în fișier, iar valoarea 2 implică folosirea finalului de fișier pe post de punct de referință.

    >>> f = open('workfile', 'rb+')
    >>> f.write(b'0123456789abcdef')
    16
    >>> f.seek(5)      # Go to the 6th byte in the file
    5
    >>> f.read(1)
    b'5'
    >>> f.seek(-3, 2)  # Go to the 3rd byte before the end
    13
    >>> f.read(1)
    b'd'

În fișierele text (adică, fișierele deschise fără să se utilizeze niciun `b` în șirul de caractere al modului de deschidere) sunt permise doar poziționările raportate la începutul fișierului (singura excepție fiind cea dată de poziționarea, folosind `seek(0, 2)`, chiar la finalul fișierului), iar valorile corecte pentru deplasament sunt numerele returnate de `f.tell()`, respectiv zero. Orice alt deplasament va cauza comportamente impredictibile.

Fișierele obiect posedă diverse alte metode, precum `isatty()` și `truncate()` care sunt mai rar întâlnite; consultați Referința bibliotecii pentru ghidul cuprinzător al obiectelor fișier.

### 7.2.2. Salvarea structurilor de date cu `json`

Șirurile de caractere se scriu ușor într-un fișier și se citesc (la fel de) ușor dintr-un fișier. Numerele presupun un efort suplimentar, dat fiind că metoda `read()` returnează doar șiruri de caractere, iar acestea din urmă trebuie transmise unei funcții precum `int()`, care funcție preia un șir ca `'123'` și întoarce valoarea sa numerică, adică pe 123. Atunci când vă interesează să salvați date de diverse tipuri mai sofisticate decât șirurile ori numerele, cum ar fi listele imbricate ori dicționarele de date, parsarea manuală și serializarea de mână devin niște sarcini complicate.

Ca să nu-i silească pe utilizatori să-și consume timpul scriind și corectând cod specializat în salvarea în fișiere a tipurilor complicate de date, Python-ul le permite acestora să întrebuințeze popularul format de (inter)schimb de date numit JSON (Notația obiectelor JavaScript). Modulul predefinit `json` preia date Python ierarhizate și le convertește în reprezentări sub formă de șiruri de caractere; un atare proces este numit serializare. Invers, reconstrucția datelor din reprezentările lor ca șiruri de caractere poartă numele de deserializare. Între o serializare și deserializarea subsecventă, șirul de caractere care reprezintă un obiect Python poate fi stocat într-un fișier sau într-un flux de date, ori transmis printr-o conexiune la rețeaua Internet către o mașină de calcul aflată la distanță.

Notă

Formatul JSON este utilizat frecvent de aplicațiile moderne care permit schimburi de date. Mulți programatori sunt deja familiarizați cu el, ceea ce îl face o alegere potrivită atunci când ne interesează interoperabilitatea.

Dacă aveți la îndemână un obiect Python `x`, atunci vă ajunge o singură linie de cod ca să-i vedeți reprezentarea JSON sub formă de șir de caractere:

    >>> import json
    >>> x = [1, 'simple', 'list']
    >>> json.dumps(x)
    '[1, "simple", "list"]'

O variantă a funcției `dumps()`, intitulată `dump()`, serializează de-a dreptul obiectul Python într-un fișier text. Așadar, dacă `f` este un obiect fișier text deschis pentru scriere, putem proceda astfel:

    json.dump(x, f)

Ca redecodificăm obiectul, presupunând că `f` este fie un obiect fișier binar fie un obiect fișier text care a fost deja deschis pentru citire, executăm:

    x = json.load(f)

Notă

Fișierele JSON trebuie să fie codificate UTF-8. Folosiți `encoding="utf-8"` atunci când deschideți un fișier JSON ca fișier text atât pentru citire cât și pentru scriere.

O asemenea tehnică simpl(ist)ă de serializare se descurcă la manevrarea listelor și a dicționarelor de date, însă serializarea în JSON a unor instanțe de clase oarecare necesită un anume efort. Referința la modulul `json` din documentație explică de ce.

Vezi și

`pickle` - modulul pickle

Spre deosebire de JSON, pickle (adică, muratul sau punerea la murat) desemnează un protocol capabil să serializeze obiecte Python oricât de complicate. Din care motiv, îi este tipic (doar) Python-ului și nu poate fi utilizat la comunicarea cu aplicații scrise în alte limbaje de programare. De asemeni, este nesigur în mod prestabilit: deserializarea unor date puse la murat pe care le-ați obținut de la o sursă dubioasă poate provoca execuții de cod despre care (să) nu știți nimic, în caz că datele au fost preparate de un atacator îndemânatic.