10. Turul pe scurt al bibliotecii standard
******************************************


10.1. Interfața cu sistemul de operare
======================================

Modulul "os" ne furnizează o cantitate consistentă de funcții pentru
interacțiunea cu sistemul de operare:

   >>> import os
   >>> os.getcwd()      # Return the current working directory
   'C:\\Python314'
   >>> os.chdir('/server/accesslogs')   # Change current working directory
   >>> os.system('mkdir today')   # Run the command mkdir in the system shell
   0

Aveți grijă să realizați importul în *stilul* dat de "import os" și nu
scriind "from os import *". Astfel, veți evita ca apelarea funcției
"os.open()" să se producă în locul celei a funcției predefinite
"open()", aceasta din urmă funcționând complet diferit.

Funcțiile predefinite "dir()" și "help()" ne vor fi utile ca ajutoare
*interactive* atunci când lucrăm cu module masive, precum "os":

   >>> import os
   >>> dir(os)
   <returnează o listă cu toate funcțiile din modul>
   >>> help(os)
   <returnează o pagină extensivă de manual, bazată pe docstring-ul modulului>

Pentru sarcinile administrative zilnice privind fișierele și
directoarele, modulul "shutil" ne pune la dispoziție o interfață de
nivel înalt care este (mai) ușor de întrebuințat:

   >>> import shutil
   >>> shutil.copyfile('datele.db', 'arhiva.db')
   'arhiva.db'
   >>> shutil.move('/build/executabilele', 'directorul_instalării')
   'directorul_instalării'


10.2. Caractere de înlocuire pentru fișiere
===========================================

Modulul "glob" dispune de o funcție care realizează liste de fișiere
bazate pe căutări cu *caractere de înlocuire* (sau *meta-caractere*;
de la englezescul *wildcard*) în directorul curent (și nu numai):

   >>> import glob
   >>> glob.glob('*.py')
   ['numere_prime.py', 'numere_aleatoare.py', 'cote.py']


10.3. Argumente în linia de comandă
===================================

Scripturile utilitare pe care le întrebuințăm în mod obișnuit necesită
adesea procesarea argumentelor primite în linia de comandă. Aceste
argumente sunt stocate, sub formă de listă, în atributul *argv* al
modulului "sys". De exemplu, să presupunem că avem un fișier
"demonstrativ.py", cu următorul conținut:

   # Fișierul demonstrativ.py
   import sys
   print(sys.argv)

Iată ce se va obține în urma execuției comenzii "python
demonstrativ.py unu doi trei" în linie de comandă:

   ['demonstrativ.py', 'unu', 'doi', 'trei']

Modulul "argparse" ne oferă un mecanism (mai) sofisticat pentru
manipularea argumentelor primite în linia de comandă. Scriptul de mai
jos extrage (din șirul de caractere al liniei de comandă) unul sau mai
multe nume de fișiere, respectiv numărul opțional de rânduri ce
urmează a fi afișate din fiecare din fișierele cu numele extrase:

   # Conținutul scriptului capturi_de_la_început.py
   import argparse

   parserul = argparse.ArgumentParser(
       prog='capturi_de_la_început',
       description='Afișează primele rânduri din fiecare fișier')
   parserul.add_argument('nume_de_fișiere', nargs='+')
   parserul.add_argument('-l', '--rânduri', type=int, default=10)
   argumentele = parserul.parse_args()
   print(argumentele)

Atunci când rulăm în linie de comandă "python capturi_de_la_început.py
--rânduri=5 alfa.txt beta.txt", scriptul îi va atribui lui
"argumentele.rânduri" valoarea "5" iar lui
"argumentele.nume_de_fișiere" valoarea "['alfa.txt', 'beta.txt']".


10.4. Redirecționarea ieșirii erorilor și încheierea programelor
================================================================

Modulul "sys" deține atribute și pentru *stdin*, *stdout*, respectiv
pentru *stderr*. Acesta din urmă este de folos la emiterea
avertismentelor și a mesajelor de eroare, chiar și atunci când
(fluxul) *stdout* a fost redirecționat:

   >>> sys.stderr.write('Atenție, nu găsesc jurnalul, încep unul nou\n')
   Atenție, nu găsesc jurnalul, încep unul nou

Modul (cel mai) direct de a *încheia* (de la englezescul *terminate*)
execuția unui script este să utilizăm "sys.exit()".


10.5. Identificarea după tipare a șirurilor de caractere
========================================================

Modulul "re" ne pune la dispoziție unelte care utilizează *expresii
regulate* pentru procesări complexe ale șirurilor de caractere. Atunci
când suntem interesați de identificări (de la englezescul *matching*)
și manipulări sofisticate, expresiile regulate ne vin în ajutor cu
soluții succinte, optimizate:

   >>> import re
   >>> re.findall(r'\bm[a-zăâ]*', 'cine, maică, mâna ori mâncarea pică la măcel')
   ['maică', 'mâna', 'mâncarea', 'măcel']
   >>> re.sub(r'(\b[a-z]+) \1', r'\1', 'hap pe pe halat')
   'hap pe halat'

Atunci când avem sarcini de identificare simple, metodele tipului *șir
de caractere* sunt de preferat, dat fiind că ele pot fi citite (în
codul Python aferent) și depanate mai ușor:

   >>> 'ceai pentru Mircea'.replace('Mircea', 'Cici')
   'ceai pentru Cici'


10.6. Matematică
================

Modulul "math" ne facilitează accesul la funcțiile corespondente ale
bibliotecii C dedicate calculului (matematicii) în virgulă mobilă:

   >>> import math
   >>> math.cos(math.pi / 4)
   0.70710678118654757
   >>> math.log(1024, 2)
   10.0

Modulul "random" ne furnizează unelte pentru realizarea de eșantioane
aleatoare:

   >>> import random
   >>> random.choice(['măr', 'pară', 'banană'])
   'măr'
   >>> random.sample(range(100), 10)   # eșantionare fără înlocuire
   [30, 83, 16, 4, 8, 81, 41, 50, 18, 33]
   >>> random.random()    # float aleator din intervalul [0.0, 1.0)
   0.17970987693706186
   >>> random.randrange(6)    # întreg aleator ales din range(6)
   4

Modulul "statistics" calculează proprietăți statistice de bază (media,
mediana, varianța șamd.) pentru datele numerice:

   >>> import statistics
   >>> datele = [2.75, 1.75, 1.25, 0.25, 0.5, 1.25, 3.5]
   >>> statistics.mean(datele)
   1.6071428571428572
   >>> statistics.median(datele)
   1.25
   >>> statistics.variance(datele)
   1.3720238095238095

Proiectul SciPy <https://scipy.org> deține multe alte module dedicate
calculului numeric.


10.7. Accesul la Internet
=========================

Un număr de module sunt dedicate accesării Internetului și procesării
protocoalelor acestuia. Două din cele mai simplu de folosit module
sunt "urllib.request", dedicat extragerii datelor din URL-uri, și
"smtplib", folosit la trimiterea de scrisori electronice. (Scrisoarea
din exemplu conține versul shakespearian *Beware the Ides of March*.
Ca traducere a sa am folosit versiunea dată de Barbu Lăzureanu):

   >>> from urllib.request import urlopen
   >>> with urlopen(
   ...     'http://worldtimeapi.org/api/timezone/Europe/Bucharest.txt') as răspunsul:
   ...     for rândul in răspunsul:
   ...         # Convertește octeți în șir de caractere:
   ...         rândul = rândul.decode()
   ...         if rândul.startswith('datetime'):
   ...             # Elimină rândul gol de la final:
   ...             print(rândul.rstrip())
   ...
   datetime: 2022-01-01T01:36:47.689215+00:00

   >>> import smtplib
   >>> serverul_de_poștă = smtplib.SMTP('localhost')
   >>> serverul_de_poștă.sendmail('profetul@de_exemplu.org',
   ...                            'iuliu_cezar@de_exemplu.org',
   ... """To: iuliu_cezar@de_exemplu.org
   ... From: profetul@de_exemplu.org
   ...
   ... Bagă de seamă sunt Idele lui Martie!
   ... """)
   >>> serverul_de_poștă.quit()

(Atenție, la cel de-al doilea exemplu veți avea nevoie de un server de
poștă electronică instalat pe *localhost*.)


10.8. Date calendaristice și momente de timp
============================================

Modulul "datetime" ne furnizează clase pentru manevrarea datelor
calendaristice și a momentelor de timp atât în moduri simple cât și în
feluri sofisticate. Deși calculul aritmetic cu date calendaristice și
momente de timp este suportat, implementarea se concentrează pe
extracția eficientă a componentelor (datelor temporale) în scopuri de
formatare și de manipulare a afișărilor. Modulul suportă inclusiv
obiecte care înțeleg fusurile orare (de la englezescul, ca jargon
informatic, *timezone*).

   >>> # datele calendaristice se construiesc și se formatează ușor
   >>> from datetime import date
   >>> acum = date.today()
   >>> acum
   datetime.date(2025, 7, 25)
   >>> # evităm impunerea unor "locale" în Windows:
   >>> acum.strftime("%d-%m-%y. %d %b %Y pică %A în ziua %d a lui %B.")
   '25-07-25. 25 Jul 2025 pică Friday în ziua 25 a lui July.'

   >>> # datele calendaristice suportă aritmetica de calendar
   >>> ziua_de_naștere = date(1964, 7, 31)
   >>> vârsta = acum - ziua_de_naștere
   >>> vârsta.days
   22274


10.9. Compresia datelor
=======================

Formatele tipice de compresie și de arhivare a datelor sunt suportate
în mod direct de către module precum: "zlib", "gzip", "bz2", "lzma",
"zipfile" și "tarfile".

   >>> import zlib
   >>> șirul = b'arac brac capac colac copac crac drac fac lac mac tac'\
   ...         b' toc trac'
   >>> len(șirul)
   62
   >>> șirul_comprimat = zlib.compress(șirul)
   >>> len(șirul_comprimat)
   44
   >>> zlib.decompress(șirul_comprimat)
   b'arac brac capac colac copac crac drac fac lac mac tac toc trac'
   >>> zlib.crc32(șirul)
   2525718583


10.10. Măsurarea performanțelor
===============================

Nu de puține ori, programatorii se interesează de compararea
performanțelor pe care le dovedesc diferitele abordări (cunoscute) ale
aceleași probleme (de programare). Python-ul ne furnizează o unealtă
de măsurare (a timpului de execuție) care poate satisface rapid aceste
curiozități.

De exemplu, putem fi tentați, la un moment dat, să întrebuințăm
împachetarea și despachetarea tuplurilor în locul abordării
tradiționale la interschimbarea (de la englezescul *swapping*) unor
valori. Modulul "timeit" ne dovedește imediat că folosirea tuplurilor
ne va conferi un mic avantaj:

   >>> from timeit import Timer
   >>> Timer('t=a; a=b; b=t', 'a=1; b=2').timeit()
   0.57535828626024577
   >>> Timer('a,b = b,a', 'a=1; b=2').timeit()
   0.54962537085770791

Spre deosebire de nivelul de granularitate (ridicat) al lui "timeit",
care îl face potrivit pe acesta pentru analiza unor fragmente mici de
cod, modulele "profile" și "pstats" posedă unelte pentru identificarea
zonelor critice în privința timpului de execuție din blocuri (mai)
mari de cod Python.


10.11. Controlul calității
==========================

Una din căile care ne conduc la producerea *software*-ului de înaltă
calitate este cea de *a compune teste* pentru verificarea fiecărei
*funcții* (funcționalități) pe parcursul implementării ei și de *a
rula aceste teste* în mod frecvent de-a lungul întregului proces de
dezvoltare a produsului software.

Modulul "doctest" ne pune la dispoziție o unealtă care realizează
*analiza lexicală* a codului (sau scanarea; de la englezescul
*scanning*) unui modul și încearcă să *valideze testele* care au fost
inserate (de către autorii codului) în docstring-urile acestui modul
(program) Python. Prepararea unui asemenea test se rezumă la
decuparea-și-lipirea (de la englezescul *cut-and-paste*) în docstring
a fragmentului de cod format din apelul tipic al funcției (pe care
vrem să o verificăm) împreună cu rezultatul (corect) returnat de acest
apel. Astfel, pe de o parte, se îmbunătățește documentația
oferindu-i-se utilizatorului unul sau mai multe exemple și, pe de altă
parte, existența respectivelor fragmente îi va permite modulului
*doctest* să verifice dacă programul pe care utilizatorul îl dezvoltă
îi rămâne fidel documentației (inițiale):

   >>> def media(valorile):
   ...     """Calculează media aritmetică a unei liste de numere.
   ...
   ...     >>> print(media([20, 30, 70]))
   ...     40.0
   ...     """
   ...     return sum(valorile) / len(valorile)
   ...
   >>> import doctest
   >>> doctest.testmod()   # validează automat testele incluse
   TestResults(failed=0, attempted=1)

Modulul "unittest" nu este tot atât de fără efort la utilizare precum
"doctest", însă ne permite să construim un set de teste (mai)
comprehensiv pe care să îl putem întreține într-un fișier separat:

   import unittest

   class TesteazăFuncțiileStatistice(unittest.TestCase):

       def testează_media(self):
           self.assertEqual(media([20, 30, 70]), 40.0)
           self.assertEqual(round(media([1, 5, 7]), 1), 4.3)
           with self.assertRaises(ZeroDivisionError):
               media([])
           with self.assertRaises(TypeError):
               media(20, 30, 70)

   unittest.main()  # La apelul în linia de comandă vor fi invocate
                    # toate testele


10.12. Bateriile sunt incluse
=============================

Python-ul urmează filozofia "includeți (și) bateriile". Putem observa
cu ușurință aceasta dacă aruncăm o privire asupra capabilităților
robuste și sofisticate pe care le probează pachetele sale cele mai
cuprinzătoare. De pildă:

* Modulele "xmlrpc.client" și "xmlrpc.server" transformă orice
  implementare de apeluri de procedură de la distanță într-o
  activitate aproape banală. În ciuda numelor acestor module, nu este
  nevoie nici de cunoștințe teoretice și nici de experință practică
  privind XML-ul.

* Pachetul "email" este o bibliotecă dedicată administrării mesajelor
  de poștă electronică, inclusiv a mesageriei documentelor MIME și a
  altor documente bazate pe **RFC 2822**. Spre deosebire de "smtplib"
  și de "poplib", care trimit și primesc efectiv mesajele, pachetul
  *email* deține un set complet de instrumente pentru construcția și
  decodificarea structurilor complexe de mesaje (incluzând aici și
  *anexele*, de la englezescul *attachment*, acestor mesaje),
  respectiv pentru implementarea protocoalelor de Internet privind
  codificările (textului) și antetele (mesajelor).

* Pachetul "json" oferă suport robust pentru *analiza sintactică și
  semantică* a codului (sau parsarea; de la englezescul *parsing*)
  acestui format popular de interschimbare de date. Modulul "csv" ne
  permite citirea și scrierea directă a fișierelor în formatul
  *valori-separate-prin-virgule* (sau *valori-delimitate-de-virgule*;
  de la englezescul *comma-separated-value*), format acceptat de
  majoritatea bazelor de date și a foilor de calcul (de la englezescul
  *spreadsheet*). Procesările XML sunt suportate de pachetele
  "xml.etree.ElementTree", "xml.dom" și "xml.sax". Laolaltă, aceste
  module și pachete ușurează considerabil schimburile de date dintre
  aplicațiile Python și alte unelte informatice.

* Modulul "sqlite3" este o împachetare a bibliotecii de baze de date
  SQLite, care ne pune la dispoziție o bază de date persistentă (pe
  *disc*) ce va putea fi actualizată și accesată folosind o sintaxă
  SQL puțin atipică.

* Internaționalizarea este facilitată de un număr de module,
  incluzându-le aici pe "gettext", "locale", precum și de pachetul
  "codecs".
