Python

Da Skypedia.
Python-logo.jpg
Python è un linguaggio di programmazione di alto livello, interpretato, orientato agli oggetti e con una semantica dinamica. Il suo alto livello di costrutti nelle strutture dati, combinato con la tipizzazione ed i binding dinamici, lo rende molto interessante per lo sviluppo rapido di applicazioni, così come per l'utilizzo come linguaggio di scripting o come linguaggio collante per connettere assieme componenti esistenti. La sintassi semplice e facile da apprendere di Python enfatizza la leggibilità e riduce il costo di mantenimento dei programmi. Python supporta moduli e package, incoraggiando così la programmazione modulare ed il riutilizzo del codice. L'interprete Python e l'estesa libreria standard sono disponibili sia come sorgente che in forma binaria, senza costo per le maggiori piattaforme, possono inoltre essere ridistribuiti liberamente.

Programmare in Python

Indentazione

In Python non si utilizza l'indentazione C-like con le parentesi graffe, bensì si rientra a capo nel corpo di un ciclo o una funziona con due o più spazi.


self

l primo argomento di ogni metodo di una classe, incluso __init__, è sempre un riferimento all'istanza corrente della classe. Per convenzione, questo argomento viene sempre chiamato self. Nel metodo __init__, self si riferisce all'oggetto appena creato; negli altri metodi della classe, si riferisce all'istanza da cui metodo è stato chiamato. Per quanto necessitiate di specificare self esplicitamente quando definite un metodo, non lo specificate quando chiamate il metodo; Python lo aggiungerà per voi automaticamente. Se dovete costruire (inizializzare con __init__) un oggetto vi serviranno degli attributi che potrete mantenere visibili all'interno del costruttore definendoli normalmente o renderli attributi di istanza di una classe con self.

Esempio:

 class Oggetto:
   def __init__():
     attributoScopeInit = "Vengo visto solo qui"
     self.attributoScopeClasse = "Sono visibile anche al di fuori di init"

attributoScopeClasse può essere richiamato anche al di fuori di init con self.attributoScopeClasse o al di fuori della classe, una volta istanziato l'oggetto, con Oggetto.attributoScopeClasse

Variabili private

C'è un supporto limitato agli identificatori privati di una classe. Qualsiasi identificatore nella forma __spam (come minimo due trattini bassi all'inizio, al più un trattino basso in coda) viene rimpiazzato, a livello di codice eseguito, con _nomeclasse__spam, dove nomeclasse è il nome della classe corrente, privato dei trattini di sottolineatura all'inizio. Questa mutilazione viene eseguita senza riguardo alla posizione sintattica dell'identificatore, quindi può essere usata per definire istanze, metodi e variabili di classe private, come pure globali, e anche per memorizzare variabili d'istanza private per questa classe, sopra istanze di altre classi. Potrebbe capitare un troncamento, nel caso in cui il nome mutilato fosse più lungo di 255 caratteri. Al di fuori delle classi, o quando il nome della classe consiste di soli trattini di sottolineatura, non avviene alcuna mutilazione.


Lambda

Le forme lambda (espressioni lambda) hanno la stessa posizione sintattica delle espressioni. Sono una scorciatoia per creare funzioni anonime; gli argomenti delle espressioni lambda producono oggetti funzione, lambda arguments: expression. L'oggetto senza nome si comporta come una funzione definita con

 def name(arguments):
     return expression

Queste sono in pratica funzioni anonime (funzioni non legate ad un nome), da usare immediatamente nelle espressioni. Bisogna notare che la forma lambda è solo una scorciatoia per una definizione semplificata di una funzione; una funzione definita in un'istruzione def può essere fornita o assegnata ad un altro nome, proprio come una funzione definita nella forma lambda. La forma def è attualmente la più potente da quando permette l'esecuzione di istruzioni multiple.

Notare la sintassi abbreviata: non ci sono parentesi attorno alla lista degli argomenti e la parola chiave return è mancante (implicita, poiché l'intera funzione può essere composta da una sola espressione). Inoltre, la funzione lambda non ha nome, ma può essere chiamata attraverso la variabile a cui è stata assegnata. Potete usare anche una funzione lambda senza assegnarla ad una variabile. Non è la cosa più utile del mondo, ma dimostra che una funzione lambda equivale ad una funzione “inline” del linguaggio C.

 >>> g = lambda x: x*2  #La funzione lambda prende un argomento e lo moltiplica per 2 restituendoci il risultato (return x*2)
 >>> g(3)
 6
 >>> (lambda x: x*2)(3) #qui è come se quello che era il nome di una funzione del tipo NomeFunzione(3) fosse decaduto e ci rimanesse solo quello che la funzione esegue
 6

Un ulteriore esempio sulle forme lambda e gli oggetti funzione:

>>> def make_incrementor(n):
...     return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43

Polimorfismo in Python

Una funzione che accetta tipi di dato diverso è chiamata polimorfica. A tal proposito riportiamo la regola fondamentale del polimorfismo: Se tutte le operazioni all'interno di una funzione possono essere applicate ad un tipo di dato allora la funzione stessa può essere applicata al tipo.

Python supporta un tipo particolare di polimorfismo, detto polimorfismo basato su signature. Quando il programmatore utilizza un oggetto, non si deve preoccupare di quale sia il suo tipo, ma deve limitarsi a pensare a quali operazioni esso supporta. L’insieme delle operazioni supportate da un oggetto costituisce la sua signature. Se due oggetti hanno la stessa signature, posso usarli intercambiabilmente senza che il codice debba essere modificato, anche se non hanno alcuna relazione di tipo. Questo tipo di polimorfismo è simile a quello che il C++ offre con i template, con la differenza che in Python avviene a runtime.

Il polimorfismo basato su signature offre numerosi vantaggi, tra cui il fatto che il codice scritto è intrisecamente generico, cioè può operare su qualsiasi oggetto, anche se di tipo diverso. Altri comuni linguaggi (es. C++, Java, C#) offrono invece un polimorfismo basato su interfaccia: questo significa che una funzione può operare solo su oggetti di un certo tipo o di un tipo derivato da esso.

Overloading e Overriding

Il meccanismo di overriding è concettualmente molto diverso da quello di overloading (o polimorfismo), e non deve essere confuso con esso.

L'overloading consente di definire in una stessa classe più metodi aventi lo stesso nome, ma che differiscano nella firma, cioè nella sequenza dei tipi dei parametri formali. È il compilatore che determina quale dei metodi verrà invocato, in base al numero e al tipo dei parametri attuali. L'overriding, invece, consente di ridefinire un metodo in una sottoclasse: il metodo originale e quello che lo ridefinisce hanno necessariamente la stessa firma, e solo a tempo di esecuzione si determina quale dei due deve essere eseguito.

Terminologia generica della gerarchia del codice Python

Oggetto

Tutto quello a cui ci possiamo riferire con un nome è un oggetto: numeri, stringhe, liste, funzioni, classi, ecc. sono tutti oggetti. Tutti gli oggetti sono first class value: ad esempio possono essere passati come argomenti alle funzioni. Nell'implementazione più utilizzata di Python, scritta in C, ogni oggetto è rappresentato da una struct contenente riferimento, tipo e valore dell'oggetto istanziato in Python.

Le funzioni Python sono oggetti

Ogni funzione in Python è a tutti gli effetti un oggetto, quindi manipolabile in modo del tutto simile a una classe, anche se vi sono alcuni aspetti che possono far confondere il programmatore abituato a trattare i metodi in un linguaggio come Java. Un esempio vale più di mille parole:

def shout(word="yes"):
    return word.capitalize()+"!"

print shout()
# outputs : 'Yes!'

# As an object, you can assign the function to a variable like any
# other object 

scream = shout

# Notice we don't use parentheses: we are not calling the function, we are
# putting the function "shout" into the variable "scream". 
# It means you can then call "shout" from "scream":

print scream()
# outputs : 'Yes!'

# More than that, it means you can remove the old name 'shout', and
# the function will still be accessible from 'scream'

del shout
try:
    print shout()
except NameError, e:
    print e
    #outputs: "name 'shout' is not defined"

print scream()
# outputs: 'Yes!'

Quello che è stato fatto sopra è stato definire una funzione shout e assegnare poi il riferimento, quindi l'oggetto funzione, a una variabile che rappresenta un altro indirizzo di memoria. Gli oggetti verranno poi caricati a run-time nella memoria dinamica (heap) e dunque potranno essere deallocati per consentire al garbage collector di ripulire quelle posizioni nel momento più opportuno per far spazio a nuovi oggetti. Nell'esempio si procede alla rimozione del riferimento a shout tramite "del", ma prima è stata effettuata una copia del riferimento tramite scream. L'oggetto funzione è dunque richiamabile (callable) utilizzando la sintassi scream(), che risulta ancora valida.

Modulo

Un modulo è l'unità di base di codice riusabile in Python: un blocco di codice importato da altro codice. In questo contesto ci interessano tre tipologie di moduli: moduli Python puri, moduli di estensione e package.

Modulo in Python puro

Un modulo scritto in Python e contenuto in un singolo file .py (e possibilmente associato con un file .pyc e/o .pyo). Alcune volte viene citato come modulo puro.

Modulo di estensione

Un modulo scritto nel linguaggio di basso livello dell'implementazione Python: C/C++ per Python, Java per Jython. Tipicamente contenuto in un singolo file precompilato caricabile dinamicamente, per esempio un file oggetto condiviso (.so) per le estensioni Python in ambiente Unix, una DLL (indicata dal file con estensione .pyd) per le estensioni Python in Windows, o un file di classe Java per le estensioni Jython.

N.B. Correntemente, le Distutils gestiscono solo estensioni C/C++ per Python.

Package

Un modulo che contiene altri moduli: tipicamente è contenuto in una directory del filesystem e si distingue dalle altre directory dalla presenza di un file __init__.py.

root package

L'apice della gerarchia dei package. (Questo non è realmente un package, in quanto non possiede un file __init__.py, ma in qualche modo lo si doveva pure chiamare). La stragrande maggioranza della libreria standard è nel package principale, come ci sono tanti piccoli, autonomi, moduli di terze parti, che non dipendono da una più grande collezione di moduli. Diversamente dai package regolari, i moduli nel package principale possono essere suddivisi in tante directory: infatti, ogni directory elencata in sys.path aggiunge moduli al package principale.


Distribuzione di moduli

Una collezione di moduli Python distribuiti insieme come una singola risorsa scaricabile e da considerarsi installabile in blocco. Esempi di alcune delle distribuzioni di moduli molto conosciute sono Numeric Python, PyXML, PIL (la libreria per la gestione delle immagini in Python) o mxBase. (Questa potrebbe essere chiamata package, solo che sono termini già sfruttati in contesto Python: una singola distribuzione di moduli, può contenere zero, uno o più package Python.)

Distribuzione di moduli in puro Python

Una distribuzione di moduli che contiene solo moduli in puro Python e package. Qualche volta vengono chiamati anche ``distribuzione pura.

Distribuzione di moduli non in puro Python

Una distribuzione di moduli che contiene come minimo un modulo di estensione. Qualche volta viene anche chiamata ``distribuzione non pura.

Distribuzione root

La directory principale del proprio albero di sorgenti (o distribuzione di sorgenti); la directory dove c'è setup.py. Generalmente setup.py viene lanciato da questa directory.


Python decorators

I decorators Python sono dei wrapper di funzioni; il che significa che "decorano" la funzione prima e dopo l'esecuzione, senza il bisogno di modificare il codice della funzione stessa. Essi sono la variante pythonic del decorator design pattern. I decorators aggiungono a tutti gli effetti quel syntactic sugar che semplifica la definizione e il passaggio di oggetti funzione che inglobino altre funzioni al loro interno. Essi forniscono un modo semplice di utilizzare le Higher order functions e dunque avvicinano parzialmente Python alla programmazione funzionale.


Considerazioni sull'utilizzo dei decorators

  • I decorators sono stati introdotti a partire da Python 2.4, quindi prima di utilizzarli è bene accertarsi che si abbia a disposizione l'interprete >=2.4 sulla macchina target
  • I decorators rallentano le chiamate di funzione, se le prestazioni sono un parametro fondamentale è bene riflettere bene prima di utilizzarli
  • Una volta decorata una funzione non si può tornare indietro facilmente. La progettazione dello sviluppo coi decorators richiede lungimiranza
  • I decorators inglobano (wrap) le funzioni e questo rende più complesso il debug

Python 2.5 introduce il modulo functools che contiene functools.wraps, tramite il quale è possibile copiare il nome, il modulo e il docstring di qualsiasi wrapper. In questo modo viene reso più semplice il debug delle decorated functions. La cosa divertente è che functools.wraps è esso stesso un decorator.

Distutils

Con l'introduzione dell'utility delle distribuzioni Python (in breve Distutils) in Python 2.0 potrete aggiungere nuove funzionalità alla vostra installazione Python sotto forma di moduli di terze parti o distribuire i vostri moduli. Ci si potrebbe trovare di fronte al sorgente della distribuzione rilasciato dall'autore/manutentore del modulo. Installare da un sorgente non è troppo difficile, purché i moduli siano pacchettizzati nel modo convenzionale. Questo documento tratta la compilazione e l'installazione dei moduli partendo da un sorgente standard di una distribuzione.

Installare un modulo distribuito con Distutils

Se scaricate un modulo sorgente di una distribuzione, potrete sapere molto velocemente se è stato pacchettizzato e distribuito nel modo standard, per esempio usando Distutils. Per prima cosa osservate come sia il nome della distribuzione che il numero di versione verranno evidenziati nel nome dell'archivio scaricato, per esempio foo-1.0.tar.gz o widget-0.9.7.zip. Successivamente l'archivio si scompatterà all'interno di una directory chiamata in modo simile: foo-1.0 o widget-0.9.7. In aggiunta, la distribuzione conterrà uno script di setup setup.py ed un file chiamato README.txt o solamente README, che dovrebbe spiegare come avviene la compilazione e l'installazione del modulo della distribuzione ed infine le modalità per eseguirlo

python setup.py install

Se avete fatto tutte queste cose, allora sarete pronti a conoscere come costruire ed installare il modulo che avete appena scaricato: eseguite il comando appena menzionato.


Distribuire un modulo con Distutils

Come sviluppatore, le vostre responsabilità (al di là della scrittura di un solido, ben documentato e ben testato codice, ovviamente...) sono:

  • scrivere uno script di setup (per convenzione setup.py)
  • (facoltativo) scrivere un file di configurazione di setup
  • creare una distribuzione di sorgenti
  • (facoltativo) creare una o più distribuzioni precompilate (binari)

setup.py

Questo script viene scritto direttamente in Python e possiamo fargli compiere una moltitudine di operazioni differenti, stando però attenti alle operazioni complesse che potrebbero dare problemi su differenti installazioni di Python. Se tutto quello che si vuole è distribuire un modulo chiamato pippo, contenuto in un file pippo.py, lo script di setup potrebbe essere tanto semplice quanto quello che segue:

 # importiamo setup da distutils
 from distutils.core import setup
 # richiamiamo il metodo setup (statico) e passiamo nome, versione e modulo
 setup(name='pippo',
      version='1.0',
      py_modules=['foo'],
      )
  • la maggior parte delle informazioni che vengono indicate a Distutils vengono fornite come argomenti chiave alla funzione setup()
  • questi argomenti chiave rientrano in due categorie: metadata di package (nome, numero di versione) ed informazioni circa il contenuto del package (in questo caso, una lista di puri moduli Python)
  • i moduli vengono specificati per nome di modulo, non per nome di file (la medesima cosa verrà fatta per package ed estensioni)
  • è bene fornire un insieme di metadata adeguati, tra cui, in particolare, il nome, l'indirizzo email ed un indirizzo URL per il progetto

Vediamo un esempio più complesso:

 #!/usr/bin/env python
 from distutils.core import setup
 # Inseriamo adesso anche descrizione,autore, email, url e i packages
 setup(name='Distutils',
      version='1.0',
      description='Python Distribution Utilities',
      author='Greg Ward',
      author_email='gward@python.net',
      url='http://www.python.org/sigs/distutils-sig/',
      packages=['distutils', 'distutils.command'],
     )

Ulteriori informazioni sui metadata

Identificare i numeri di versione del modulo

La codifica delle informazioni sulla versione è un'arte essa stessa. I package Python generalmente sono conformi al formato di versione major.minor[.patch][sub]. Il numero maggiore è 0 per la release iniziale e sperimentale del software. Viene incrementato per rilasci che rappresentano un punto importante di svolta del package. Il numero minore viene incrementato quando importanti, nuovi aggiornamenti vengono aggiunti al package. Il numero di patch aumenta quando vengono rilasciati dei bug-fix. Elementi aggiuntivi per informazioni varie sulle versioni vengono talvolta usati per rappresentare sottoversioni. Queste vengono solitamente denominate "a1, a2,...,aN) (per versioni alpha, dove funzionalità e API possono cambiare), "b1,b2,...,bN) (per versioni beta, che sono solamente dei bug-fix) e "pr1,pr2,...,prN" (per versioni di testing di pre-release della versione finale).

Creazione della distribuzione dei sorgenti

Una volta creato lo script setup.py si procederà alla creazione della distribuzione dei sorgenti con il seguente comando

python setup.py sdist

che creerà un file archivio (per esempio un tarball in Unix, un file ZIP in Windows) contenente lo script di setup setup.py ed il proprio modulo (i.e.pippo.py). Il file archivio verrà chiamato pippo-1.0.tar.gz (o .zip) e scompattato in una directory pippo-1.0. Se un utente finale desidera installare il modulo, le uniche cose che deve fare è scaricare pippo-1.0.tar.gz, scompattarlo, e nella directory pippo-1.0 eseguire:

python setup.py install

che completa la copia di pippo.py nella giusta directory per i moduli di terze parti nella propria installazione Python.

Distribuzione installer eseguibili per Windows

Per creare la distribuzione per Windows non bisogna far altro che dare il seguente comando:

python setup.py bdist_wininst

Che creerà degli installer eseguibili (pippo-1.0.win32.exe).

Distribuzione RPM

Basta dare il comando

python setup.py bdist_rpm

Che creerà un pacchetto RPM per la propria distribuzione. Per sapere quali formati sono disponibili per la propria distribuzione basta dare il seguente comando:

python setup.py bdist --help-formats

Pacchettizzare il modulo per una data distribuzione

Non tutti gli sviluppatori di moduli hanno accesso ad un vasto numero di piattaforme diverse tra loro, così non ci si può aspettare che possano creare una moltitudine di distribuzioni precompilate e anche voi di certo non lo farete. Se l'applicazione da voi distribuita avrà successo si spera che un gruppo di intermediari, chiamati packagers, coprano questa necessità.




PyGTK

La gerarchia delle widget

La classe madre in pratica è gtk.Object da cui deriva la classe gtk.Widget e poi gtk.Container e gtk.Window. Per avere una visione ad albero della gerarchia si può far riferimento al PyGTK 2.0 Tutorial.

Il Boxing del container

In una gtk.Window si può inserire al più un solo widget. Per inserirne di più basta affidarsi al sistema del Boxing con la seguente gerarchia:

+−− gobject.GObject
 +−− gtk.Object
   +−− gtk.Widget
     +−− gtk.Container
       +−− gtk.Box
         +−− gtk.VBox
         +−− gtk.HBox

I packing box non sono altro che una serie di scatole invisibili in cui inserire i widget, possiamo utilizzare due tipi differenti di box, orizzontali gtk.Hbox() e verticale gtk.Vbox, entrambi discendono dal widget padre gtk.Box.

# Il costruttore 
gtk.Box(homogeneous=FALSE, spacing=0)

Ovviamente gtk.Box dovrà essere sostituito con gtk.VBox oppure gtk.HBox.

  • homogeneous determina se lo spazio allocato ai widget all'interno del box dovrà essere omogeneo, non nelle due direzioni, ma in verticale per gtk.VBox ed ovviamente in orizzontale per gtk.HBox.
  • spacing determina lo spazio aggiuntivo in pixel da applicare tra un widget e i suoi vicini all'interno dei gtk.Box.

Con la funzione pack_start() o pack_end() i widget saranno inseriti nei gtk.Box, il prototipo del metodo valido per gli oggetti gtk.Box e' nella forma:

 def pack_start(child, expand=TRUE, fill=TRUE, padding=0)
  • child: il widget da inserire in gtk.Box
  • expand: se TRUE, il widget ricevera' spazio aggiuntivo all'interno del box, pari al widget più grande in esso contenuto.
  • fill: se TRUE, il widget si espanderà in modo da avere uguale dimensione al widget più grande
  • padding: è lo spazio in pixel tra il widget e i suoi vicini, questo spazio si sommerà a quello eventualmente già allocato quando e' stato costruito il box.

Per posizionare i contenuti al loro interno si utilizza pack_start(). Il pack_end() opera al contrario posizionando i contenuti in maniera inversa.

La gerarchia dei bottoni

+−− gobject.GObject
 +−− gtk.Object
   +−− gtk.Widget
     +−− gtk.Container
       +−− gtk.Bin
         +−− gtk.Button
           +−− gtk.ColorButton
         +−− gtk.FontButton
         +−− gtk.ToggleButton
           +−− gtk.CheckButton
             +−− gtk.RadioButton

I segnali emessi da un Button

  • enter: il puntatore entra nel bottone
  • leave: il puntatore esce dal bottone
  • clicked: il bottone viene cliccato
  • pressed: il bottone viene premuto
  • released: il bottone viene rilasciato

Una finestra riutilizzabile

Nell'ambito del software design non può di certo essere sottovalutato il riutilizzo delle proprie classi e non fa eccezione di certo la programmazione orientata alla creazione di toolkit grafici. Vediamo una Window modificata per i nostri scopi da riutilizzare con un semplice import:

# Ereditiamo da Window
class Finestra(gtk.Window):
    #passiamo al costruttore larghezza altezza e titolo
    def __init__(self, larghezza, altezza, titolo):
        gtk.Window.__init__(self) # utilizziamo il costruttore della superclasse
        self.set_title(titolo)
        self.set_default_size(larghezza, altezza)
        self.connect("delete_event", self.delete_event)
        self.connect("destroy", self.destroy)

    def delete_event(self, widget, event, data=None):
        return gtk.FALSE # con FALSE chiudiamo

    def destroy(self, widget, data=None):
        return gtk.main_quit()

Usare Glade per creare interfacce pyGTK

Glade è un RAD per la creazione di interfacce GUI direttamente con componenti visuali. Genera dei file XML da importare nel codice Python e si appoggia a delle librerie C per la traduzione nel corrispettivo GTK delle stesse. Basta dunque installare glade e le libglade per manipolare e costruire le interfacce in maniera facile e veloce. Il tutto ovviamente necessita di accorgimenti perché tutto funzioni correttamente.

Vediamo cosa serve via codice per l'importazione dell'interfaccia creata in Glade:

#!/usr/bin/env python

#### IMPORTIAMO LE LIBRERIE NECESSARIE
import sys
try:
 	import pygtk
  	pygtk.require("2.0")
except:
  	pass
try:
	import gtk
  	import gtk.glade
except:
	print("GTK Not Availible")
	sys.exit(1)

class HellowWorldGTK:
	"""This is an Hello World GTK application"""

	def __init__(self):
		
		#Set the Glade file
		self.gladefile = "pyhelloworld.glade"  
	        self.wTree = gtk.glade.XML(self.gladefile) 
		
		#Get the Main Window, and connect the "destroy" event
		self.window = self.wTree.get_widget("MainWindow")
		if (self.window):
			self.window.connect("destroy", gtk.main_quit)

if __name__ == "__main__":
	hwg = HellowWorldGTK()
	gtk.main()

Abbiamo importato il file XML generato da glade con la chiamata gtk.glade.XML. Si può creare direttamente in Glade una serie di eventi da intercettare come segnali e da interpretare poi via codice con le relative callbacks tramite una funzione che connette automaticamente questi segnali ai relativi metodi tramite la funzione gtk.glade.XML.signal_autoconnect

Risorse