Implentazione degli
oggetti in Python
Antonio Cuni
Marco Tozzi
Seminario IL
Sommario
1. Panoramica sui linguaggi di scripting
2. Panoramica su Python
3. Implementazione degli oggetti in
Python
Seminario IL
1. Panoramica sui linguaggi
di scripting
Seminario IL
Linguaggi di scripting
• Che cos’è uno script?
• Un piccolo programma per automatizzare un
lavoro ripetitivo e noioso
• Una serie di comandi inviati ad un “ambiente
esterno” che li interpreta e esegue
• Ambiente Esterno:
• SO tramite shell
• Programma applicativo es: MS Office
Con l’aumento della complessità la distinzione
tra script e programmi è sempre più sfumata
Seminario IL
Linguaggi di scripting (2)
• Che cos’è un linguaggio di scripting?
• Supporto per scrivere velocemente e
facilmente script
• Programmazione ad alto livello, consente di
tralasciare dettagli di basso livello
(es: gestione della memoria)
• Alcuni sono talmente potenti da consentire
lo sviluppo di intere applicazioni
Seminario IL
Linguaggi di scripting: esempi
• Specializzati in compiti specifici:
• awk elaborazione di testo organizzato in righe e
colonne
• sed elaborazione di testo arbitrario
• Che consentono di controllare un’applicazione
ospitante:
• Visual Basic for Applications (MS Office)
• Javascript (web browser)
• Emacs Lisp (Emacs)
• General purpouse adatti per applicazioni più
complesse:
• Python
• Perl
• Ruby
Seminario IL
Linguaggi interpretati
• Non richiedono alcuna compilazione
esplicita
• In realtà in molti casi (Python, Perl, Emacs
Lisp) c’è un compilatore che produce bytecode interpretato da una VM
Nota: stessa architettura usata da Java e
.NET solo che in questi è il programmatore
a chiedere la compilazione
Seminario IL
Tipizzazione forte VS debole
Abbiamo spesso (non sempre!) che:
• Linguaggi di script
tipizzazione debole
Es: sommiamo un numero ad una stringa
• Javascript
• 3+"3" == "33"
• 3-"3" == 0
• PHP
• 3+"3" == 6
• 3+"ciao" == 3
Seminario IL
Tipizzazione forte VS debole (2)
• Debole: comoda per script semplici e
brevi
• A lungo andare può portare a bug molto
insidiosi e difficili da trovare
• Es di linguaggio con tipizzazione forte:
Python avrebbe generato un errore negli
esempi precedenti
Seminario IL
Tipizzazione statica VS dinamica
• Tipizzazione statica:
• eseguibili più efficienti
• intercettare alcuni (pochi!) errori a compiletime
• In quasi tutti i linguaggi di scripting
abbiamo tipizzazione dinamica:
• Tipo determinato a run-time e può variare
durante l’esecuzione
• Notevole flessibilità
Seminario IL
2. Introduzione a Python
Seminario IL
Python: introduzione
• È un linguaggio di programmazione:
•
•
•
•
•
Interpretato
Di altissimo livello
Semplice da imparare e usare
Potente e produttivo
Ottimo anche come primo linguaggio (molto simile
allo pseudocodice)
• Inoltre
• È open source (www.python.org)
• È multipiattaforma
• È facilmente integrabile con C/C++ e Java
Seminario IL
Python: introduzione (2)
È veramente usato da qualcuno?
• RedHat
anaconda (installazione), tool di
configurazione grafici, log viewer
• NASA
• www.google.com
• Industrial Light and Magic
quelli che fanno gli effetti speciali per Star
Wars...
• E molti altri …
Seminario IL
Python: l’interprete interattivo
Python dispone di un interprete interattivo
molto comodo e potente:
• Avvio: digitare python al prompt di una shell
appare ora il prompt >>> pronto a ricevere comandi
• Possiamo ora inserire qualsiasi costrutto del linguaggio e vedere
immediatamente l’output:
>>> 3+5
8
>>> print "Hello world!"
Hello world!
L’ istruzione print stampa a video il risultato di un’espressione
Seminario IL
Python come calcolatrice
• Possiamo usare l’interprete interattivo come calcolatrice:
>>> (6+3) / 2
4
>>> 3/2
1
>>> 3/2.0
1.5
• Per usare una variabile basta assegnarle un valore; c’e‘ anche
l’operatore ** che rappresenta l’elevamento a potenza:
>>> x = 3+5
>>> x
8
>>> x**2
64
Seminario IL
Python: manipolazione di stringhe
•
•
Per indicare una stringa bisogna racchiuderla tra virgolette a differenza di molti altri
linguaggi, possiamo usare sia le virgolette singole che quelle doppie
>>> print "ciao"
ciao
>>> print ’Ho detto "ciao"’
Ho detto "ciao"
>>> print "ecco l’apice"
ecco l’apice
Possiamo creare delle stringhe multilinea usando le triple virgolette (sia ’’’ che """):
>>> frase = """questa e‘ una
... stringa che occupa
... tre righe"""
>>> print frase
questa e‘ una
stringa che occupa
tre righe
Seminario IL
Python: stringhe (2)
•
Possiamo concatenare due stringhe usando l’operatore +:
>>> print ’ciao ’ + ’ciao’
ciao ciao
•
Possiamo ripetere una stringa usando l’operatore *:
>>> print ’-’ * 20
------------------->>> print ’ciao ’ * 3
ciao ciao ciao
•
Per accedere ai singoli caratteri di una stringa, usiamo l’operatore [];
l’indice puo‘ anche essere negativo:
>>> ’ciao’[0]
’c’
>>> ’ciao’[1]
’i’
>>> ’ciao’[-1]
’o’
Seminario IL
Python: stringhe(3)
• Le stringhe in Python, come in Java, sono immutabili:
non e‘ possibile assegnare un nuovo valore ai singoli
caratteri.
• Sottostringhe e slices:
usiamo una variante dell’operatore [] chiamata slice
notation:
>>> ’una stringa’[0:3]
’una’
>>> ’una stringa’[1:-1]
’na string’
Seminario IL
Python: slices
mystring[i:j] restituisce tutti i caratteri compresi tra gli
indici i (compreso) e j (escluso):
• Se il primo indice viene omesso, la slice parte dal
primo carattere della stringa:
>>> ’una stringa’[:3]
’una’
• Se il secondo indice viene omesso, la slice arriva sino
all’ulitmo carattere della stringa:
>>> ’una stringa’[4:]
’stringa’
Seminario IL
Python: metodi delle stringhe
• Le stringhe sono oggetti a tutti gli effetti. Alcuni esempi:
>>> ’Ciao’.upper()
’CIAO’
>>> ’Ciao’.lower()
’ciao’
>>> ’ciao anto’.replace(’anto’, ’marco’)
’ciao marco’
• Per ottenere la lunghezza c’è la funzione len():
>>> len(’ciao’)
4
Attenzione! len() è una funzione, si applica anche ad altre
sequenze
Seminario IL
Python: liste
• Una lista e‘ un oggetto composto simile per molti versi
agli array presenti in altri linguaggi.
• Possiamo creare una lista racchiudendone gli elementi
tra parentesi quadre e separandoli con una virgola (gli
elementi possono anche avere tipi diversi):
>>> mylist = [0, 100, ’ciao’, 15.4]
>>> mylist
[0, 100, ’ciao’, 15.4]
• Per accedere agli elementi di una lista usiamo la
stessa notazione usata per le stringhe:
>>> mylist[1]
100
>>> mylist[:2]
[0, 100]
Seminario IL
Python: liste (2)
• Le liste sono oggetti mutabili: possiamo modificarne la
composizione
>>> mylist[1] = ’salve’
>>> mylist
[0, ’salve’, ’ciao’, 15.4]
• Come per le stringhe, anche le liste hanno dei metodi:
>>> mylist
[0, ’salve’, ’ciao’, 15.4]
>>> mylist.append(1)
>>> mylist
[0, ’salve’, ’ciao’, 15.4, 1]
Seminario IL
Python: liste (3)
• Per aggiungere un elemento in una posizione
qualunque, usiamo il metodo insert():
>>> mylist.insert(1, 100)
>>> mylist
[0, 100, ’salve’, ’ciao’, 15.4, 1]
• Possiamo ordinare una lista usando il metodo sort(),
ed invertirla con il metodo reverse():
>>> mylist = [1, 5, 7, 4, 10]
>>> mylist.sort()
>>> mylist
[1, 4, 5, 7, 10]
>>> mylist.reverse()
>>> mylist
[10, 7, 5, 4, 1]
Seminario IL
Python: Tuple
Un altro tipo di sequenza questa volta immutabile
• In genere sono racchiuse tra parentesi tonde:
>>> mytuple = (1, ’ciao’, 100)
>>> mytuple
(1, ’ciao’, 100)
• Possiamo usare una tupla quando in altri linguaggi siamo costretti
a ricorrere a costrutti più complessi (es: struct del C) es:
>>> mypoint = (4, 6)
• possiamo assegnare in un sol colpo ogni elemento ad una
variabile diversa:
>>> mypoint
(4, 6)
>>> x, y = mypoint
>>> x
4
>>> y
6
Seminario IL
Python: dizionari
Python offre anche un supporto nativo per le hash table,
chiamate dizionari.
• espresso come sequenza di coppie chiave:valore
racchiuse tra parentesi graffe e separate da virgole:
>>> voti = {’programmazione’: 18, ’LP’: 26, ’Implementazione’: 30}
>>> voti
{’programmazione’: 18, ’Implementazione’: 30, ’LP’: 26}
• per accedere ai singoli elementi:
>>> voti[’LP’]
26
>>> voti[’Info Gen’] = 20
>>> voti
{’programmazione’: 18, ’Implementazione’: 30, ’Info Gen’: 20, ’LP’: 26}
Seminario IL
Python: esempio di algoritmo
• Calcoliamo i numeri di Fibonacci:
>>>
...
...
...
>>>
...
...
...
1
1
2
3
5
8
# Serie di Fibonacci:
# Ogni numero e‘ la somma
# dei due numeri precedenti
a, b = 0, 1
while b < 10:
print b
a, b = b, a+b
Seminario IL
Osservazioni
• per delimitare il ciclo non usiamo né {} né
begin/end: il blocco e‘ delimitato solo ed
esclusivamente dall’ indentazione.
• assegnamento multiplo
• per delimitare le istruzioni non serve il ;
• sembrerebbe che Python non abbia tipi in
realtà è strongly typed:
• Ogni oggetto ha un tipo che non può cambiare mai
• Le variabili (references) sono come delle “etichette”
che si “appiccicano” agli oggetti
Seminario IL
Python Funzioni
• La keyword def introduce una definizione di funzione
…
• Proviamo a scrivere una funzione che calcoli i numeri
di fibonacci:
>>>def fib(n):
... “””Stampa la serie di Fibonacci fino a n”””
... a, b = 0, 1
... while b < n:
...
print b,
...
a, b = b, a+b
...
>>> fib(2000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
•
Il primo statement può essere una docstring usata da tools che
producono documentazione
Seminario IL
Python funzioni (2)
• Gli argomenti sono passati tramite call by
value (value è un object reference quindi call by
object reference)
• L’esecuzione di una funzione introduce una nuova symbol table
per le variabili locali (look up: local,global,built-in table)
• Function name è introdotto nella symbol table corrente
• Il nome ha un tipo riconosciuto come user-defined function e
un valore
• Può essere assegnato ad un altro nome (es: rinomina)
>>> fib
<function object at 10042ed0>
>>> f = fib
>>> f(100)
1 1 2 3 5 8 13 21 34 55 89
Seminario IL
Python funzioni (3)
• Si possono specificare argomenti di default, quindi le chiamate
possono anche non specificarli tutti:
>>> def info(cognome, nome='Marco'):
... print cognome, nome
...
>>> info('Tozzi')
Tozzi Marco
• Possono essere invocate usando gli argomenti come keywords :
>>> info(cognome='Cuni', nome='Antonio')
Cuni Antonio
• Funzioni possono avere una lista arbitraria di argomenti:
def foo(*arg, **keywords):
*arg è una tupla contenente i parametri posizionali
**keywords è un dizionario dei keyword arguments a cui non corrisponde
un parametro formale
Seminario IL
Python Classi
• Definizione:
class ClassName:
<statement-1>
…
<statement-N>
•
•
Successivamente ad una definizione di classe si crea un class object
I class object supportano due operazioni:
• Riferimento agli attributi:
>>> class MyClass:
... i = 12345
... def f(self):
...
return 'hello world'
...
>>> MyClass.i
12345
>>> MyClass.f
<unbound method MyClass.f>
Seminario IL
Python Classi (2)
• Instanziazione (come una chiamata di funzione senza parametri
che restituisce un’istanza):
x = MyClass()
• Per inserire uno stato iniziale bisogna definire il metodo __init__:
>>> class Complex:
... def __init__(self, realpart, imagpart):
...
self.r = realpart
...
self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)
• Gli attributi sono creati la prima volta che gli viene assegnato un
valore
• La chiamata di metodo passa implicitamente l’istanza dell’oggetto
come primo argomento:
x.f() è perfettamente equivalente a MyClass.f(x)
Seminario IL
Python Class Inheritance
• Sintassi:
class DerivedClassName(BaseClassName):
<statement-1>
…
<statement-N>
• Se BaseClass non è nello stesso modulo:
class DerivedClassName(modname.BaseClassName):
• super(type,[object-or-type]) ritorna la superclasse di
type:
class C(B):
def meth(self, arg):
super(C, self).meth(arg)
• Supporta ereditarietà multipla:
class DerivedClassName(Base1, Base2, Base3):
risoluzione attributi depht-first, left-to-right
Seminario IL
3. Implementazione degli
oggetti in Python
Seminario IL
Oggetti e nomi
• In Python ogni oggetto
risiede in un “universo”
di oggetti e ha una
identità distinta.
• Noi ci riferiamo agli
oggetti dei nomi che si
riferiscono ad essi.
• I nomi degli oggetti sono
raggruppati in
namespaces.
• Quando un oggetto non
è più riferito da alcun
nome, viene raccolto dal
garbage collector.
Seminario IL
x
y
z
w
Universo degli
oggetti
Namespace dei
nomi
Binding di un oggetto
• Il binding è quel meccanismo che associa un
nome ad un oggetto.
• Esso è realizzato dall’operatore =
• Quando assegniamo un valore ad una
variabile, in realtà stiamo legando un nome ad
un oggetto.
x = ‘ciao’
y=x
x = ‘salve’
Seminario IL
‘ciao’
x
‘salve’
y
Tutto è un 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.
>>> def somma(a, b): return a + b
...
>>> def applica(funzione, a, b):
...
return funzione(a,b)
...
>>> applica(somma, 3, 5)
8
Seminario IL
Proprietà di un oggetto
• Da un punto di vista astratto, un oggetto
possiede due proprietà:
• Uno stato, che rappresenta il valore attuale assunto
dall’oggetto.
• Un tipo, che identifica le operazioni che possono
essere compiute sull’oggetto.
• Alcuni tipi sono immutabili: questo significa che
un oggetto di tale tipo non potrà mai cambiare
stato.
Es.: numeri, stringhe, tuple
• Altri tipi sono mutabili, ovvero lo stato dei
rispettivi oggetti può cambiare.
Es.: liste, dizionari
Seminario IL
Layout degli oggetti
• La principale implementazione di Python è
scritta in C ed è chiamata CPython.
• Ogni oggetto di Python è rappresentato da una
struct del C.
• Tali struct sono composte da una serie di
campi; ad esempio:
• Reference count (per la gestione della memoria)
• Puntatore al tipo dell’oggetto (che a sua volta sarà
rappresentato da una struct.
• Campi che memorizzano lo stato.
Seminario IL
Esempio
• Segue la definizione della struttura PyIntObject, che
rappresenta un numero intero.
typedef struct {
int ob_refcnt;
struct _typeobject *ob_type;
long ob_ival;
} PyIntObject;
• Possiamo notare la presenza di tre campi:
• ob_refcnt memorizza il reference count;
• ob_type punta al tipo (che sarà PyInt_Type);
• ob_ival memorizza il valore vero e proprio del numero.
Seminario IL
Layout dei tipi
• In Python anche i tipi sono oggetti ed anch’essi
sono rappresentati da una struct.
• Oltre ai campi precedenti, le struct dei tipi
contengono anche una serie di puntatori a
funzione che identificano alcune operazioni
“standard”.
• Ad esempio le struct dei tipi numerici
contengono puntatori a funzioni che servono a
fare addizioni, sottrazioni, ecc.
Seminario IL
Attributi degli oggetti
• La maggior parte degli oggetti possiede anche un
dizionario, che mappa la corrispondenza tra i nomi e i
valori degli attributi.
• Esempio:
class MiaClasse:
def __init__(self):
self.x = ‘ciao’
def saluta(self):
print self.x
mioOggetto = MiaClasse()
Seminario IL
Esempio
class MiaClasse:
...
mioOggetto = MiaClasse()
struct PyTypeObject {
char* tp_name = “MiaClasse”;
PyObject* tp_dict;
...
}
struct PyInstanceObject
{
PyObject* in_type;
PyObject* in_dict;
...
}
PyDictObject:
‘__init__’
‘saluta’
‘__dict__’
PyDictObject:
‘__class__’
‘x’
‘__dict__’
Seminario IL
PyMethodObject:
self.x = ‘ciao’
PyMethodObject:
print self.x
PyStringObject:
‘ciao’
Lookup degli attributi
• Il meccanismo di lookup degli attributi è il concetto
centrale degli oggetti di Python.
• Esso è progettato in modo tale che alcune idee centrali
del mondo OO diventano quasi automatiche:
• Differenza tra campi (che si riferiscono ad un oggetto) e
metodi (che si riferiscono al suo tipo o classe).
• Ereditarietà singola e multipla
• Polimorfismo
• Metodi e attributi statici e di classe.
• Proprietà
Seminario IL
Regole di lookup (semplificate)
•
Supponiamo di avere un oggetto obj e di
voler accedere ad un attributo di nome “x”:
1. Controllo il nome “x” è presente nel dizionario di
obj (obj.__dict__).
2. Se non c’è controllo se “x” è presente nel
dizionario del tipo di obj (obj.__class__.__dict__)
3. Se non c’è controllo il dizionario di tutte le classi
base (le basi di una classe C sono elencate
nell’attributo C.__bases__).
Seminario IL
Regole di lookup: esempio
class Base:
def __init__(self, nome):
self.nome = nome
def saluta(self):
print ‘ciao’, self.nome
class Derivata(Base):
def saluta(self):
print ‘Buongiorno’, self.nome
obj = Derivata(‘anto’)
Seminario IL
Regole di lookup: esempio
Base
__bases__
__init__
saluta
Derivata
__bases__
saluta
obj
__class__
name
• Lookup di obj.name
• Lookup di obj.saluta
• Lookup di obj.__init__
Lookup di obj.X
•
•
•
Seminario IL
Controllo obj.__dict__
Controllo obj.__class__.__dict__
Controllo tutte le classi in
obj.__class__.__bases__
Funzioni e metodi
• Abbiamo visto che le classi possono avere dei metodi.
• Internamente i metodi sono memorizzati come normalissime
funzioni, ma con una differenza: il primo parametro (self) è
passato automaticamente.
• Esempio
class MiaClasse:
def mioMetodo(self, a):
print a
obj = MiaClasse()
obj.mioMetodo(‘ciao’)
• Al momento della chiamata passo un solo argomento a
mioMetodo, ma l’interprete ne aggiunge automaticamente uno (il
self).
Seminario IL
Bound methods
• Questo è possibile perché quando la procedura di lookup si
accorge di aver trovato una funzione, la trasforma in un bound
method.
• I bound method sono delle funzioni che “si ricordano” a quale
oggetto fanno riferimento: quando sono chiamati aggiungono
automaticamente tale oggetto in cima alla lista degli argomenti.
• L’effetto finale è che quando chiamiamo un metodo su un oggetto
il parametro self è passato in modo automatico.
• I bound method sono oggetti come gli altri: posso anche
memorizzarli in una variabile e chiamarli più tardi.
>>> obj = MiaClasse()
>>> obj.mioMetodo # nota: non è chiamato!
<bound method MiaClasse.MioMetodo of
<__main__.MiaClasse instance at 0x0082CAD8>>
>>> xxx = obj.mioMetodo
>>> xxx(‘ciao’)
ciao
Seminario IL
Ereditarietà singola
• Abbiamo visto che se la procedura di
lookup non trova un attributo cerca
ricorsivamente nelle classi base.
• Questo ci consente di implementare
“gratis” l’ereditarietà: tutto quel che
dobbiamo fare è memorizzare
nell’attributo __bases__ la nostra classe
base, e la procedura di lookup farà il
resto.
Seminario IL
Ereditarietà multipla
Persona
• In presenza di ereditarietà multipla le cose
sono più complicate.
• Seguendo le regole appena descritte l’ordine Studente Lavoratore
di lookup di un attributo su uno
StudenteLavoratore sarebbe:
• StudenteLavoratore, Studente, Persona,
Lavoratore, Persona
StudenteLavoratore
• In genere non è questo che vogliamo, perché altrimenti i metodi di
Persona sovrascritti da Lavoratore non verrebbero mai chiamati.
• Inoltre la classe Persona sarebbe controllata due volte.
• Al momento della creazione della classe viene calcolato un ordine
di lookup seguendo una visita in ampiezza del grafo di ereditarietà.
• Tale ordine è memorizzato nell’attributo __mro__ (Method
Resolution Order) della classe, che viene usato dalla procedura di
lookup per controllare le classi base.
• Il MRO di StudenteLavoratore è:
• StudenteLavoratore, Studente, Lavoratore, Persona
Seminario IL
Polimorfismo
• 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.
Seminario IL
Esempio di polimorfismo
>>> class Classe1:
...
def saluta(self): print 'ciao'
...
>>> class Classe2:
...
def saluta(self): print 'salve'
...
>>> def miaFunzione(obj):
...
obj.saluta()
...
>>> c1 = Classe1()
>>> c2 = Classe2()
>>>
>>> miaFunzione(c1)
ciao
>>> miaFunzione(c2)
salve
Seminario IL
Creazione delle classi
• In Python tutto, comprese le classi, è un oggetto.
• Il tipo di una generica classe è type.
• Quando definiamo una classe non facciamo altro che creare una
istanza di type!
• Il costruttore di type vuole tre parametri: il nome della classe,
l’elenco delle sue basi e il dizionario dei suoi attributi.
• Il dizionario degli attributi non è altro che un normalissimo
dizionario in cui ad ogni nome di attributo corrisponde il suo valore
(normalmente sarà una funzione).
class A(B,C):
• Nell’esempio a fianco:
def metodo1(self):
• Il nome della classe è la stringa ‘A’
print ‘ciao’
• L’elenco delle basi è la tupla (B,C)
def metodo2(self):
• Il dizionario degli attributi contiene
print ‘salve’
le chiavi ‘metodo1’ e ‘metodo2’
• A = type(‘A’, (B,C), {‘metodo1’: …,‘metodo2’:… })
Seminario IL
Creazione delle classi
>>> def __init__(self, nome):
...
self.nome = nome
...
>>> def saluta(self):
...
print ‘ciao’, self.nome
...
>>> attributi = {'__init__': __init__, 'saluta':
saluta}
>>> MiaClasse = type('MiaClasse', (), attributi)
>>>
>>> obj = MiaClasse('anto')
>>> obj.saluta()
ciao anto
Seminario IL
Implementazione del lookup
• L’interprete Python è basato su una macchina astratta.
• Per vedere come è implementato il meccanismo di lookup
possiamo iniziare ad esaminare il bytecode eseguito da tale
macchina.
• Il modulo standard dis serve a disassemblare il bytecode.
>>> import dis
>>> def prova(x):
...
return x.attributo
...
>>> dis.dis(prova)
0 SET_LINENO
1
3 SET_LINENO
2
6 LOAD_FAST
0 (x)
9 LOAD_ATTR
1 (attributo)
12 RETURN_VALUE
Seminario IL
L’opcode LOAD_ATTR
•
•
L’opcode della macchina che si occupa di eseguire un lookup è
LOAD_ATTR.
Esaminiamo il sorgente C della macchina virtuale; questo è il main loop:
switch (opcode) {
case LOAD_FAST: ...
case LOAD_CONST: ...
case LOAD_ATTR:
w = GETITEM(names, oparg);
v = TOP();
x = PyObject_GetAttr(v, w);
Py_DECREF(v);
SET_TOP(x);
if (x != NULL) continue;
break;
...
}
Seminario IL
PyObject_GetAttr
• A questo punto possiamo esaminare la funzione
PyObject_GetAttr, definita in Objects/object.c:
PyObject *
PyObject_GetAttr(PyObject *v, PyObject *name)
{
PyTypeObject *tp = v->ob_type;
...
if (tp->tp_getattro != NULL)
return (*tp->tp_getattro)(v, name);
...
}
Seminario IL
Il campo tp_getattro
• Esaminando il codice precedente possiamo notare una
cosa importante: la funzione che esegue il lookup vero e
proprio non è univoca, ma dipende dal tipo dell’oggetto.
• Ogni tipo ha un campo tp_getattro che contiene un
puntatore ad una funzione che esegue il lookup.
• Questo significa che se quella di default non ci soddisfa
possiamo anche definire una nostra politica di lookup.
• Il campo tp_getattro della struct PyType_Type punta alla
funzione type_getattro, definita in Objects/typeobject.c: tale
funzione implementa la politica di lookup vista in
precedenza.
• Ai margini è interessante notare come, in pratica, gli
sviluppatori di Python hanno implementato in C un
meccanismo di dispatching simile a quello delle funzioni
virtuali presenti in altri linguaggi.
Seminario IL
Cenni sul descriptor protocol
• In realtà l’implementazione di Python è leggermente
più complessa di quanto abbiamo visto fin’ora.
• Prima di continuare, definiamo un nuovo termine:
• Un descrittore è un oggetto che possiede un metodo __get__
(e opzionalmente un metodo __set__)
• Durante il lookup di un attributo, prima di restituire
l’oggetto corrispondente controlliamo se quest’ultimo è
un descrittore.
• Se l’oggetto è un descrittore, chiamiamo il suo metodo
__get__ (o __set__, a seconda dei casi) e restituiamo
il risultato.
• Questo meccanismo consente agli oggetti di
controllare la modalità con cui possono essere
acceduti.
Seminario IL
Esempio di descrittore
• Senza saperlo, abbiamo già visto un esempio di descrittore: le
funzioni.
• Le funzioni hanno un metodo __get__: se esso si accorge che
stiamo facendo il lookup su una istanza di classe, costruisce al
volo un bound method e lo restituisce.
• In genere il metodo __get__ viene chiamato automaticamente
dall’interprete, ma possiamo anche farlo “a mano”:
>>> def somma(a, b): return a+b
...
>>> dummyMethod = somma.__get__(3)
>>> dummyMethod
<bound method ?.somma of 3>
>>> dummyMethod(5)
8
• In questo esempio abbiamo “fatto credere” a somma di essere un
metodo dell’oggetto 3. Quando richiamiamo il metodo il primo
parametro (3) è aggiunto automaticamente.
Seminario IL
Altri descrittori utili
• Il descriptor protocol è un meccanismo fondamentale
per implementare alcuni costrutti OO; ecco alcuni
esempi:
• staticmethod: è un tipo il cui costruttore prende una funzione e
restituisce un descrittore che, al contrario delle normali
funzioni, non le trasforma in bound method. Questo consente
di ottenere dei metodi statici.
• classmethod: è un tipo il cui costruttore prende una funzione e
restituisce un descrittore che, quando acceduto, lega una
funzione alla classe dell’istanza di provenienza. Questo
consente di ottenere dei metodi di classe.
• property: questo tipo accetta come parametri due funzioni: un
“getter” e un “setter”: restituisce un oggetto che quando viene
acceduto inoltra la richiesta al getter o al setter, a seconda dei
casi. Questo consente di implementare le proprietà: agli occhi
dell’utente appaiono come normali attributi, ma in realtà
quando vengono accedute vengono automaticamente
chiamate dei metodi “trigger”.
Seminario IL
Metaclassi
• Come tutti gli oggetti, anche le classi hanno un tipo: esso è
chiamato metaclasse (la “classe della classe”).
• Abbiamo visto che quando definiamo una classe, l’interprete crea
una istanza di type: in questo caso, type è proprio la metaclasse.
• Possiamo anche definire delle metaclassi alternative da usare al
posto di type: con esse possiamo alterare il normale
funzionamento delle istanze.
• Ecco alcuni esempi di possibili metaclassi:
• readonly: le classi istanziate potranno avere solo attributi read-only
• interface: tutti i metodi delle classi istanziati dovranno essere astratti
• monitor: le classi istanziate si comporteranno come un monitor
• Per determinare la metaclasse da usare, l’interprete guarda se nel
dizionario è presente l’attributo __metaclass__; altrimenti usa la
metaclasse di default.
• Abbiamo barato ! In tutti gli esempi che abbiamo fatto fin’ora,
abbiamo assunto che la metaclasse di default fosse proprio type.
• In realtà questo non è vero, per ragioni di compatibilità con
versioni di Python precedenti alla 2.2.
Seminario IL