Con il capitolo 4 comincia la seconda parte del libro che si snoda intorno allo sviluppo di una applicazione Windows Forms chiamata MultiDoc. I sorgenti dei vari capitoli sono disponibili online.
Le puntate precedenti sono: oggetti .NET e IronPython, introduzione a Python, introduzione al libro e il preambolo.
Il libro in questo capitolo mostra come struttura una applicazione MVC usando anche alcuni design pattern in Python.
Modellazione dei dati e duck typing
Python usa i protocol invece delle interfacce per creare ricche strutture dati o aggiungere funzionalità a strutture esistenti. Ad esempio l’interfaccia IList
equivale al sequence protocol come l’interfaccia IDictionary equivale al mapping protocol.
Per supportare un dato protocollo in Python bisogna implementare dei metodi speciali chiamati anche magic methods (facilmente identificabili perché hanno un doppio underscore in testa e in coda, es: __ge__
).
Segnalo che Python 3.0 ha aggiunto anche una serie di classi astratte, chiamate ABC, per facilitare l’implementazione di protocolli.
I metodi __getitem__
, __setitem__
, __delitem__
sono impiegati per implementare i protocolli sequence e mapping, difatti l’operazione: x = something[key]
equivale in tutto e per tutto a x = something.__getitem__(key)
e così via.
Oltre ai suddetti esistono vari altri protocolli: per i numeri, per l’iterazione, per i confronti booleani, per la serializzazione e così via.
La chiave per il funzionamento di tali protocolli è il duck typing: in sostanza se un oggetto risponde ad un metodo o ad un attributo richiesto (se si comporta come un’anatra) allora è possibile usarlo come un altro oggetto (allora è come se fosse un’anatra). In poche parole è definibile anche come la versione tipizzata dinamicamente delle interfacce:
>>> class SampleMapping(object):
... def __init__(self):
... self._store = dict()
... def __getitem__(self, key):
... value = self._store[key]
... print "Got %s" % value
... return value
... def __setitem__(self, key, value):
... self._store[key] = value
... print "%s = %s" % (key, value)
... def __delitem__(self, key):
... del self._store[key]
... print "Removed %s" % key
...
>>>
>>> m = SampleMapping()
>>> m['something'] = 'foo'
something = foo
>>> m.__setitem__('something_too', 'bar')
something_too = bar
>>> m['something']
Got foo
'foo'
>>> m.__getitem__('something_too')
Got bar
'bar'
>>> del m['something']
Removed something
>>> m.__delitem__('something_too')
Removed something_too
>>>
SampleMapping
è utilizzabile in tutti i contesti di indexing. Avrete notato come questo permette anche di implementare una qualsiasi logica di controllo o tracking dell’accesso alle chiavi memorizzate. Proxy pattern è la mia prima associazione mentale ;-)
Model View Controller in IronPython
Da qui in poi il libro sviluppa il primo esempio funzionante dell’applicazione MultiDoc. La scelta di usare Windows Forms a me sembra azzeccatissima perché permette di mostrare come strutturare una applicazione in maniera disaccoppiata e mostra varie funzionalità di .NET e di IronPython.
L’applicazione MultiDoc è sostanzialmente un editor multipagina con una interfaccia a tab con funzionalità di salvataggio e caricamento di documenti. Date un’occhiata ai sorgenti del capitolo 4 eseguendoli per farvi un’idea.
L’applicazione è strutturata da una parte di view (MainForm
), un model (Document
e Page
) ed un controller (TabController
). Il model è un documento con una sequenza ordinata di pagine, dunque implementato come un container:
class Document(object):
def __init__(self, fileName=None):
self.fileName = fileName
self.pages = []
self.addPage()
def addPage(self, title='New Page'):
page = Page(title)
self.pages.append(page)
def __getitem__(self, index):
return self.pages[index]
def __setitem__(self, index, page):
self.pages[index] = page
def __delitem__(self, index):
del self.pages[index]
class Page(object):
def __init__(self, title):
self.title = title
self.text = ''
Il controller invece si occupa di connettere il model alla view tenendole sincronizzate.
Le azioni di save, save as e load vengono implementate usando il Command pattern. Una scelta azzeccatissima secondo me, dato il tipo di applicazione.
Da questo capitolo saltano fuori anche vari consigli durante gli esempi.
L’autore suggerisce di scegliere tra le funzionalità di accesso ai file di Python e quelle di .NET in base al gusto o alla necessità (portabilità del codice ecc ecc). Vediamo un esempio Python:
>>> f = open('sample.txt', 'w')
>>> f.write('hello world\n')
>>> f.close()
>>> f = open('sample.txt', 'r')
>>> f.read()
hello world
>>> f.close()
Il tutto è abbastanza chiaro, comunque: si apre un file chiamato sample.txt in scrittura, viene scritta una linea di testo e viene chiuso. In seguito viene riaperto in lettura e poi chiuso. È ovviamente possibile aprire i file anche in modalità binaria (usando rb o wb al posto di w e r).
Come è già stato detto precedentemente le eccezioni .NET sono mappate sulle eccezioni Python:
>>> from System.IO import StreamWriter
>>> filename = r'b:\nonexistent.txt'
>>> try:
... writer = StreamWriter(filename)
... except IOError, e:
... print "Something went wrong"
... print "Exception message: %s" % str(e)
...
Something went wrong
Exception message: Impossibile trovare una parte del percorso 'b:\nonexistent.txt'.
IOError
è l’equivalente Python di IOException
mentre str(e)
trasforma l’eccezione in stringa (vedi ToString()
).
Verso la fine mostra anche come attaccare gli eventi ai controlli della form e sfruttando le funzioni anonime (lambda functions) evita di creare funzioni inutili per gli event handler:
>>> button.Click += lambda sender, event: command.execute()