# 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:/python312/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')