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
===================================

So far we've encountered two ways of writing values: *expression
statements* and the "print()" function.  (A third way is using the
"write()" method of file objects; the standard output file can be
referenced as "sys.stdout". See the Library Reference for more
information on this.)

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.

File objects have some additional methods, such as "isatty()" and
"truncate()" which are less frequently used; consult the Library
Reference for a complete guide to file objects.


7.2.2. Salvarea structurilor de date cu "json"
----------------------------------------------

Strings can easily be written to and read from a file.  Numbers take a
bit more effort, since the "read()" method only returns strings, which
will have to be passed to a function like "int()", which takes a
string like "'123'" and returns its numeric value 123.  When you want
to save more complex data types like nested lists and dictionaries,
parsing and serializing by hand becomes complicated.

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.
