Il capitolo 5 del libro continua lo sviluppo dell’applicazione MultiDoc introducendo il concetto di first class functions che verranno usate come callback per rispondere ad eventi (sia GUI che del parser XML ad eventi).
Il libro si concentra anche sull’aggiunta delle funzionalità di caricamento e salvataggio di documenti multipagina in XML usando System.Xml
.
I post precedenti sono: applicazioni e design pattern, oggetti .NET e IronPython, introduzione a Python, introduzione al libro e il preambolo
First class function
Le funzioni si definiscono più o meno first class se sono trattate come normali oggetti, ossia possono essere passate come argomenti ad altre funzioni o metodi, possono essere assegnate a variabili, possono essere usate come valori di ritorno ecc. ecc.
Python tra le altre cose permette anche le funzioni innestate.
Le funzioni di ordine superiore invece sono funzioni che prendono funzioni come argomento o ritornano funzioni come valore. Tali funzioni (higher order functions) permettono di implementare facilmente pattern come lo strategy, il visitor e qualsiasi meccanismo di callback.
Le funzioni in Python fanno parte della categoria dei callable perché posseggono l’attributo __call__
per default. Data la versatilità di Python è abbastanza scontato dire che è possibile rendere callable anche altri tipi di oggetti:
>>> def f(): return 1
...
>>> f.__call__()
1
>>> class Foo(object):
... def __call__(self):
... return 1
...
>>> foo = Foo()
>>> foo()
1
Le higher order functions sono anche impiegate per creare decoratori. I decoratori in Python sono un costrutto che potenzia una funzione passatagli come argomento.
Breve nota: uso spesso il termine funzione invece che quello di metodo per il semplice fatto che i metodi in Python sono particolari funzioni e data la dinamicità del linguaggio è possibile assegnare funzioni a classi o istanze dinamicamente con un banale accorgimento (sostanzialmente l’istanza esplicita come primo argomento).
I decoratori sono implementati solitamente come funzioni che prendono una funzione come argomento (sebbene possano essere implementati anche come classi callable). La sintassi per applicarli è la seguente:
>>> def fun(): return "fun" # defines a function
...
>>> def wrapper(f): # defines the decorator
... def inner():
... print "Wrapper"
... return f()
... return inner
...
>>> wrapped = wrapper(fun) # wrap the function with the decorator
>>> fun()
'fun'
>>> wrapped()
Wrapper
'fun'
>>> @wrapper # use the @syntax to wrap automatically
... def fun2(): return "fun2"
...
>>> fun2()
Wrapper
'fun2'
La sintassi @decorator
è zucchero sintattico ed equivale grossomodo a fun = decorator(fun)
.
Un esempio fatto dal libro è un decoratore che controlla la presenza di un valore nullo negli argomenti della funzione decorata:
>>> def checkarguments(f):
... def decorated(*args):
... if None in args:
... raise TypeError("Invalid argument")
... return f(*args)
... return decorated
...
>>> class Bar(object):
... @checkarguments # apply the decorator
... def method(self, arg1, arg2):
... return arg1 + arg2
...
>>> b = Bar()
>>> b.method(1, 2)
3
>>> b.method(1, None) # None it's not allowed
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in decorated
TypeError: Invalid argument
>>>
Come potete vedere l’applicazione del decoratore non modifica affatto l’uso normale della classe. La sintassi *args
(che già conoscete se avete letto il tutorial ;-) è una delle finezze di Python. La funzione che dichiara i suoi argomenti in tal modo riceve una tupla contenente tutti gli argomenti passati dal chiamante, indipendentemente dal loro tipo o numero.
Gestione XML
Il capitolo da qui in poi introduce l’uso delle funzioni first class all’interno del dominio della gestione dei documenti XML per l’applicazione MultiDoc. La struttura di tali documenti è qualcosa tipo:
<?xml version="1.0" encoding="utf-8"?>
<document>
<page title="The page title">This is the contents of the first page.</page>
<page title="Page Two">This is another page, as beautiful as the first.</page>
</document>
L’autore utilizza XmlWriter
per creare tali documenti con l’ausilio della classe StringBuilder
. Fa anche notare una stranezza: il serializzatore di XmlWriter
ignora in toto la dichiarazione dell’encoding se deve scrivere su uno StringBuilder
. Sostanzialmente la dichiarazione dell’XML è sempre in UTF-16:
>>> import clr
>>> clr.AddReference('System.Xml')
>>> from System.Xml import XmlWriter, XmlWriterSettings
>>> settings = XmlWriterSettings()
>>> settings.Indent = True
>>> settings.Encoding
<System.Text.UTF8Encoding object at 0x000000000000002C [System.Text.UTF8Encoding
]>
>>> from System.Text import StringBuilder
>>> doc = StringBuilder()
>>> w = XmlWriter.Create(doc, settings)
>>> w.WriteStartDocument()
>>> w.Flush()
>>> doc.ToString()
'<?xml version="1.0" encoding="utf-16"?>'
La parte di caricamento viene invece sviluppata usando XmlReader
e una serie di funzioni come callback passate in dizionari.
Infine MultiDoc viene suddivisa in più file per dimostrare come sia semplice organizzare il codice in IronPython.