11. Giro Breve della Libreria Standard --- Parte II
***************************************************

Questo secondo giro copre moduli più avanzati che supportano esigenze
di programmazione professionali. Questi moduli raramente si trovano in
piccoli script.


11.1. Formattazione dell'Output
===============================

Il modulo "reprlib" fornisce una versione di "repr()" personalizzata
per visualizzazioni abbreviate di contenitori grandi o profondamente
annidati:

   >>> import reprlib
   >>> reprlib.repr(set('supercalifragilisticexpialidocious'))
   "{'a', 'c', 'd', 'e', 'f', 'g', ...}"

Il modulo "pprint" offre un controllo più sofisticato sulla stampa di
oggetti sia integrati che definiti dall'utente in modo leggibile
dall'interprete. Quando il risultato è più lungo di una riga, il
"pretty printer" aggiunge interruzioni di riga e indentazione per
rivelare più chiaramente la struttura dei dati:

   >>> import pprint
   >>> t = [[[['black', 'cyan'], 'white', ['green', 'red']], [['magenta',
   ...     'yellow'], 'blue']]]
   ...
   >>> pprint.pprint(t, width=30)
   [[[['black', 'cyan'],
      'white',
      ['green', 'red']],
     [['magenta', 'yellow'],
      'blue']]]

Il modulo "textwrap" formatta paragrafi di testo per adattarsi a una
larghezza di schermo specificata:

   >>> import textwrap
   >>> doc = """The wrap() method is just like fill() except that it returns
   ... a list of strings instead of one big string with newlines to separate
   ... the wrapped lines."""
   ...
   >>> print(textwrap.fill(doc, width=40))
   The wrap() method is just like fill()
   except that it returns a list of strings
   instead of one big string with newlines
   to separate the wrapped lines.

Il modulo "locale" accede a un database di formati di dati specifici
della cultura. L'attributo di raggruppamento della funzione di
formattazione del locale fornisce un modo diretto di formattare i
numeri con separatori di gruppo:

   >>> import locale
   >>> locale.setlocale(locale.LC_ALL, 'English_United States.1252')
   'English_United States.1252'
   >>> conv = locale.localeconv()          # get a mapping of conventions
   >>> x = 1234567.8
   >>> locale.format_string("%d", x, grouping=True)
   '1,234,567'
   >>> locale.format_string("%s%.*f", (conv['currency_symbol'],
   ...                      conv['frac_digits'], x), grouping=True)
   '$1,234,567.80'


11.2. Templating
================

Il modulo "string" include una versatile classe "Template" con una
sintassi semplificata adatta per l'editing da parte degli utenti
finali. Questo permette agli utenti di personalizzare le loro
applicazioni senza dover alterare l'applicazione.

Il formato utilizza nomi di segnaposto formati da "$" con
identificatori Python validi (caratteri alfanumerici e trattini
bassi). Circondando il segnaposto con parentesi graffe, è possibile
farlo seguire da altre lettere alfanumeriche senza spazi intermedi.
Scrivere "$$" crea un singolo "$" escapato:

   >>> from string import Template
   >>> t = Template('${village}folk send $$10 to $cause.')
   >>> t.substitute(village='Nottingham', cause='the ditch fund')
   'Nottinghamfolk send $10 to the ditch fund.'

Il metodo "substitute()" solleva un'eccezione "KeyError" quando un
segnaposto non è fornito in un dizionario o come parametro keyword.
Per applicazioni stile mail-merge, i dati forniti dall'utente possono
essere incompleti e il metodo "safe_substitute()" potrebbe essere più
appropriato --- lascerà i segnaposto invariati se i dati sono
mancanti:

   >>> t = Template('Return the $item to $owner.')
   >>> d = dict(item='unladen swallow')
   >>> t.substitute(d)
   Traceback (most recent call last):
     ...
   KeyError: 'owner'
   >>> t.safe_substitute(d)
   'Return the unladen swallow to $owner.'

Le sottoclassi di Template possono specificare un delimitatore
personalizzato. Ad esempio, un'utilità per il rinominamento dei batch
di un browser di foto può scegliere di usare segni percentuali per i
segnaposto come la data corrente, il numero di sequenza delle immagini
o il formato del file:

   >>> import time, os.path
   >>> photofiles = ['img_1074.jpg', 'img_1076.jpg', 'img_1077.jpg']
   >>> class BatchRename(Template):
   ...     delimiter = '%'
   ...
   >>> fmt = input('Enter rename style (%d-date %n-seqnum %f-format):  ')
   Enter rename style (%d-date %n-seqnum %f-format):  Ashley_%n%f

   >>> t = BatchRename(fmt)
   >>> date = time.strftime('%d%b%y')
   >>> for i, filename in enumerate(photofiles):
   ...     base, ext = os.path.splitext(filename)
   ...     newname = t.substitute(d=date, n=i, f=ext)
   ...     print('{0} --> {1}'.format(filename, newname))

   img_1074.jpg --> Ashley_0.jpg
   img_1076.jpg --> Ashley_1.jpg
   img_1077.jpg --> Ashley_2.jpg

Un'altra applicazione per il templating è separare la logica del
programma dai dettagli di più formati di output. Questo rende
possibile sostituire template personalizzati per file XML, rapporti in
testo semplice e rapporti HTML web.


11.3. Lavorare con Layout Binari dei Dati
=========================================

Il modulo "struct" fornisce le funzioni "pack()" e "unpack()" per
lavorare con formati di record binari a lunghezza variabile. Il
seguente esempio mostra come scorrere le informazioni
dell'intestazione in un file ZIP senza usare il modulo "zipfile". I
codici pack ""H"" e ""I"" rappresentano numeri senza segno a due e
quattro byte rispettivamente. Il ""<"" indica che hanno dimensioni
standard e sono in byte order little-endian:

   import struct

   with open('myfile.zip', 'rb') as f:
       data = f.read()

   start = 0
   for i in range(3):                      # show the first 3 file headers
       start += 14
       fields = struct.unpack('<IIIHH', data[start:start+16])
       crc32, comp_size, uncomp_size, filenamesize, extra_size = fields

       start += 16
       filename = data[start:start+filenamesize]
       start += filenamesize
       extra = data[start:start+extra_size]
       print(filename, hex(crc32), comp_size, uncomp_size)

       start += extra_size + comp_size     # skip to the next header


11.4. Multi-threading
=====================

Il threading è una tecnica per disaccoppiare i compiti che non sono
sequenzialmente dipendenti. I thread possono essere utilizzati per
migliorare la reattività delle applicazioni che accettano l'input
dell'utente mentre altri compiti vengono eseguiti in background. Un
caso d'uso correlato è eseguire I/O in parallelo con calcoli in un
altro thread.

Il seguente codice mostra come il modulo ad alto livello "threading"
può eseguire compiti in background mentre il programma principale
continua a funzionare:

   import threading, zipfile

   class AsyncZip(threading.Thread):
       def __init__(self, infile, outfile):
           threading.Thread.__init__(self)
           self.infile = infile
           self.outfile = outfile

       def run(self):
           f = zipfile.ZipFile(self.outfile, 'w', zipfile.ZIP_DEFLATED)
           f.write(self.infile)
           f.close()
           print('Finished background zip of:', self.infile)

   background = AsyncZip('mydata.txt', 'myarchive.zip')
   background.start()
   print('The main program continues to run in foreground.')

   background.join()    # Wait for the background task to finish
   print('Main program waited until background was done.')

La principale sfida delle applicazioni multi-threaded è coordinare i
thread che condividono dati o altre risorse. A tal fine, il modulo
threading fornisce numerosi primitivi di sincronizzazione tra cui
lock, eventi, variabili di condizione e semafori.

Sebbene questi strumenti siano potenti, piccoli errori di
progettazione possono provocare problemi difficili da riprodurre.
Quindi, l'approccio preferito per il coordinamento dei compiti è
concentrare tutto l'accesso a una risorsa in un singolo thread e poi
usare il modulo "queue" per alimentare quel thread con richieste
provenienti da altri thread. Le applicazioni che usano oggetti "Queue"
per la comunicazione e il coordinamento tra thread sono più facili da
progettare, più leggibili e più affidabili.


11.5. Logging
=============

Il modulo "logging" offre un sistema di logging completo e flessibile.
Nel suo uso più semplice, i messaggi di log vengono inviati a un file
o a "sys.stderr":

   import logging
   logging.debug('Debugging information')
   logging.info('Informational message')
   logging.warning('Warning:config file %s not found', 'server.conf')
   logging.error('Error occurred')
   logging.critical('Critical error -- shutting down')

Questo produce il seguente output:

   WARNING:root:Warning:config file server.conf not found
   ERROR:root:Error occurred
   CRITICAL:root:Critical error -- shutting down

Per impostazione predefinita, i messaggi informativi e di debug sono
soppressi e l'output è inviato all'errore standard. Altre opzioni di
output includono l'instradamento dei messaggi tramite email,
datagrammi, socket o a un server HTTP. Filtri nuovi possono
selezionare l'instradamento diverso basato sulla priorità del
messaggio: "DEBUG", "INFO", "WARNING", "ERROR", e "CRITICAL".

Il sistema di logging può essere configurato direttamente da Python o
può essere caricato da un file di configurazione modificabile
dall'utente per un logging personalizzato senza alterare
l'applicazione.


11.6. Riferimenti Deboli
========================

Python gestisce automaticamente la memoria (conteggio dei riferimenti
per la maggior parte degli oggetti e *garbage collection* per
eliminare i cicli). La memoria viene liberata poco dopo che l'ultimo
riferimento ad essa è stato eliminato.

Questo approccio funziona bene per la maggior parte delle applicazioni
ma occasionalmente è necessario tracciare gli oggetti solo finché
vengono utilizzati da qualcos'altro. Sfortunatamente, solo il
tracciamento crea un riferimento che li rende permanenti. Il modulo
"weakref" fornisce strumenti per tracciare gli oggetti senza creare un
riferimento. Quando l'oggetto non è più necessario, viene
automaticamente rimosso da una tabella weakref e viene attivato un
callback per gli oggetti weakref. Applicazioni tipiche includono il
caching di oggetti costosi da creare:

   >>> import weakref, gc
   >>> class A:
   ...     def __init__(self, value):
   ...         self.value = value
   ...     def __repr__(self):
   ...         return str(self.value)
   ...
   >>> a = A(10)                   # create a reference
   >>> d = weakref.WeakValueDictionary()
   >>> d['primary'] = a            # does not create a reference
   >>> d['primary']                # fetch the object if it is still alive
   10
   >>> del a                       # remove the one reference
   >>> gc.collect()                # run garbage collection right away
   0
   >>> d['primary']                # entry was automatically removed
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
       d['primary']                # entry was automatically removed
     File "C:/python314/lib/weakref.py", line 46, in __getitem__
       o = self.data[key]()
   KeyError: 'primary'


11.7. Strumenti per Lavorare con Liste
======================================

Molte necessità di strutture dati possono essere soddisfatte con il
tipo di lista integrato. Tuttavia, a volte c'è bisogno di
implementazioni alternative con diversi compromessi di prestazioni.

The "array" module provides an "array" object that is like a list that
stores only homogeneous data and stores it more compactly.  The
following example shows an array of numbers stored as two byte
unsigned binary numbers (typecode ""H"") rather than the usual 16
bytes per entry for regular lists of Python int objects:

   >>> from array import array
   >>> a = array('H', [4000, 10, 700, 22222])
   >>> sum(a)
   26932
   >>> a[1:3]
   array('H', [10, 700])

The "collections" module provides a "deque" object that is like a list
with faster appends and pops from the left side but slower lookups in
the middle. These objects are well suited for implementing queues and
breadth first tree searches:

   >>> from collections import deque
   >>> d = deque(["task1", "task2", "task3"])
   >>> d.append("task4")
   >>> print("Handling", d.popleft())
   Handling task1

   unsearched = deque([starting_node])
   def breadth_first_search(unsearched):
       node = unsearched.popleft()
       for m in gen_moves(node):
           if is_goal(m):
               return m
           unsearched.append(m)

Oltre alle implementazioni alternative di liste, la libreria offre
anche altri strumenti come il modulo "bisect" con funzioni per
manipolare liste ordinate:

   >>> import bisect
   >>> scores = [(100, 'perl'), (200, 'tcl'), (400, 'lua'), (500, 'python')]
   >>> bisect.insort(scores, (300, 'ruby'))
   >>> scores
   [(100, 'perl'), (200, 'tcl'), (300, 'ruby'), (400, 'lua'), (500, 'python')]

Il modulo "heapq" fornisce funzioni per implementare heap basati su
liste regolari. L'ingresso a valore più basso viene sempre mantenuto
in posizione zero. Questo è utile per applicazioni che accedono
ripetutamente all'elemento più piccolo ma non vogliono eseguire un
ordinamento completo della lista:

   >>> from heapq import heapify, heappop, heappush
   >>> data = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
   >>> heapify(data)                      # rearrange the list into heap order
   >>> heappush(data, -5)                 # add a new entry
   >>> [heappop(data) for i in range(3)]  # fetch the three smallest entries
   [-5, 0, 1]


11.8. Decimal Floating-Point Arithmetic
=======================================

The "decimal" module offers a "Decimal" datatype for decimal floating-
point arithmetic.  Compared to the built-in "float" implementation of
binary floating point, the class is especially helpful for

* applicazioni finanziarie e altri usi che richiedono una
  rappresentazione decimale esatta,

* controllo sulla precisione,

* controllo sull'arrotondamento per soddisfare requisiti legali o
  normativi,

* tracciamento delle cifre decimali significative, oppure

* applicazioni in cui l'utente si aspetta che i risultati
  corrispondano ai calcoli fatti a mano.

Ad esempio, calcolare una tassa del 5% su una tariffa telefonica di 70
centesimi dà risultati diversi in decimale a virgola mobile e binario
a virgola mobile. La differenza diventa significativa se i risultati
vengono arrotondati al centesimo più vicino:

   >>> from decimal import *
   >>> round(Decimal('0.70') * Decimal('1.05'), 2)
   Decimal('0.74')
   >>> round(.70 * 1.05, 2)
   0.73

Il risultato "Decimal" mantiene uno zero finale, inferendo
automaticamente una significatività a quattro posti dai fattori
moltiplicativi con significatività a due posti. Decimale riproduce la
matematica fatta a mano ed evita problemi che possono sorgere quando
la virgola mobile binaria non può rappresentare esattamente quantità
decimali.

La rappresentazione esatta permette alla classe "Decimal" di eseguire
calcoli modulo e test di uguaglianza che sono inadatti per la virgola
mobile binaria:

   >>> Decimal('1.00') % Decimal('.10')
   Decimal('0.00')
   >>> 1.00 % 0.10
   0.09999999999999995

   >>> sum([Decimal('0.1')]*10) == Decimal('1.0')
   True
   >>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 == 1.0
   False

Il modulo "decimal" fornisce aritmetica con precisione quanto
richiesta:

   >>> getcontext().prec = 36
   >>> Decimal(1) / Decimal(7)
   Decimal('0.142857142857142857142857142857142857')
