Chapter 2: Il linguaggio Python

Il linguaggio Python

Python

Python

Python è un linguaggio di programmazione general purpose (utilizzabile quindi per gli scopi più diversi) estremamente di alto livello. La sua filosofia di progettazione pone l'accento sulla produttività del programmatore e sulla leggibilità del codice. Il suo nucleo ha una sintassi minimale con pochissimi comandi e con una semantica semplice, ma contiene un'ampia libreria standard con API per molte delle funzioni del sistema operativo su cui viene eseguito l'interprete. Il codice Python, seppur minimale, definisce oggetti di tipo liste (list), tuple (tuple), dizionari (dict) ed interi a lunghezza arbitraria (long).

Python supporta diversi paradigmi di programmazione: programmazione orientata agli oggetti (class), programmazione imperativa (def), e programmazione funzionale (lambda). Python ha un sistema di tipizzazione dinamica e un sistema automatico di gestione della memoria che utilizza il conteggio dei riferimenti (come Perl, Ruby e Scheme).

La prima versione di Python è stata rilasciata da Guido van Rossum nel 1991. Il linguaggio ha un modello di sviluppo aperto, basato su una comunità di sviluppatori gestita dalla Python Software Foundation, una associazione non-profit. Ci sono molti interpreti e compilatori che implementano il linguaggio Python, tra cui uno in Java (Jython). In questo breve approfondimento si farà riferimento all'implementazione C creata da Guido.

Sul sito ufficiale di Python[python] si possono trovare molti tutorial, la documentazione ufficiale e il manuale di riferimento della libreria standard del linguaggio.

Per ulteriori approfondimenti si possono vedere i libri indicati nelle note.[guido] e [lutz] .

Questo capitolo può essere saltato se si ha già familiarità con il linguaggio Python.

Avvio

shell

Le distribuzioni binarie di web2py per Microsoft Windows e Apple Mac OS X già comprendono l'interprete Python.

Su Windows web2py può essere avviato con il seguente comando (digitato al prompt del DOS):

web2py.exe -S welcome

Su Apple Mac OS X digitare il seguente comando nella finestra del Terminale (dalla stessa cartella contenente web2py.app):

./web2py.app/Contents/MacOS/web2py -S welcome

Su una macchina Linux o su macchine Unix è molto probabile che Python sia già installato. In questo caso digitare al prompt dello shell il seguente comando:

python web2py.py -S welcome

Nel caso che Python 2.5 non sia presente dovrà essere scaricato e installato prima di eseguire web2py.

L'opzione della linea di comando -S welcome indica a web2py di eseguire la shell interattiva come se i comandi fossero inseriti in un controller dell'applicazione welcome, l'applicazione di base di web2py. Questo rende disponibili quasi tutte le classi, le funzioni e gli oggetti di web2py. Questa è l'unica differenza tra la shell interattiva di web2py e la normale linea di comando dell'interprete Python.

L'interfaccia amministrativa fornisce anche una shell utilizzabile via web per ogni applicazione. Per accedere a quella dell'applicazione "welcome" utilizzare l'indirizzo:

http://127.0.0.1:8000/admin/shell/index/welcome

Tutti gli esempi di questo capitolo possono essere provati sia nella shell standard di Python che nella shell via web di web2py.

help, dir

help
dir

Nel linguaggio Python sono presenti due comandi utili per ottenere informazioni sugli oggetti, sia interni che definiti dall'utente, istanziati nello scope corrente.

Utilizzare il comando help per richiedere la documentazione di un oggetto (per esempio "1"):

>>> help(1)
Help on int object:

class int(object)
 |  int(x[, base]) -> integer
 |
 |  Convert a string or number to an integer, if possible.  A floating point
 |  argument will be truncated towards zero (this does not include a string
 |  representation of a floating point number!)  When converting a string, use
 |  the optional base.  It is an error to supply a base when converting a
 |  non-string. If the argument is outside the integer range a long object
 |  will be returned instead.
 |
 |  Methods defined here:
 |
 |  __abs__(...)
 |      x.__abs__() <==> abs(x)
...

e, poichè "1" è un intero, si ottiene come risposta la descrizione della classe int e di tutti i suoi metodi. Qui l'output del comando è stato troncato perchè molto lungo e dettagliato.

Allo stesso modo si può ottenere una lista di metodi dell'oggetto "1" con il comando dir:

>>> dir(1)
['__abs__', '__add__', '__and__', '__class__', '__cmp__', '__coerce__',
'__delattr__', '__div__', '__divmod__', '__doc__', '__float__', 
'__floordiv__', '__getattribute__', '__getnewargs__', '__hash__', '__hex__',
'__index__', '__init__', '__int__', '__invert__', '__long__', '__lshift__',
'__mod__', '__mul__', '__neg__', '__new__', '__nonzero__', '__oct__', 
'__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdiv__',
'__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__',
'__rlshift__', '__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__',
'__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__',
'__str__', '__sub__', '__truediv__', '__xor__']

Tipi

tipi

Python è un linguaggio dinamicamente tipizzato. Questo significa che le variabili non hanno un tipo e pertanto non devono essere dichiarate. I valori, d'altra parte, hanno un tipo. Si può interrogare una variabile per sapere il tipo del valore che contiene:

>>> a = 3
>>> print type(a)
<type 'int'>
>>> a = 3.14
>>> print type(a)
<type 'float'>
>>> a = 'hello python'
>>> print type(a)
<type 'str'>

Python include nativamente anche strutture dati come le liste e i dizionari.

str

str
ASCII
UTF8
Unicode
encode

Python supporta l'utilizzo di due differenti tipi di stringhe: stringhe ASCII e stringhe Unicode. Le stringhe ASCII sono delimitate dagli apici ('...'), dai doppi apici("...") o da tre doppi apici ("""..."""). I tre doppi apici delimitano le stringhe multilinea. Le stringhe Unicoode iniziano con il carattere u seguito dalla stringa che contiene i caratteri Unicode. Una stringa Unicode può essere convertita in una stringa ASCII scegliendo una codifica (encoding). Per esempio:

>>> a = 'this is an ASCII string'
>>> b = u'This is a Unicode string'
>>> a = b.encode('utf8')

Dopo aver eseguito questi tre comandi la variabile a è una stringa ASCII che memorizza caratteri codificati con UTF8. Internamente web2py utlizza sempre la codifica UTF8 nelle stringhe.

È possibile scrivere il valore delle variabili nelle stringhe in diversi modi:

>>> print 'number is ' + str(3)
number is 3
>>> print 'number is %s' % (3)
number is 3
>>> print 'number is %(number)s' % dict(number=3)
number is 3

L'ultima notazione è la più esplicita e la meno soggetta ad errori. Ed è pertanto da preferire.

Molti oggetti in Python, per esempio i numeri, possono essere convertiti in stringhe utlizzando str o repr. Questi due comandi sono molto simili e producono risultati leggermente diversi. Per esempio:

>>> for i in [3, 'hello']:
        print str(i), repr(i)
3 3
hello 'hello'

Per le classi definite dall'utente str e repr possono essere definiti utilizzando gli speciali operatori __str__ e __repr__. Questi metodi sono brevemente descritti più avanti. Per ulteriori informazioni si può fare riferimento alla documentazione ufficiale di Python[pydocs] . repr ha sempre un valore di default.

Un'altra caratteristica importante delle stringhe in Python è che sono oggetti iterabili (sui quali si può eseguire un ciclo che ritorna sequenzialmente ogni elemento dell'oggetto):

>>> for i in 'hello':
        print i
h
e
l
l
o

liste

list

I metodi principali delle liste in Python sono append (aggiungi), insert (inserisci) e delete (cancella):

>>> a = [1, 2, 3]
>>> print type(a)
<type 'list'>
>>> a.append(8)
>>> a.insert(2, 7)
>>> del a[0]
>>> print a
[2, 7, 3, 8]
>>> print len(a)
4

Le liste possono essere affettate (sliced) per ottenerne solo alcuni elementi:

>>> print a[:3]
[2, 7, 3]
>>> print a[1:]
[7, 3, 8]
>>> print a[-2:]
[3, 8]

e concatenate:

>>> a = [2, 3]
>>> b = [5, 6]
>>> print a + b
[2, 3, 5, 6]

Una lista è un oggetto iterabile quindi si può ciclare su di essa:

>>> a = [1, 2, 3]
>>> for i in a:
        print i
1
2
3

Gli elementi di una lista non devono obbligatoriamente essere dello stesso tipo, ma possono essere qualsiasi tipo di oggetto Python.

tuple

tuple

Una tupla è come una lista, ma la sua dimensione e i suoi elementi sono immutabili, mentre in una lista sono mutabili. Se un elemento di una tupla è un oggetto gli attributi dell'oggetto sono mutabili. Una tupla è definita dalle parentesi tonde:

>>> a = (1, 2, 3)

Perciò, mentre questa assegnazione è valida per una lista:

>>> a = [1, 2, 3]
>>> a[1] = 5
>>> print a
[1, 5, 3]

l'assegnazione di un elemento all'interno di una tupla non è un'operazione valida:

>>> a = (1, 2, 3)
>>> print a[1]
2
>>> a[1] = 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

La tupla, così come la lista, è un oggetto iterabile. È da notare che una tupla formata da un solo elemento deve includere una virgola alla fine:

>>> a = (1)
>>> print type(a)
<type 'int'>
>>> a = (1,)
>>> print type(a)
<type 'tuple'>

Le tuple, a causa della loro immutabilità, sono molto efficienti per l'impacchettamento (packing) degli oggetti. Le parentesi spesso sono opzionali:

>>> a = 2, 3, 'hello'
>>> x, y, z = a
>>> print x
2
>>> print z
hello

dict

dict

Un dizionario (dict) in Python è una hash table che collega una chiave ad un valore. Per esempio:

>>> a = {'k':'v', 'k2':3}
>>> a['k']
v
>>> a['k2']
3
>>> a.has_key('k')
True
>>> a.has_key('v')
False

Le chiavi possono essere di un qualsiasi tipo che implementa il metodo __hash__ (int, stringa, o classe). I valori possono essere di qualsiasi tipo. Chiavi e valori diversi nello stesso dizionario non devono obbligatoriamente essere dello stesso tipo. Se le chiavi sono caratteri alfanumerici un dizionario può anche essere definito con una sintassi alternativa:

>>> a = dict(k='v', h2=3)
>>> a['k']
v
>>> print a
{'k':'v', 'h2':3}

Metodi utili di un dizionario sono has_key, keys, values e items:

>>> a = dict(k='v', k2='3)
>>> print a.keys()
['k', 'k2']
>>> print a.values()
['v', 3]
>>> print a.items()
[('k', 'v'), ('k2', 3)]

Il metodo items produce una lista di tuple ognuna contenente una chiave ed il suo valore associato.

Gli elementi di un dizionario o di una lista possono essere cancellati con il comando del:

>>> a = [1, 2, 3]
>>> del a[1]
>>> print a
[1, 3]
>>> a = dict(k='v', h2=3)
>>> del a['h2']
>>> print a
{'k':'v'}

Internamente Python utilizza l'operatore hash per convertire gli oggetti in interi ed utilizza quell'intero per determinare dove memorizzare l'oggetto.

>>> hash("hello world")
-1500746465

Indentazione del codice

Python utilizza l'indentazione per delimitare blocchi di codice. Un blocco inizia con una linea che termina con i due punti (":") e continua per tutte le linee che hanno lo stesso livello di indentazione o un livello maggiore. Per esempio:

>>> i = 0
>>> while i < 3:
>>>    print i
>>>    i = i + 1
>>>
0
1
2

Normalmente si utilizzano 4 spazi per ogni livello di indentazione È una buona regola non mischiare i caratteri di tabulazione (tab) con gli spazi, perchè questo potrebbe causare problemi.

for ... in

for

In Python è possibile ciclare (loop) sugli oggetti iterabili:

>>> a = [0, 1, 'hello', 'python']
>>> for i in a:
        print i
0
1
hello
python

Una scorciatoia molto utilizzata è il comando xrange, che genera un iterabile senza memorizzare la lista degli elementi:

>>> for i in xrange(0, 4):
        print i
0
1
2
3

Questo è equivalente alla sintassi in C/C++/C#/Java:

for(int i=0; i<4; i=i+1) { print(i); }

Un altro utile comando è enumerate, che esegue il conteggio all'interno di un ciclo:

>>> a = [0, 1, 'hello', 'python']
>>> for i, j in enumerate(a):
        print i, j
0 0
1 1
2 hello
3 python

Esiste inoltre il comando range(a, b, c) che ritorna una lista di interi che ad iniziare dal valore a, incrementato del valore c e che si conclude con l'ultimo valore più piccolo di b. a ha come valore di default 0 e c ha default 1. xrange è simile ma non genera nessuna lista, solo un iteratore sulla lista, per questo è maggiormente utilizzato nei cicli.

Per uscire prematuramente da un ciclo si può utilizzare il comando break:

>>> for i in [1, 2, 3]:
         print i
         break
1

Per avviare la successiva iterazione del ciclo, senza eseguire tutto il blocco di codice che lo compone si può utilizzare il comando continue:

>>> for i in [1, 2, 3]:
         print i
         continue
         print 'test'
1
2
3

while

while

Il ciclo while in Python è simile a quello di molti altri linguaggi di programmazione: ripete l'iterazione per un numero infinito di volte controllando la condizione all'inizio ciclo. Se la condizione è False il ciclo termina.

>>> i = 0
>>> while i < 10:
        i = i + 1
>>> print i
10

Non esiste un costrutto di tipo loop ... until in Python.

def ... return

def
return

Questa è una tipica funzione in Python:

>>> def f(a, b=2):
        return a + b
>>> print f(4)
6

Non c'è la necessità (e neanche la possibilità) di specificare i tipi degli argomenti o il tipo del valore che viene ritornato dalla funzione.

Gli argomenti delle funzioni possono avere valori di default e possono ritornare oggetti multipli:

>>> def f(a, b=2):
        return a + b, a - b
>>> x, y = f(5)
>>> print x
7
>>> print y
3

Gli argomenti delle funzioni possono essere passati esplicitamente per nome:

>>> def f(a, b=2):
        return a + b, a - b
>>> x, y = f(b=5, a=2)
>>> print x
7
>>> print y
-3

Le funzioni possono avere un numero variabile di argomenti:

>>> def f(*a, **b):
        return a, b
>>> x, y = f(3, 'hello', c=4, test='world')
>>> print x
(3, 'hello')
>>> print y
{'c':4, 'test':'world'}

In questo esempio gli argomenti non passati per nome (3, 'hello') sono memorizzati nella lista a e gli argomenti passati per nome (c, test) sono memorizzati nel dizionario b.

Nel caso opposto una lista o una tupla può essere passata ad una funzione che richiede argomenti posizionali individuali tramite un'operazione di spacchettamento (unpacking):

>>> def f(a, b):
        return a + b
>>> c = (1, 2)
>>> print f(*c)
3

e un dizionario può essere spacchettato per ottenere argomenti con nome:

>>> def f(a, b):
        return a + b
>>> c = {'a':1, 'b':2}
>>> print f(**c)
3

if ... elif ... else

if
elif
else
L'utilizzo dei comandi condizionali in Python è intuitivo:

>>> for i in range(3):
>>>     if i == 0:
>>>         print 'zero'
>>>     elif i == 1:
>>>         print 'one'
>>>     else:
>>>         print 'other'
zero
one
other

"elif" significa "else if". Sia elif che else sono clausole opzionali. Può esistere più di un elif ma può essere presente un unico else. Condizioni di maggior complessità possono essere create utilizzando gli operatori not, and and or.

>>> for i in range(3):
>>>     if i == 0 or (i == 1 and i + 1 == 2):
>>>         print '0 or 1'

try ... except ... else ... finally

try
except
finally
Exception
Python può generare un eccezione:

>>> try:
>>>     a = 1 / 0
>>> except Exception, e
>>>     print 'oops: %s' % e
>>> else:
>>>     print 'no problem here'
>>> finally:
>>>     print 'done'
oops: integer division or modulo by zero
done

Quando l'eccezione è generata viene intercettata dalla clausola except, che viene eseguita, mentre non viene eseguita la clausola else. Se non viene generata nessuna eccezione la clausola except non viene eseguita ma viene eseguita la clausola else. La clausola finally è sempre eseguita.

Possono esistere diverse clausole di tipo except per gestire differenti tipi di eccezione.

>>> try:
>>>     raise SyntaxError
>>> except ValueError:
>>>     print 'value error'
>>> except SyntaxError:
>>>     print 'syntax error'
syntax error

Le clausole else e finally sono opzionali:

Quella che segue è una lista di eccezioni predefinite in Python con l'aggiunta dell'unica eccezione che viene generata da web2py:

BaseException
 +-- HTTP (definita da web2py)
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- Exception
      +-- GeneratorExit
      +-- StopIteration
      +-- StandardError
      |    +-- ArithmeticError
      |    |    +-- FloatingPointError
      |    |    +-- OverflowError
      |    |    +-- ZeroDivisionError
      |    +-- AssertionError
      |    +-- AttributeError
      |    +-- EnvironmentError
      |    |    +-- IOError
      |    |    +-- OSError
      |    |         +-- WindowsError (Windows)
      |    |         +-- VMSError (VMS)
      |    +-- EOFError
      |    +-- ImportError
      |    +-- LookupError
      |    |    +-- IndexError
      |    |    +-- KeyError
      |    +-- MemoryError
      |    +-- NameError
      |    |    +-- UnboundLocalError
      |    +-- ReferenceError
      |    +-- RuntimeError
      |    |    +-- NotImplementedError
      |    +-- SyntaxError
      |    |    +-- IndentationError
      |    |         +-- TabError
      |    +-- SystemError
      |    +-- TypeError
      |    +-- ValueError
      |    |    +-- UnicodeError
      |    |         +-- UnicodeDecodeError
      |    |         +-- UnicodeEncodeError
      |    |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
	   +-- ImportWarning
	   +-- UnicodeWarning

Per una descrizione dettagliata di ogni eccezione fare riferimento alla documentazione ufficiale di Python.

web2py espone solamente una nuova eccezione, chiamata HTTP. Quando questa viene generata ritorna una pagina di errore HTTP (per maggiori informazioni fare riferimento al Capitolo 4). Qualsiasi oggetto può essere generato come un'eccezione, ma è buona norma generare solamente oggetti che estendono le eccezioni predefinite.

classi

class

Poichè Python è dinamicamente tipizzato a prima vista le classi e gli oggetti possono sembrare strani. In effetti non è necessario definire le variabili membro (attributi) quando si dichiara una classe e differenti istanze di una stessa classe possono avere differenti attributi. Gli attributi sono generalmente associati con l'istanza e non con la classe (tranne nel caso in cui sono dichiarati come attributi di classe, che è l'equivalente delle variabili membro statiche in C++/Java.

Ecco un esempio:

>>> class MyClass(object): pass
>>> myinstance = MyClass()
>>> myinstance.myvariable = 3
>>> print myinstance.myvariable
3

pass è un comando che non fa nulla. In questo caso è utilizzato per definire una classe MyClass che non contiene nulla. MyClass() richiama il costruttore della classe (in questo caso il costruttore di default) e ritorna on oggetto che è un'istanza della classe. (object) nella definizione della classe indica che la nuova classe estende la classe predefinita object. Questo non è obbligatorio ma è una buona regola da seguire.

Ecco qui una classe più complessa:

>>> class MyClass(object):
>>>    z = 2
>>>    def __init__(self, a, b):
>>>        self.x = a, self.y = b
>>>    def add(self):
>>>        return self.x + self.y + self.z
>>> myinstance = MyClass(3, 4)
>>> print myinstance.add()
9

Le funzioni dichiarate all'interno di una classe sono chiamate metodi. Alcuni metodi speciali hanno nomi riservati. Per esempio __init__ è il costruttore della classe. Tutte le variabili sono locali al metodo, tranne le variabili dichiarate all'esterno del metodo. Per esempio z è una variabile di classe, equivalente alle variabili membro statiche del C++, che contiene lo stesso valore per ogni istanza della classe.

È da notare che __init__ ha tre argomenti e il metodo add ne ha uno ma vengono chiamati rispettivamente con due e con zero argomenti. Il primo argomento rappresenta, per convenzione, il nome locale usato all'interno dei metodi per riferirsi all'istanza corrente della classe. Qui viene utilizzato self per riferirsi all'oggetto corrente, ma potrebbe essere utilizzato un qualsiasi nome. self ha lo stesso ruolo di *this in C++ o this in Java, ma self non è una parola chiave riservata. In questo modo si evitano ambiguità nella dichiarazione di classi nidificate, come nel caso di una classe definita in un metodo interno ad un altra classe.

Attributi, metodi ed operatori speciali

Gli attributi, i metodi e gli operatori di classe che iniziano con un doppio underscore (__) sono solitamente considerati privati, anche se questa è solamente una convenzione è non è una regola forzata dall'interprete.

Alcuni di questi sono parole chiave riservate ed hanno un significato speciale.

Per esempio:

  • __len__
  • __getitem__
  • __setitem__

possono essere utilizzati per creare un oggetto contenitore che agisce come se fosse una lista:

>>> class MyList(object)
>>>     def __init__(self, *a): self.a = a
>>>     def __len__(self): return len(self.a)
>>>     def __getitem__(self, i): return self.a[i]
>>>     def __setitem__(self, i, j): self.a[i] = j
>>> b = MyList(3, 4, 5)
>>> print b[1]
4
>>> a[1] = 7
>>> print b.a
[3, 7, 5]

Altri operatori speciali sono __getattr__ e __setattr__, che definiscono i modi di impostare e recuperare gli attributi della classe e __sum__ e __sub__, che possono eseguire l'overload degli operatori aritmetici. Sono già stati menzionati gli operatori speciali __str__ e __repr__. È consigliabile riferirsi a letture più approfondite per l'utilizzo di questi operatori.

File Input/Output

file.read
file.write

In Python è possibile aprire e scrivere in un file con:

>>> file = open('myfile.txt', 'w')
>>> file.write('hello world')

Allo stesso modo si può rileggere il contenuto di un file con:

>>> file = open('myfile.txt', 'r')
>>> print file.read()
hello world

In alternativa si possono leggere i file in modalità binaria con "rb", scrivere in modalità binaria con "wb", e aprire i file in modalità append con "a", secondo la notazione standard del C.

Il comando read ha un argomento opzionale che è il numero di byte da leggere. Si può saltare ad una qualsiasi posizione all'interno del file con il comando seek.

file.seek

Si può leggere il contenuto del file con read:

>>> print file.seek(6)
>>> print file.read()
world

il file viene chiuso con:

>>> file.close()

sebbene spesso questo non sia necessario perchè il file è chiuso automaticamente quando la variable che lo rappresenta non è più nello scope.

Quando si usa web2py non si è a conoscenza di quale sia la directory corrente, perchè questo dipende da come web2py è configurato. Per questo motivo la variabile request.folder contiene il path dell'applicazione corrente. I path possono essere concatenati con il comando os.path.join discusso in seguito.

lambda

lambda

Ci sono casi in cui è necessario generare dinamicamente una funzione anonima. Questo può essere fatto utilizzando la parola chiave lambda:

>>> a = lambda b: b + 2
>>> print a(3)
5

L'espressione "lambda [a]:[b]" si può leggere come "una funzione con argomento [a] che ritorna [b]". Anche se la funzione è anonima può essere memorizzata in una variabile e in questo modo acquisisce un nome. Questo tecnicamente è diverso dall'utilizzo di una def, perchè è la variabile che fa riferimento alla funzione anonima che ha un nome, non la funzione stessa.

A cosa servono le lamba? In effetti sono molto utili perchè permettono di riscrivere una funzione in un altra impostando dei parametri di default senza dover creare una nuova funzione. Per esempio:

>>> def f(a, b): return a + b
>>> g = lambda a: f(a, 3)
>>> g(2)
5

Ecco un esempio più completo e più interessante: una funzione che controlla se un numero è primo:

def isprime(number):
    for p in range(2, number):
        if number % p:
            return False
    return True

Questa funzione, ovviamente, impiega molto tempo ad essere eseguita. Si supponga però di avere una funzione di cache cache.ram con tre argomenti: una chiave, una funzione e un numero di secondi:

value = cache.ram('key', f, 60)

La prima volta che viene chiamata la funzione cache.ram richiama la funzione f(), memorizza il risultato in un dizionario e ritorna il suo valore:

value = d['key']=f()

La seconda volta che la funzione cache.ram viene chiamata se la chiave è già nel dizionario e non è più vecchia del numero di secondi specificati (60), ritorna il valore corrispondente senza eseguire la chiamata alla funzione f().

value = d['key']

Come fare per memorizzare nella cache l'output della funzione isprime per ogni valore in input?

Ecco come:

>>> number = 7
>>> print cache.ram(str(number), lambda: isprime(number), seconds)
True
>>> print cache.ram(str(number), lambda: isprime(number), seconds)
True

L'output è sempre lo stesso, ma la prima volta che viene chiamata la funzione cache.ram viene eseguita anche isprime, la seconda volta no.

L'esistenza di lambda permette di riscrivere una funzione esistente in termini di un differente set di argomenti. cache.ram e cache.disk sono due funzioni di cache di web2py.

exec, eval

exec
eval

A differenza di Java Python è un linguaggio completamente interpretato. Questo significa che si ha la possibilità di eseguire dei comandi Python inseriti in una stringa. Per esempio:

>>> a = "print 'hello world'"
>>> exec(a)
'hello world'

Cosa succede in questo caso? La funzione exec comanda all'interprete di richiamare se stesso ed eseguire il contenuto della stringa passata come argomento. È anche possibile eseguire il contenuto di una stringa con uno specifico contesto definito con i simboli inseriti in un dizionario:

>>> a = "print b"
>>> c = dict(b=3)
>>> exec(a, {}, c)
3

In questo caso l'interprete, quando esegue il contenuto della stringa a, vede i simboli definiti in c (b nell'esempio), ma non vede c o a. Questo è differente dall'esecuzione in un ambiente ristretto poichè exec non limita cosa può essere fatto dal codice interno; semplicemente definisce le variabili visibili al codice da eseguire.

Una funzione molto simile ad exec è eval, l'unica differenza è che si aspetta che l'argomento passato ritorni un valore che viene a sua volta ritornato:

>>> a = "3*4"
>>> b = eval(a)
>>> print b
12

import

import
random
Il grande potere di Python risiede nella sua libreria di moduli. Questi infatti forniscono un ampio e consistente set di API (Application Programming Interface) per molte delle librerie di sistema (spesso in un modo indipendente dal sistema operativo).

Per esempio, per utilizzare il generatore di numeri casuali:

>>> import random
>>> print random.randint(0, 9)
5

Questo codice stampa un intero tra 0 e 9 (incluso) generato casualmente, in questo caso il numero 5. La funzione randint è definita nel modulo random. È anche possibile importare un oggetto da un modulo nel namespace corrente:

>>> from random import randint
>>> print randint(0, 9)

o importare tutti gli oggetti da un modulo nel namespace corrente:

>>> from random import *
>>> print randint(0, 9)

o importare tutto il modulo in un nuovo namespace:

>>> import random as myrand
>>> print myrand.randint(0, 9)

Nel resto del libro saranno utilizzati principalmente oggetti definiti nei moduli os, sys, datetime, time e cPickle.

Tutti gli oggetti di web2py sono accessibili tramite un modulo chiamato gluon, e questo sarà il soggetto dei capitoli successivi. Internamente web2py utilizza diversi moduli di Python (per esempio thread), ma sarà raramente necessario accedere ad essi direttamente.

Nelle seguenti sezioni sono elencati i moduli di maggiore utilità.

os

os
os.path.join
os.unlink

Questo modulo fornisce una interfaccia alle API del sistema operativo. Per esempio:

>>> import os
>>> os.chdir('..')
>>> os.unlink('filename_to_be_deleted')

Alcune delle funzioni del modulo os, come per esempio chdir, NON DEVONO essere utilizzate in web2py perchè non sono thread-safe (cioè sicure per essere utilizzate in applicazioni con più thread eseguiti contemporaneamente).

os.path.join è estremamente utile; consente di concatenare i path in un modo indipendente dal sistema operativo.

>>> import os
>>> a = os.path.join('path', 'sub_path')
>>> print a
path/sub_path

Le variabili d'ambiente possono essere accedute con:

>>> print os.environ

che è un dizionario in sola lettura.

sys

sys
sys.path
Il modulo sys contiene molte variabili e funzioni, ma quella che sarà maggiormente usata è sys.path. Contiene infatti una lista di path nei quali l'interprete Python ricerca i moduli. Quando si tenta di importare un modulo Python lo ricerca in tutte le cartelle elencate in sys.path. Se vengono installati moduli aggiuntivi in altre cartelle e si vuol far sì che Python li possa importare è necessario aggiungere il percorso al modulo in sys.path.

>>> import sys
>>> sys.path.append('path/to/my/modules')

Quando si esegue web2py Python rimane residente in memoria ed esiste perciò un unico sys.path mentre ci sono molti thread che rispondono alle richieste HTTP. Per evitare problemi di memoria è bene controllare se il percorso è già presente prima di aggiungerlo a sys.path:

>>> path = 'path/to/my/modules'
>>> if not path in sys.path:
        sys.path.append(path)

datetime

date
datetime
time

L'uso del modulo datetime è illustrato nei seguenti esempi:

>>> import datetime
>>> print datetime.datetime.today()
2008-07-04 14:03:90
>>> print datetime.date.today()
2008-07-04

A volte potrebbe essere necessario marcare un dato con un orario UTC (invece che locale). In questo caso è possibile utilizzare la seguente funzione:

>>> import datetime
>>> print datetime.datetime.utcnow()
2008-07-04 14:03:90

Il modulo datetime contiene diverse classi: date, datetime, time e timedelta. La differenza tra due date, due datetime o due time è un timedelta:

>>> a = datetime.datetime(2008, 1, 1, 20, 30)
>>> b = datetime.datetime(2008, 1, 2, 20, 30)
>>> c = b - a
>>> print c.days
1

In web2py date e datetime sono utilizzati per memorizzare i corrispondenti tipi SQL quando sono passati o ritornati dal database.

time

time

Il modulo time differisce da date e datetime perchè rappresenta il tempo come numero di secondi a partire dall'inizio del 1970 (epoch).

>>> import time
>>> t = time.time()
1215138737.571

Si può far riferimento alla documentazione ufficiale di Python per le funzioni di conversione tra il tempo in secondi e il tempo come datetime.

cPickle

cPickle

Questo è un modulo estremamente potente. Fornisce una serie di funzioni che possono serializzare quasi tutti gli oggeti Python, inclusi gli oggetti auto-referenziali. Ecco per esempio, un oggetto piuttosto strano:

>>> class MyClass(object): pass
>>> myinstance = MyClass()
>>> myinstance.x = 'something'
>>> a = [1 ,2, {'hello':'world'}, [3, 4, [myinstance]]]

ed ora:

>>> import cPickle
>>> b = cPickle.dumps(a)
>>> c = cPickle.loads(b)

In questo esempio b è la rappresentazione (serializzazione) di a in una stringa, e c è una copia di a generata dalla deserializzazione di b.

CPickle può anche serializzare e deserializzare da e su un file:

>>> cPickle.dumps(a, open('myfile.pickle', 'wb'))
>>> c = cPickle.loads(open('myfile.pickle', 'rb'))
 top