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:\\Python311'
   >>> 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)
   <returns a list of all module functions>
   >>> help(os)
   <returns an extensive manual page created from the module's docstrings>

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('data.db', 'archive.db')
   'archive.db'
   >>> shutil.move('/build/executables', 'installdir')
   'installdir'


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')
   ['primes.py', 'random.py', 'quote.py']


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

Common utility scripts often need to process command line arguments.
These arguments are stored in the "sys" module's *argv* attribute as a
list.  For instance the following output results from running "python
demo.py one two three" at the command line:

   >>> import sys
   >>> print(sys.argv)
   ['demo.py', 'one', 'two', 'three']

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:

   import argparse

   parser = argparse.ArgumentParser(
       prog='top',
       description='Show top lines from each file')
   parser.add_argument('filenames', nargs='+')
   parser.add_argument('-l', '--lines', type=int, default=10)
   args = parser.parse_args()
   print(args)

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('Warning, log file not found starting a new one\n')
   Warning, log file not found starting a new one

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'\bf[a-z]*', 'which foot or hand fell fastest')
   ['foot', 'fell', 'fastest']
   >>> re.sub(r'(\b[a-z]+) \1', r'\1', 'cat in the the hat')
   'cat in the hat'

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:

   >>> 'tea for too'.replace('too', 'two')
   'tea for two'


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

The "math" module gives access to the underlying C library functions
for floating point math:

   >>> 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(['apple', 'pear', 'banana'])
   'apple'
   >>> random.sample(range(100), 10)   # sampling without replacement
   [30, 83, 16, 4, 8, 81, 41, 50, 18, 33]
   >>> random.random()    # random float
   0.17970987693706186
   >>> random.randrange(6)    # random integer chosen from range(6)
   4

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

   >>> import statistics
   >>> data = [2.75, 1.75, 1.25, 0.25, 0.5, 1.25, 3.5]
   >>> statistics.mean(data)
   1.6071428571428572
   >>> statistics.median(data)
   1.25
   >>> statistics.variance(data)
   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/etc/UTC.txt') as response:
   ...     for line in response:
   ...         line = line.decode()             # Convert bytes to a str
   ...         if line.startswith('datetime'):
   ...             print(line.rstrip())         # Remove trailing newline
   ...
   datetime: 2022-01-01T01:36:47.689215+00:00

   >>> import smtplib
   >>> server = smtplib.SMTP('localhost')
   >>> server.sendmail('soothsayer@example.org', 'jcaesar@example.org',
   ... """To: jcaesar@example.org
   ... From: soothsayer@example.org
   ...
   ... Beware the Ides of March.
   ... """)
   >>> server.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*).

   >>> # dates are easily constructed and formatted
   >>> from datetime import date
   >>> now = date.today()
   >>> now
   datetime.date(2003, 12, 2)
   >>> now.strftime("%m-%d-%y. %d %b %Y is a %A on the %d day of %B.")
   '12-02-03. 02 Dec 2003 is a Tuesday on the 02 day of December.'

   >>> # dates support calendar arithmetic
   >>> birthday = date(1964, 7, 31)
   >>> age = now - birthday
   >>> age.days
   14368


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
   >>> s = b'witch which has which witches wrist watch'
   >>> len(s)
   41
   >>> t = zlib.compress(s)
   >>> len(t)
   37
   >>> zlib.decompress(t)
   b'witch which has which witches wrist watch'
   >>> zlib.crc32(s)
   226805979


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 average(values):
       """Computes the arithmetic mean of a list of numbers.

       >>> print(average([20, 30, 70]))
       40.0
       """
       return sum(values) / len(values)

   import doctest
   doctest.testmod()   # automatically validate the embedded tests

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 TestStatisticalFunctions(unittest.TestCase):

       def test_average(self):
           self.assertEqual(average([20, 30, 70]), 40.0)
           self.assertEqual(round(average([1, 5, 7]), 1), 4.3)
           with self.assertRaises(ZeroDivisionError):
               average([])
           with self.assertRaises(TypeError):
               average(20, 30, 70)

   unittest.main()  # Calling from the command line invokes all tests


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".
