Salta al contenuto principale

uno sguardo al python, seconda parte.

donato taddei in privato, 05\02\2008, h. 23.50.

Nota.

Ed ora, un po' di pubblicità

:

A scanso di confusioni si precisa che l'autore di questo testo ha adottato la seguente convenzione, che quindi vale anche per la prima parte:

le righe contrassegnate da ">>>" rappresentano  ciò che viene immesso al  prompt di python;

le righe che iniziano con scritte racchiuse tra parentesi angolari "<...>" rappresentano invece la  risposta dell'interprete a tali comandi, non hanno cioè
niente a che vedere con la sintassi  del python.  

In questa seconda parte vengono illustrati  concetti riguardo ai cosiddetti  moduli e sono di grande importanza per comprendere il significato dei più di
venti moduli presenti nel pacchetto nvda, segnatamente nelle cartelle appmodule e synthdrivers. 

Affronta altresì uno degli agmenti più indigesti degli attuali ambienti di programmazione, a prescindere dal linguaggio:

moduli, oggetti, classi, ereditarietà sui quali sarà necessario  tornare in seguito, con qualche noterella sulla loro storia evolutiva.

Per ora, e sempre a scanso di equivoci e confusioni, mi limiterò a una disambiguazione semantica:

programmazione ad oggetti e programmazione orientata agli oggetti, nel senso in cui sono  normamente utilizzati, sottengono concetti non completamente sovrapponibili:

i programmatori di  applicazioni windows chiamano oggetti qualsiasi cosa: finestgre, caselle, elementi grafici ecc.

Nvda per esempio chiama navigatore ad oggetti il dispositivo con cui più o meno esplora le applicazioni sullo schermo.

Sarebbe forse pù esatto  chiamarli risorse.

Anche la programmazione windows conosce e manipola oggetti in senso proprio:

le interfacce ole, con cui ad esempio si gestisce da programma un foglio di excels una pagina web o un documento di word sono appunto degli oggetti in senso
proprio.

Dunque Guido Ruggeri usa massicciamente oggetti nel suo winguido.

Cose come direcrt x, e gli altri sistemi di script che windows mette a disposizione (wscript ecc.) manipolano oggetti in senso proprio. 

Java, perl, pytho, e il loro fratello maggiore c++ sono linguaggi che sono stati inventati appunto per manipolare oggetti.

Da quanto apppreso in questo testo e affini letti ultimamente devo convenire che tra a questi linguaggi sicuramente il python è quello con la sintassi più
semplice.

Posso infatti confermare per esperienza diretta che la gestione degli oggetti in perl è sicuramente più astrusa, tant'é che ho fin qui evitato quando possibile
di utilizzarli. 

                        Donato Taddei

Uno sguardo al Python - parte seconda  

      In questo articolo completiamo il discorso introduttivo al Python
      esaminandone le caratteristiche più avanzate: classi, ereditarietà ed
      eccezioni. Vedremo che il linguaggio utilizza un modello di OOP molto
      semplice ma potente, che supporta ereditarietà multipla e operator
      overloading. 
      Uno sguardo al Python
      parte seconda
      di Michele Sciabarrà 
      Se l'introduzione di Python della volta scorsa vi è parso interessante,
      questa seconda parte è una buona occasione per approfondirlo. Ovviamente
      non abbiamo alcuna pretesa di essere esaustivi, non potendo trattare un
      completo linguaggio di programmazione in 10 pagine, esempi compresi.
      Tuttavia questi due articoli dovrebbero essere sufficienti a muovere i
      primi passi e decidere se vale la pena continuare. Entriamo subito nel
      vivo esaminando le caratteristiche un po' più avanzate concernenti
      programmazione ad oggetti e la gestione degli errori. Vedremo infatti che
      Python pur nella sua grande semplicità è orientato ad oggetti in maniera
      così decisa che è stato proposto come linguaggio standard per lo scripting
      di oggetti distribuiti CORBA.  
      Istanze
      Abbiamo visto i moduli, ovvero file che contengono definizioni di funzioni
      e altri comandi. Creando un nuovo file, chiamato per esempio modulo.py,
      contenente la definizione di funzione f(), è possibile importarlo con
      import modulo per poi chiamare la funzione f usando la sintassi
      modulo.f(). Per inciso, il file è importabile se la directory che lo
      contiene si trova nel PYTHONPATH. L'impostazione del PYTHONPATH varia da
      sistema a sistema, ma generalmente si tratta di impostare la variabile di
      ambiente PYTHONPATH. Notare che f in questo caso è una funzione, non un
      metodo o altro, anche se per accedervi occorre utilizzare il prefisso
      modulo. Un modulo è infatti un esempio di namespace; gli elementi in un
      namespace sono detti attributi. Per accedere ad un elemento di un
      namespace si utilizza la sintassi namespace.attributo. I builtin (per
      esempio dir) si trovano nel namespace __builtins__., e sono accedibili
      (come caso particolare) anche se non si specifica un prefisso. Un altro
      modo per evitare il prefisso è utilizzare from import
      , ma è meglio non abusare di questa facility per non avere
      problemi di collisioni di nomi.
      Le istanze delle classi sono anche essi dei namespace, anche se si
      comportano in maniera più sofisticata dei moduli. Si tratta in un certo
      senso della naturale evoluzione della programmazione modulare verso la
      programmazione ad oggetti. Nel resto dell'articolo si assumono basi di
      OOP: non spiegheremo che cosa è una classe, una istanza, un costruttore o
      una funzione virtuale, per cui se siete a digiuni di queste nozioni
      potreste incontrare qualche difficoltà nella lettura.
      Abbiamo visto che in Python ci sono svariati tipi di dato, come le
      sequenze, i dizionari, e i moduli. Adesso introdurremo un nuovo tipo di
      dato, ovvero la classe. Prima vediamo come si usano le classi esistenti,
      poi esamineremo come si definiscono nuove classi. Sfruttiamo l'aspetto
      interattivo del Python, che ci consente di imparare cose nuove
      sperimentando con l'interprete a riga di comando. Come esperimento per
      imparare l'uso di oggetti preleveremo ed esamineremo una pagina Web con
      una connessione http, usando la classe HTTP del modulo httplib: 
      >>> import httplib
      >>> httplib.HTTP
     
      >>> h = httplib.HTTP("192.168.1.1")
      >>> h
       
      Innanzitutto notiamo che le classi, come le funzioni, sono contenute
      dentro moduli. In particolare la classe HTTP è contenuta nel modulo
      httplib, per cui occorre importare il modulo (altrimenti tale classe non è
      accessibile). Valutando httplib.HTTP osserviamo che si tratta di un
      oggetto di tipo classe, utilizzabile però in maniera analoga ad una
      funzione. Chiamando la classe, otteniamo un oggetto di tipo istanza della
      classe, come si vede nell'esempio. In pratica la classe è una funzione
      costruttore che produce istanze. L'oggetto istanza così costruito si
      comporta in maniera simile ad un modulo, ovvero come contenitore di
      funzioni che possono essere chiamate. In realtà un oggetto è qualcosa di
      più di un modulo, in quanto mantiene uno stato separato per ogni istanza.
      Quindi la classe è la naturale evoluzione del concetto di modulo: un
      oggetto è un namespace analogamente ad un modulo. La differenza principale
      è che si possono creare istanze diverse dello stesso modulo, ottenendo
      namespace separati con variabili indipendenti. Questo è il punto cruciale.
      I dati contenuti in una istanza di una classe vengono inizializzati
      chiamando una particolare funzione di inizializzazione: __init__. L'ultimo
      elemento importante che differenzia le classi dai moduli è il fatto che su
      di esse si può applicare l'ereditarietà, come vedremo più avanti.
      Riassumendo:
        Namespace: spazio di nomi, contenente attribuiti; agli attributi di un
        namespace si accede con la sintassi namespace.attributo.
        Modulo: namespace, che contiene funzioni e altri elementi, letto da un
        file.
        Classe: una funzione in grado di generare istanze, ovvero un
costruttore.
        Istanza: namespace che contiene funzioni dette metodi; l'istanza
        differisce dal modulo per il fatto che viene creata dinamicamente
        (mentre i moduli sono definiti con dei file) e che gli attributi sono
        indipendenti per ogni istanza, anche se l'istanza viene creata dallo
        stesso costruttore.
        Metodo: funzione appartenente ad una classe, che solitamente modifica le
        variabili dell'istanza a cui appartiene (in altre parole cambia lo stato
        dell'oggetto). 
      Torniamo al nostro esempio, e utilizzando l'istanza di HTTP chiamandone i
      metodi: 
      >>> h.putrequest('GET', '/')
      >>> h.endheaders()
      >>> h.getreply()
      (200, 'OK', )
      >>> f = h.getfile()
      >>> lines = f.readlines()
      >>> lines[0]
      '\012' 
      Di ogni oggetto, per usarlo occorre conoscerne il funzionamento leggendone
      la documentazione. Nel caso che stiamo esaminando adesso, una istanza di
      HTTP invia delle richieste corrispondenti ai comandi HTTP. Questa classe
      in realtà non maschera molto il protocollo http, in quanto occorre
      conoscerlo almeno per sommi capi. In particolare, occorre sapere che la
      richiesta per prelevare una pagina web è GET, seguita dall'URL della
      pagina senza l'indirizzo dell'host. Dopo la richiesta, occorre anche
      aggiungere delle informazioni supplementari che vengono date al Web Server
      utilizzando degli header. Nel nostro caso non inviamo alcuna informazione
      supplementare, completando la richiesta con endheader(), e leggiamo la
      risposta del Web Server. Poiché è OK (codice 200, stringa di messaggio OK,
      altre informazioni nella istanza mimetools.Message) possiamo leggere la
      pagina web tramite un oggetto file per la lettura. Per semplicità,
      leggiamo le righe ponendole in una lista, e ne stampiamo solo la prima. 
      Classi
      Impariamo adesso a creare nuove classi. Nel caso più semplice possiamo
      dichiarare una classe vuota: 
      >>> class Void:
      ... pass
      ...
      >>> x = Void()
      >>> x
      <__main__.Void instance at 85ff80>
      >>> x.a
      Traceback (innermost last):
      File "", line 0, in ?
      AttributeError: a
      >>> x.a=1
      >>> y = Void()
      >>> y.a=2
      >>> print x.a,y.a
      1 2 
      Utilizzando la keyword class abbiamo dichiarato una nuova classe, in
      questo chiamata Void. In una definizione di classe possono esserci comandi
      di qualsiasi genere, che vengono eseguiti durante la definizione della
      classe. I comandi per noi interessanti comunque sono quelli che dichiarano
      attributi, ovvero variabili e funzioni che diventano campi e metodi della
      classe. Gli altri comandi possono servire per esempio a definire dei
      metodi condizionalmente (per esempio in un modo sotto Windows e un altro
      sotto Unix) ma è meglio non abusare di queste possibilità.
      Ad una classe non vengono assegnati una volta e per tutte tutti i suoi
      attributi, ma possono essere aggiunti anche dopo che l'istanza è stata
      creata. Infatti in genere gli attributi vengono aggiunti in fase di
      inizializzazione. Come si vede nel'esempio, dopo aver dichiarato la
      classe, utilizziamo la funzione costruttore, avente lo stesso nome della
      classe, per creare nuove istanze. Nell'esempio utilizzando Void come
      funzione abbiamo creato l'istanza. Ogni instanza è un namespace separato
      che inizialmente non contiene alcun attributo. La natura dinamica del
      Python ci consente di aggiungere, "al volo", nuovi attribuiti,
      nell'esempio a. Che le istanze siano namespace separati e indipendenti si
      vede anche dal fatto che creando due istanze, possiamo assegnare ad ogni
      istanza un valore diverso per l'attribuito a. Otteniamo, come ci si
      aspetta, due a separati e indipendenti per ogni istanza.
      Listato 1   

class List:
 def __init__(self, data, next=None):
  self.data=data
  self.next=next
  
 def append(self, data):
  curr = self
  while not curr.next is None:
   curr = curr.next
  curr.next = List(data)
  return self
    
 def push(self, data):
  return List(data,self)
   
 def printall(self):
  while not self is None:
   print self.data
   self=self.next   

      Le classi divengono interessanti quando contengono campi e metodi. Nel
      Listato 1 possiamo vedere la dichiarazione di una classe List, contenuta
      nel file list.py, che andiamo subito ad utilizzare: 
      >>> import list
      >>> x = list.List(2)
      >>> x.printall
     
      >>> x.printall()
      2
      >>> x = x.push(1)
      >>> x.printall()
      1
      2
      >>> x=x.append(3)
      >>> x.printall()
      1
      2
      3 
      Come si vede, abbiamo definito una classe lista con tre metodi: append,
      push e printall. Esaminiamo ora in dettaglio il meccanismo della
      definizione della classe. Dichiarando una classe ci ritroviamo con una
      funzione costruttore che ha lo stesso nome della classe. In realtà questo
      costruttore crea soltanto l'oggetto istanza, ma non lo inizializza. Se
      occorre effettuare delle inizializzazioni supplementari (caso abbastanza
      frequente), si deve definire una funzione di inizializzazione che si deve
      chiamare __init__. L'uso dei doppi underscore all'inizio e alla fine è una
      convenzione Python usata ovunque sia necessario definire nomi che hanno un
      significato speciale, e non è limitata solo a questo caso. La __init__
      viene chiamata automaticamente dopo che è stata creata l'istanza
      dell'oggetto.
      Veniamo ora a quello che è un punto critico, ed è basilare per comprendere
      tutto il meccanismo della OOP in Python. Dovrebbe essere chiaro come
      funzionano in Python le funzioni (non c'è niente di speciale, a parte il
      fatto che sono solitamente contenute dentro dei moduli). Ora, nelle classi
      non ci sono funzioni ma metodi. In OOP, in generale i metodi sono funzioni
      speciali, in quanto conoscono l'oggetto a cui appartengono. In Java, C++ e
      altri linguaggi OOP l'accesso all'oggetto corrente è implicito e nascosto:
      il programmatore non se ne accorge. In Python invece l'oggetto corrente
      viene passato esplicitamente come primo parametro della chiamata del
      metodo. Confrontando l'ultimo esempio con il listato il meccanismo
      dovrebbe diventare abbastanza chiaro. Precisamente, scrivendo x =
      list.List(2) si costruisce un oggetto istanza, poi viene chiamato
      automaticamente __init__(self, data, next=None). Il primo parametro di
      __init__ è l'oggetto appena costruito, il secondo è il parametro fornito
      al costruttore (2). In questo esempio si utilizza anche la feature dei
      parametri di default: poiché non abbiamo specificato il terzo parametro
      questo assume il valore di default None. Il meccanismo è analogo quando
      invochiamo i metodi: chiamato x.printall() viene chiamata la funzione
      printall passando x come primo parametro. Per convenzione il primo
      parametro di un metodo viene chiamato self. Non c'è niente di speciale in
      questo nome ma non seguire questa convenzione può compromettere la
      leggibilità del vostro listato ad altri programmatori Python. Non ci sono
      modi per accedere direttamente ai campi di un oggetto: occorre usare
      sempre il prefisso self. Per quanto questa cosa possa apparire noiosa, in
      pratica questo migliora la leggibilità e evita ambiguità con le variabili
      locali.  
      Ereditarietà
      L'ereditarietà è il meccanismo che consente di riutilizzare codice già
      esistente organizzato in classi: grazie ad essa possiamo creare nuove
      classi che estendono e modificano quelle esistenti. Esemplifichiamo il
      funzionamento, considerando una classe Punto2D, che estendiamo per
      ottenere un Punto3D: 
      import math
      class Punto2D:
      def __init__(self, x, y):
      self.x = x
      self.y = y
      def dist(self):
      return math.sqrt(self.x **2 + self.y **2)
      class Punto3D(Punto2D):
      def __init__(self, x, y, z):
      Punto2D.__init__(self, x, y)
      self.z = z
      def dist(self):
      return math.sqrt(Punto2D.dist(self)**2 + self.z**2)
      Per ereditare da una classe si usa la sintassi class
      Derivata(Base1,Base2), dove Derivata è la classe che eredita dalle classi
      Base1 , Base2. Nell'esempio di sopra è mostrato un caso di ereditarietà
      singola ma Python supporta in generale l'ereditarietà multipla. La nuova
      classe eredita tutti i metodi delle classi base, il che significa che un
      metodo disponibile in una Base1 o Base2 è disponibile anche nella classe
      Derivata. Un metodo può trovarsi anche in più di una delle classi base: in
      tal caso viene usato il metodo trovato effettuando una ricerca deep-first
      nel grafo delle classi.
      La cosa importante è che ereditando possiamo ridefinire i metodi della
      classe base, come si vede nell'esempio, dove il metodo dist di Punto3D
      ridefinisce il metodo dist di Punto2D. Per accedere ai metodi della classe
      base (operazione necessaria per sfruttare il codice preesistente) si deve
      far riferimento esplicitamente al metodo chiamandolo con il nome della
      classe e passando self come parametro. Nell'esempio si nota come vengano
      chiamati esplicitamente Punto2D.dist e Punto2D.__init__. Infine accenniamo
      al fatto che Python supporta anche l'operator overloading: è possibile per
      esempio ridefinire l'operatore + definendo il metodo __sum__, l'operatore
      * ridefinendo il metodo __mult__ e così via. Non trattiamo in dettaglio
      queste caratteristiche per ragioni di spazio. 
      Eccezioni
      Durante l'esecuzione dei programmi possono insorgere degli errori.
      Linguaggi tradizionali come il C o il Pascal non prendono particolari
      provvedimenti per gestirli (è compito del programmatore "stare attento" e
      verificare bene i valori ritornati per riconoscere e gestire gli errori).
      In pratica però una condizione di errore è sempre qualcosa di particolare
      che altera il normale flusso del programma, e che tende a sfuggire
      all'attenzione del programmatore. Queste condizioni eccezionali, in
      pratica così eccezionali non sono, e devono essere in qualche modo gestite
      perché i programmi diventino robusti (ovvero non si piantano ogni 5
      minuti). Non a caso i "disastri a catena" che avvengono nei programmi
      hanno origine in qualche errore non gestito che si propaga causando errori
      su errori fino alla terminazione del programma (nei casi fortunati) o il
      blocco del sistema (di solito). Per gestire le condizioni di errore è
      stato inventato, fin dai tempi antichi dell'informatica (vent'anni fa), il
      meccanismo delle eccezioni. Per molto tempo però la gestione delle
      eccezioni è rimasta confinata ai linguaggi assembler: da qualche anno è
      approdata anche ai linguaggi ad alto livello, come C++, Java e, appunto,
      Python.
      Vediamo come funzionano le eccezioni considerando la gestione di un errore
      tanto semplice quanto (spesso) inaspettato: una divisione per zero: 
      >>> x = 0
      >>>
      >>> 1/x
      Traceback (innermost last):
      File "", line 0, in ?
      ZeroDivisionError: integer division or modulo
      >>> try:
      ... y = 1/x
      ... except ZeroDivisionError:
      ... print "cannot divide!"
      ...
      cannot divide!
      >>> ZeroDivisionError
       
      In caso di eccezione, l'esecuzione si interrompe e causa la stampa di un
      messaggio di errore. Per essere esatti, durante l'esecuzione di un
      programma una eccezione causa il ritorno dalla funzione o metodo in cui ci
      si trova e la generazione di un eccezione nel punto in cui si ritorna.
      Questo meccanismo viene ripetuto, portando ad un ritorno forzato da tutte
      le chiamate correnti finché non si raggiunge il toplevel (che causa la
      terminazione con un messaggio errore), a meno che l'eccezione non venga in
      qualche modo catturata e gestita. In questo modo le condizioni di errore
      non sono ignorabili a meno che il programmatore non decida esplicitamente
      di ignorarle. Comunque in questo mod si tende a confinare gli errori in
      precisi sottosistemi che non causano la terminazione anomala del
      programma. Nell'esempio vediamo anche come funziona il meccanismo di
      gestione delle eccezioni: si sottopone a controllo il blocco che può
      causare l'eccezione tramite try. Le eccezioni possono essere provocate
      esplicitamente utilizzando raise. Se scatta una eccezione, questa viene
      confrontata con le possibili eccezioni che si vogliono gestire, usando la
      clausola except. Non ci deve essere necessariamente una sola clausola
      except ma anzi è utile che ce ne sia più d'una. Scatterà quella
      corrispondente al tipo di eccezione sollevato. Se nessuna va bene,
      l'eccezione si propaga come se non ci fosse stato alcun controllo. Questa
      procedura è anche il modo più corretto di gestirla: controllare gli errori
      che si prevedono e lasciar passare quelli inaspettati in modo che in fase
      di test e debug si possano rilevare le condizioni di errore non previste.
      Accenniamo infine al fatto che in Python la try può anche avere le
      clausole else e finally. La else viene eseguita se il codice sottoposto a
      try non causa eccezioni. La clausola finally invece viene eseguita in ogni
      caso, sia nel caso che il codice causi eccezioni, sia nel caso che tutti
      fili liscio. È anche possibile utilizzare classi definite dall'utente per
      organizzare gerarchicamente le eccezioni. Anche questo non lo trattiamo in
      dettaglio per motivi di spazio. 
      Esempio: Gestione Form
      Concludiamo l'articolo con un esempio un po' più dettagliato e
      significativo: un piccolo programma che può essere usato per i vostri
      script Web di gestione form. Tengo a precisare che l'esempio qui
      pubblicato è stato scritto utilizzando Windows 98 con Personal Web Server.
      L'unico accorgimento per utilizzarlo in questo ambiente è quello di
      aggiungere al registro la key
      "HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\W3SVC\Parameters\Script
      Map\.py" con valore di tipo stringa "C:\Python\Python.exe %s", e di porre
      i vostri script in una directory web-shared con il permesso execute
      attivato. Lo script comunque ha girato senza problemi sotto Linux. In
      questo caso è stato usato il meccanismo del bang-path, ovvero inserire
      come prima riga del codice il riferimento all'interprete dello script con
      il prefisso #! Una schermata dello script è visibile in Figura 2, mentre
      lo script stesso è nel Listato 2.
      Figura 2
      
      Listato 2   

#!/usr/bin/python 
# Parameters
REREFER="
http://192.168.1.4/python/form.py" 
# imports
import cgi
import string 
# Print the Form
def print_form(err, nome, cognome,
email):    

 print """
Modulo
Richiesta di Informazioni

Modulo Richiesta
Informazioni

Si prega di specificare i suoi
dati.
Verrà rincontattato al più
presto.
""" 
   if err:
    print "

Errore
nel modulo: "+err+"


  # output form with current fields
   print """

 
  Nome:
 
  Cognome:
 
  Email:
 
 
 

""" % (REREFER, nome,
cognome, email) 
def form_check(form): 
 err = ""
 nome = ""
 cognome = ""
 email = "" 
 if not form.has_key("nome"):
  err = err + "Specificare
il nome"
 else:  

  nome = form["nome"].value
 if not form.has_key("cognome"):  
  err = err + "Specificare
il cognome"
 else:  

  cognome = form["cognome"].value
 if not form.has_key("email"):
  err = err + "Specificare
l'email"
 else:  

  email = form["email"].value
  if string.find(email, "@")
== -1:
   err = err + "Il
formato della email non e' corretto"
  
 return (err, nome, cognome, email)   
def send_mail(form):
 email = """
Nome.....: %s
Cognome..: %s
Email....: %s
""" %
(form['nome'].value, form['cognome'].value, form['email'].value)
 print """
Modulo
Richiesta di Informazioni

Conferma Richiesta

E' stata inviata la seguente richiesta
di informazioni:
%s
""" % (email)
 # send mail here... 
# Always
print "Content-type:
text/html\n\n" 
form = cgi.FieldStorage()
(err, nome,cognome,email) =
form_check(form) 
if form.has_key("invia"):
 if err:
  print_form(err, nome, cognome, email)
 else:
  send_mail(form)
else:
 # first run
 print_form("",nome, cognome,
email)   
print "La ringraziamo
per la preferenza accordataci.Cordiali Saluti."   

      Spendiamo due parole sul funzionamento dello script anche perché il Python
      è posizionabile come una alternativa al Perl, e gli script CGI sono
      probabilmente il campo di applicazione più immediato del Python. Nel
      nostro script abbiamo utilizzato il modulo standard cgi per decifrare i
      parametri dello script. Come è noto il protocollo CGI invia i dati in un
      formato encrittato non particolarmente user-friendly. Chiamando form =
      cgi.FieldStorage viene costruito un oggetto contente i parametri passati
      allo script decifrati. A questo oggetto è possibile accedere come array
      associativo, estraendo i campi della form: form["nome"], eccetera.
      Attenzione che se si accede ad un campo non definito si ottiene una
      eccezione, per cui occorre verificare l'esistenza di una chiave usando
      form.has_key("nome"). Nello script abbiamo anche usato altre due feature
      del Python adatte alla generazione di pagine Web: il triple quote """ e
      l'operatore di I/O %. Per inserire in uno script Python stringhe di testo
      contenenti newline si deve usare come delimitatore tre virgolette. Un
      altro aspetto importante è che la print stampa una stringa, e le stringhe
      sono in Python immutabili. Per cui, anche se è possibile, fare "taglia e
      cuci" con le stringhe per comporre la pagina di output, è abbastanza
      inefficiente e scomodo. Per ovviare, esiste un meccanismo di output
      analogo alla printf del C, che illustriamo con un esempio: 
      >>> x = 1
      >>> y = 'hello'
      >>> z = 64
      >>> print "x=%d y=%s z=%c" % (x,y,z) 
      La stringa che segue il print contiene delle specifiche di formato,
      composte dal % seguito da un carattere: d indica i numeri decimali, s le
      stringhe e c i caratteri. Le specifiche di formato vengono sostituite dai
      parametri che seguono l'operatore % dopo la stringa. Nell'esempio vediamo
      come il %d venga sostituito dal valore decimale di x, il %s dalla stringa
      y mentre il %c genera il carattere il cui codice ascii è contenuto nella
      variabile z (il codice ascii di
'@'
è appunto 64). 
      Conclusioni
      Il Python sta crescendo molto ed occupa ormai un posto rispettabile nel
      mondo dei linguaggi di scripting, ormai quasi al pari del più noto Perl.
      In effetti, una chiara prova della diffusione del linguaggio è data da
      freshmeat.net, un sito che pubblica il nuovo freeware disponibile in rete:
      freshmeat riportava 10 nuovi package Perl recenti e ben 8 Python… Come
      dire, il linguaggio si avvicina in popolarità e diffusione al Perl e
      conquista ogni giorno nuovi cultori. Spero con questi articoli di aver
      contribuito alla sua diffusione, con lo scopo non di proporre un poco
      utile linguaggio per passare qualche momento di relax, ma uno ottimo
      strumento da utilizzare in pratica per risolvere ben precise categorie di
      problemi (nella fattispecie scripting Web, System Adminstration e supporto
      alla programmazione). 
      Michele Sciabarrà, laureato in Scienze dell'Informazione, è direttore
      tecnico di SATORI Network Solutions, ditta marchigiana focalizzata su Java
      e le tecnologie di Internet. Programma in svariati linguaggi da 15 anni, e
      in Java da quando il linguaggio era in versione alpha. È stato tra gli
      espositori di applicazioni Java già alla 1st Italian Java Conference, ha
      scritto decine di articoli su Java e sviluppato numerose applicazioni in
      Java. Mantiene sul Web un corso online di Java in italiano
      (http://www.satorins.com/CorsoJava)    

con cui mi sto     

e tutti i      

come fa Guido