E ora qualcosa di completamente differente...
Python Istantaneo

di Magnus Lie Hetland
tradotto da Alex Martelli





Questo è un corso rapido di minima sul linguaggio di programmazione Python. Per saperne di più, dai un'occhio alla documentazione sul sito web di Python, http://www.python.org/, specialmente il tutorial. Se ti stai chiedendo perchè ti dovrebbe interessare, guarda la pagina dei paragoni, dove Python è paragonato ad altri linguaggi.

Questa introduzione ha ricevuto molte lodi da lettori soddisfatti, ed è stata tradotta in vari linguaggi, fra cui Portoghese, Russo, Norvegese, Coreano e Spagnolo.

(Pagina Python di M. L. Hetland)

1. Fondamenti
Anzitutto, pensa al Python come pseudo-codice. È quasi vero. Le variabili non hanno tipi, nè si dichiarano. Appaiono quando le assegni, spariscono quando non le usi più. L'assegnazione si fa con l'operatore =. L'eguaglianza si controlla con l'operatore ==. Puoi assegnare più variabili alla volta:

    x,y,z = 1,2,3

    first, second = second, first

    a = b = 123

I blocchi sono indicati dall'indentazione, e solo dall'indentazione. (Nè BEGIN/END nè graffe.) Alcune comuni strutture di controllo:

    
    if x < 5 or (x > 10 and x < 20):
        print "Il valore è OK."

    if x < 5 or 10 < x < 20:
        print "Il valore è OK."

    for i in [1,2,3,4,5]:
        print "Iterazione numero", i


    x = 10
    while x >= 0:
        print "x ancora non negativo."
        x = x-1

I primi due esempi sono equivalenti.

La variabile indice data nel ciclo for itera sugli elementi di una list (scritta come nell'esempio). Per fare un ciclo for "normale" (cioè un ciclo di conteggio), usa la funzione built-in range().

    # Stampa i valori da 0 a 99 compresi.
    for value in range(100):
        print value

(La riga che inizia con "#" è un commento, ed è ignorata dall'interprete.)

Ok; ora ne sai abbastanza per (in teoria) implementare qualsiasi algoritmo in Python. Aggiungiamo un po' di interazione elementare con l'utente. Per avere input dall'utente (da un prompt di testo), usa la funzione built-in input.

    x = input("Immettere un numero: ")
    print "Il quadrato del numero è", x*x

La funzione input mostra il prompt passatole (che può essere vuoto) e permette all'utente di immettere qualsiasi valore Python valido. In questo caso ci aspettavamo un numero -- se viene immesso qualcosa d'altro (ad esempio una stringa), il programma va in crash. Per evitarlo ci servirebbe un po' di controllo degli errori. Qui non starò a parlarne; basti dire che, se vuoi che l'input dell'utente sia preso verbatim come stringa (così che tutto possa venire immesso), userai invece la funzione raw_input. Se volessi convertire la stringa immessa s in un intero, potresti poi usare int(s).

Nota: Se vuoi leggere una stringa con input, l'utente dovrà scrivere esplicitamente le virgolette. In Python, le stringhe possono essere racchiuse fra virgolette semplici o doppie.

Quindi, abbiamo coperto strutture di controllo, input e output -- ora ci servono delle belle strutture dati. Le più importanti sono liste e dizionari. Le liste sono scritte con parentesi quadre, e possono naturalmente essere annidate:

    name = ["Cleese", "John"]

    x = [[1,2,3],[y,z],[[[]]]]

Una delle cose carine delle liste è che puoi accederne gli elementi separatamente o a gruppi, attraverso indiciamento e affettamento. L'indiciamento si fa (come in molti altri linguaggi) appendendo l'indice fra parentesi quadre alla lista. (Nota che il primo elemento ha indice 0).

    print name[1], name[0] # Stampa "John Cleese"

    name[0] = "Smith"

L'affettamento è quasi come l'indiciamento, eccetto che scriverai sia l'indice d'inizio sia quello di fine del risultato, con un due-punti (":") a separarli:

    x = ["spam","spam","spam","spam","spam","eggs","and","spam"]

    print x[5:7] # Stampa la lista ["eggs","and"]

Nota che la fine è non-compresa. Se manca uno degli indici, significa che vuoi tutto in quella direzione. Per esempio list[:3] significa "ogni elemento dall'inizio di list sino all'elemento 3, non compreso" (Si potrebbe sostenere che significa in realtà l'elemento 4, poichè il conteggio parte a 0... vabbè). list[3:], d'altra parte, significherebbe "ogni elemento di list, partendo dall'elemento 3 (compreso) sino all'ultimo, compreso." Per avere risultati davvero interessanti, puoi usare anche numeri negativi: list[-3] è il terzo elemento dalla fine della lista...

A proposito di indiciamento, potrebbe interessarti sapere che la funzione built-in len ti dà la lunghezza di una lista.

Ora, dunque -- e i dizionari? Per farla semplice, sono come liste, ma i loro contenuti non sono ordinati. Come si indicizzano allora? Bè, ogni elemento ha una chiave, cioè un "nome" che è usato per trovare l'elemento proprio come in un vero dizionario. Un paio di dizionari d'esempio:

    { "Alice" : 23452532, "Boris" : 252336,
      "Clarice" : 2352525, "Doris" : 23624643}

    person = { 'first name': "Robin", 'last name': "Hood",
               'occupation': "Scoundrel" }

Ora, per avere l'occupazione di person, usiamo l'espressione person["occupation"]. Se volessimo cambiare il suo cognome, potremmo scrivere:

    person['last name'] = "of Locksley"

Semplice, no? Come le liste, i dizionari possono contenere altri dizionari. O anche liste. E naturalmente anche le liste possono contenere dizionari. Quindi, si possono facilmente fare strutture dati piuttosto avanzate.

2. Funzione
Prossimo passo: l'astrazione. Vogliamo dare un nome a un brano di codice, e chiamarlo con un paio di parametri. In altre parole -- vogliamo definire una funzione (o "procedura"). È facile. Usa la keyword def così:

    def square(x):
        return x*x

    print square(2) # Stampa 4

Per quelli di voi che lo capiscono: tutti i parametri in Python sono passati per riferimento (come, ad esempio, in Java). Per quelli che non lo capiscono: non ve ne preoccupate :)

Python ha gran copia di cose graziose come gli argomenti con nome e gli argomenti di default e può gestire un numero variabile di argomenti a una singola funzione. Per altre informazioni su questo, vedere il tutorial di Python, sezione 4.7.

Se sai usare le funzioni in generale, questo è quel che ti serve sapere su di loro in Python. (Ah sì... la keyword return termina l'esecuzione della funzione e restituisce il valore datole.)

Una cosa che potrebbe esserti utile sapere, però, è che in Python le funzioni sono valori. Così, se hai una funzione come square, potresti fare qualcosa come:

    queeble = square
    queeble(2)
    Stampa 4

Per chiamare una funzione senza argomenti devi ricordarti di scrivere doit() e non doit. Quest'ultimo, come abbiamo mostrato, ritorna solo la funzione stessa, come valore. (Questo vale anche per i metodi degli oggetti... vedi oltre.)

3. Oggetti e roba...
Penso tu sappia come funziona la programmazione a oggetti. (Altrimenti, questa sezione potrebbe non avere molto senso per te. Non importa... comincia a giocare senza gli oggetti :).) In Python si definiscono le classi con la keyword (sorpresa!) class, così:

    class Basket:

        # Ricorda sempre l'argomento *self*
        def __init__(self,contents=None):
            self.contents = contents or []

        def add(self,element):
            self.contents.append(element)

        def print_me(self):
            result = ""
            for element in self.contents:
                result = result + " " + `element`
            print "Contiene:"+result

Cose nuove qui:

  1. Tutti i metodi (funzioni di un oggetto) ricevono un argomento in più all'inizio della lista degli argomenti, che contiene l'oggetto stesso (detto self in questo esempio, il che è l'abitudine.)
  2. I metodi si chiamano così: object.method(arg1,arg2).
  3. Alcuni nomi di metodo, come __init__, sono predefiniti, con significati speciali. __init__ è il nome del costruttore della classe, cioè la funzione chiamata quando crei un'istanza.
  4. Alcuni argomenti possono essere opzionali e avere un valore di default (come detto prima, nella sezione sulle funzioni). Questo si fa scrivendo la definizione come:
            def spam(age=32): ...
    		
    Qui, spam può essere chiamata con uno o zero parametri. Se non ne vengono usati, il parametro age avrà valore 32.
  5. "Logica corto circuito". È un dettaglio carino... vedi sotto.
  6. Gli apostrofi rovesciati convertono un oggetto alla sua rappresentazione stringa. (Così se element contiene il numero 1, allora `element` è lo stesso di "1" mentre 'element' è una stringa letterale.)
  7. Il segno di addizione + è usato anche per concatenare liste e stringhe. Le stringhe in realtà sono solo liste di caratteri (il che significa che potete usare su di esse indiciamento e affettamento e la funzione len. Ganzo, eh?)

Nessun metodo (nè variabile membro) è protetto (o privato o simile) in Python. L'incapsulamento è praticamente questione di stile di programmazione. (Se ti serve davvero, ci sono convenzioni sui nomi che permettono un po' di privacy :)).

Ora, quella logica corto-circuito...

Tutti i valori in Python possono essere usati come valori logici. Alcuni dei più "vuoti", come [], 0, "" e None rappresentano la falsità logica, mentre la maggior parte degli altri valori (come [0], 1 o "Hello, world") rappresentano la verità logica.

Ora, le espressioni logiche come a and b sono valutate così: Primo, si controlla se a è vero. Se non lo è, è il risultato. Se lo è, il risultato è b (che rappresenterà il valore di verità dell'espressione.) La logica corrispondente per a or b è: se a è vero, è il risultato. Se non lo è, il risultato è b.

Questo meccanismo fa sì che and e or si comportino come gli operatori booleani che dovrebbero implementare, ma ti permette anche di scrivere espressioni condizionali semplici e dolci. Per esempio, l'istruzione

    if a:
        print a
    else:
        print b

potrebbe essere scritta anche:

    print a or b

In effetti, questo è piuttosto idiomatico in Pyton, quindi meglio abituarcisi. È quel che facciamo nel metodo Basket.__init__. L'argomento contents ha un valore di default di None (che è, fra le altre cose, false). Quindi, per controllare se ha un valore, potremmo scrivere:

    if contents:
        self.contents = contents
    else:
        self.contents = []

Naturalmente, ora sai che c'è un modo migliore. E perchè non gli diamo il valore di default di [] da subito? Perchè a causa di come funziona Python, questo darebbe a tutti i Basket la stessa lista vuota come contenuto di default. Appena uno comincia a riempirsi, tutti conterrebbero gli stessi elementi, e il default non sarebbe più vuoto... per imparare di più su questo, dovresti leggere la documentazione e cercare la differenza fra identità e eguaglianza.

Un altro modo di fare quanto sopra è:

    def __init__(self, contents=[]):
        self.contents = contents[:]

Puoi indovinare come questo funziona? Invece di usare la stessa lista vuota dappertutto, usiamo l'espressione contents[:] per fare una copia. (Semplicemente, affettiamo il tutto.)

Così, per fare effettivamente un Basket e usarlo (cioè chiamare qualche metodo su di esso) faremo qualcosa come:

    b = Basket(['apple','orange'])
    b.add("lemon")
    b.print_me()

Ci sono altri metodi magici oltre a __init__. Uno di essi è __str__, che definisce che aspetto vuole avere l'oggetto se viene trattato come stringa. Potremmo usarlo nel nostro Basket invece di print_me:

    def __str__(self):
        result = ""
        for element in self.contents:
            result = result + " " + `element`
        return "Contains:"+result

Ora, se volessimo stampare il Basket b, potremmo semplicemente usare:

    print b

Ganzo, eh?

Le sottoclassi si fanno così:

    class SpamBasket(Basket):
        # ... 

Python permette eredità multipla, quindi puoi avere varie superclassi fra parentesi, separate da virgole. Le classi si istanziano così: x = Basket(). I costruttori, come dicevo, si fanno definendo la speciale funzione membro __init__. Supponiamo che SpamBasket abbia un costruttore__init__(self,type). Allora potresti farne uno così: y = SpamBasket("apples").

Se, nel costruttore di SpamBasket, ti servisse chiamare il costruttore di una o più superclassi, potresti chiamarlo così: Basket.__init__(self). Nota che, oltre ai normali parametri, devi fornire esplicitamente self, poichè la __init__ della superclasse non sa con quale istanza stia trattando.

Per altre meraviglie sulla programmazione a oggetti in Python, vedi la sezione 9 del tutorial.

4. Un trucco mentale Jedi.
(Questa sezione è qua solo perchè penso che sia bella. Decisamente non è necessario leggerla per iniziare a imparare Python.)

Ti piacciono i concetti sbalorditivi? Allora, se sei davvero audace, potresti dare un'occhiata al saggio di Guido van Rossum sulle metaclassi. Se però preferisci che il tuo cervello non esploda, potrebbe bastarti questo trucchetto.

Python usa spazi di nomi dinamici e non lessicali. Questo significa che se hai una funzione così:

    def orange_juice():
        return x*2

... dove una variabile (in questo caso x) non è connessa a un argomento e non riceve un valore entro la funzione, Python userà il valore che essa ha dove e quando la funzione è chiamata. In questo caso:

    x = 3
    y = orange_juice()
    # y è ora 6
    x = 1
    y = orange_juice()
    # y è ora 2

Di solito, questo è il tipo di comportamento che desideri (benchè questo esempio sia un po' artificioso - raramente si accede così alle variabili.) Tuttavia, a volte può essere carino avere qualcosa di simile a uno spazio di nomi statico, cioè, memorizzare alcuni valori dall'ambiente nella funzione quando viene creata. Il modo di fare questo in Python è attraverso gli argomenti di default.

    x = 4
    def apple_juice(x=x):
        return x*2

Qui, l'argomento x riceve un valore di default che è eguale al valore della variabile x nel momento in cui la funzione viene definita. Quindi, sinchè nessuno fornisce un argomento per la funzione, si comporterà così:

    x = 3
    y = apple_juice()
    # y is now 8
    x = 1
    y = apple_juice()
    # y is now 8

Così - il valore di x non è cambiato. Se ci servisse solo questo, avremmo equivalentemente potuto scrivere:

    def tomato_juice():
        x = 4
        return x*2

o anche

    def carrot_juice():
        return 8

Tuttavia, il punto è che il valore di x è raccolto dall'ambiente nel momento in cui la funzione viene definita. In che modo questo è utile? Facciamo un esempio -- una funzione che compone altre due funzioni.

Vogliamo una funzione che lavora così:

    from math import sin, cos

    sincos = compose(sin,cos)

    x = sincos(3)

Dove compose è la funzione che vogliamo costruire, e x ha il valore -0.836021861538, cioè sin(cos(3)). Ora, come facciamo questo?

(Nota che qui stiamo usando funzioni come argomento... il che è un trucco già parecchio carino in sè.)

Chiaramente, compose accetta due funzioni come parametri, e restituisce una funzione che accetta un parametro. Quindi, uno scheletro di soluzione potrebbe essere:

    def compose(fun1, fun2):
        def inner(x):
            pass # ...
        return inner

Potremmo essere tentati di scrivere return fun1(fun2(x)) entro la funzione inner e accontentarci. No, no, no. Questo si comporterebbe molto stranamente. Immagina il seguente scenario:

    from math import sin, cos

    def fun1(x):
        return x + " world!"

    def fun2(x):
        return "Hello,"

    sincos = compose(sin,cos)  # usa la versione sbagliata

    x = sincos(3)

Ora, che valore avrebbe x? Esatto: "Hello, world". Perchè? Perchè quando compose viene chiamata, raccoglie i valori di fun1 e fun2 dall'ambiente, non quelli che erano in giro quando fu creata. Per avere una soluzione funzionante, tutto quel che dobbiamo fare è usare la tecnica che ho descritto prima:

    def compose(fun1, fun2):
        def inner(x, fun1=fun1, fun2=fun2):
            return fun1(fun2(x))
        return inner

Ora dobbiamo solo sperare che nessuno passi alla funzione risultante più di un argomento, perchè questo la romperebbe :). A proposito, visto che non ci serve il nome inner, ed esso contiene solo un'espressione, tanto vale fare una funzione anonima, usando la keyword lambda:

    def compose(f1, f2):
        return lambda x, f1=f1, f2=f2: f1(f2(x))

Conciso, ma chiaro. Devi amarlo :)

(E se non ne hai capito nulla, non preoccuparti. Almeno spero ti abbia convinto che Python è più che "solo un linguaggio di scripting"... :))

5. E ora...
Giusto poche cosette vicino alla fine. La maggioranza delle funzioni e classi utili sono piazzate in moduli, che sono file di testo contenenti codice Python. Puoi importarli e usarli nei tuoi programmi. Per esempio, per usare il metodo split del modulo standard string, puoi fare, o:

    import string

    x = string.split(y)

Oppure...

    from string import split

    x = split(y)

Per altre informazioni sui moduli della libreria standard, dai un'occhiata a www.python.org/doc/lib. Contengono un mucchio di roba utile.

Tutto il codice nel modulo/script è eseguito quando viene importato. Se vuoi che il tuo programma possa essere usato sia come modulo importabile sia come programma eseguibile, aggiungi alla fine di esso qualcosa come:

    if __name__ == "__main__": go()

Questo è un modo magico per dire che se questo modulo viene fatto girare come script eseguibile (cioè, se non stiamo venendo importati da un altri script), allora va chiamata la funzione go. Naturalmente, potresti fare qualsiasi cosa in questa posizione dopo il due-punti... :)

E per quelli di voi che vogliono rendere eseguibile uno script su UN*X, usate la seguente prima riga per farlo funzionare da solo:

    #!/usr/bin/env python

Infine, breve menzione di un concetto importante: le Eccezioni. Alcune operazioni (come dividere per zero, o leggere da un file inesistente) producono una condizione di errore, cioè di eccezione. Puoi anche farti le tue, sollevandole in momenti appropriati.

Se non viene gestita l'eccezione, il tuo programma termina e stampa un messaggio di errore. Puoi evitarlo con una istruzione di try/except. Per esempio:

    def safe_division(a,b):
        try:
            return a/b
        except ZeroDivisionError:
            return None

ZeroDivisionError è un'eccezione standard. In questo caso, avresti potuto controllare se b era zero, ma in molti casi, questa strategia non è applicabile. Inoltre, se non avessimo la clausola try in safe_division, cioè se fosse una funzione rischiosa da chiamare, potremmo ancora fare qualcosa come:

    try:
        unsafe_division(a,b)
    except ZeroDivisionError:
        print "Something was divided by zero in unsafe_division"

Nei casi in cui normalmente non ci sarebbe uno specifico problema, però potrebbe succedere, usare le eccezioni permette di evitare costosi controlli ecc.

Beh, questo è tutto. Spero tu abbia imparato qualcosa. Ora vai e gioca. E ricorda il motto per imparare il Python: "Use the source, Luke." (Traduzione: leggi tutto il codice su cui puoi mettere le mani :)) Per cominciare, ecco un esempio. È il ben noto algoritmo QuickSort di Hoare. Una versione con sintassi colorizzata è qui.

C'è una cosa che può meritare di essere detta su questo esempio. La variabile done controlla se la partition abbia o meno finito di muovere gli elementi. Quindi, quando uno dei due cicli interni vuol terminare l'intera sequenza di scambi, mette done ad 1, poi esce con break. Perchè i cicli interni usano done? Perchè, quando il primo ciclo interno finisce con break, se il prossimo ciclo debba partire o meno dipende dal fatto che il ciclo principale sia finito, cioè, se done sia stata o meno posta ad 1:

    while not done:
        while not done:
            # Itera sino ad un break

        while not done:
            # Eseguito solo se il primo non ha posto "done" ad 1

Una versione equivalente, forse più chiara, ma nella mia opinione meno bellina, sarebbe:

    while not done:
        while 1:
            # Itera sino ad un break

        if not done:
            while 1:
                # Eseguito solo se il primo non ha posto "done" ad 1

L'unica ragione per cui ho usato la variabile done nel primo ciclo era che mi piaceva conservare la simmetria fra i due. Così li si potrebbe scambiare e l'algoritmo funzionerebbe ancora.

Alcuni altri esempi si trovano sulla pagina dei bocconcini di Joe Strout.

[Pagina principale di Python]


Copyright © Magnus Lie Hetland (mlh@idi.ntnu.no)
Ultima modifica dell'originale: Wed May 19 23:29:50 MET DST 1999