Comments
Description
Transcript
About Python
Python Giovanni Aglialoro slide tratte anche dalle presentazioni di Marco Barisione, Antonio Cuni, Marco Tozzi. Guido van Rossum www.python.org I Monty Python Python: introduzione ► ► È un linguaggio di programmazione: interpretato di altissimo livello semplice da usare potente e produttivo permette programm. procedurale e a oggetti ottimo anche come primo linguaggio (molto simile allo pseudocodice) Inoltre è open source (www.python.org) è multipiattaforma è facilmente integrabile con C/C++ e Java Python: introduzione (2) È veramente usato da qualcuno? ► ► ► ► ► ► RedHat Linux e altre distribuzioni tool di configurazione grafici, applicazioni varie per costruire siti web: CMS, framework Django, Zope NASA www.google.com Industrial Light and Magic (quelli che fanno gli effetti speciali per Star Wars ...) … e molti altri Python: introduzione (3) manuali, documentazione, esercizi www.python.org (ove scaricare la versione 2.7.11 …) http://www.python.it/doc/Howtothink/HowToThink_ITA.pdf http://it.diveintopython.net/ http://www.python.it/doc/articoli/instpy-0.html http://codex.altervista.org/corsopython/corsopython.html http://www.slideshare.net/eugenio1958/manuale-pythonv2 http://www.pierotofy.it/pages/sorgenti/Python/Python_p1/ Python: introduzione (4) interpretati (Basic, Perl, PHP, Python, Ruby, Java) Linguaggi (semplificando…) compilati (C/C++, Pascal, Delphi, Java) codice sorgente codice sorgente estrazione dell’istruzione traduzione (interpretazione) Esecuzione in ling. macchina traduzione del programma esecuzione del programma in ling. macchina 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 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 alcune caratteristiche ► per delimitare i cicli 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 input e output ► Si può formattare l’output come il c: >>> x=18; y=15 >>> print "x=%d y=%d\n" % (x,y) x=18 y=15 ► Per leggere un numero si usa input() >>> x=input(‘Scrivi un numero: ’) darà errore se non si inserisce un numero. ► Per leggere una stringa si usa raw_input() >>> x=raw_input(‘Scrivi il tuo nome: ’) variabili ► I nomi di variabili sono composti da lettere, numeri e underscore, il primo carattere non può essere un numero (come in C) Sono validi: “x”, “ciao”, “x13”, “x1_y”, “_”, “_ciao12” Non sono validi: “1x”, “x-y”, “$a”, “àñÿô” ► Le variabili non devono essere dichiarate (tipizzazione dinamica) ► Una variabile non può essere utilizzata prima che le venga assegnato un valore ► Ogni variabile può riferirsi ad un oggetto di qualsiasi tipo moduli ► Per importare moduli (funzioni matematiche e altro): import math import random ... ► Per usarli: math.sqrt(2), math.log(5), random.choice(‘ATCG’), random.randint(1,50) ecc. if La sintassi di “if” è: if condizione: ... elif condizione_altern.: ... else: ... ► Sia la parte “elif” sia la parte “else” sono facoltative ► Può esserci un numero qualsiasi di “elif” ► Non sono necessarie le parentesi intorno all’espresisone booleana ► Non sono possibili assegnamenti all’interno della condizione a=float(input("dividendo = ")) b=float(input("divisore = ")) quoz=a/b resto=a%b if resto!=0: print"risultato = ",int(quoz) print"resto = ",int(resto) else: print"risultato = ",int(quoz) print"resto = 0" Il costrutto for sembra molto limitato perché, a differenza di altri linguaggi di programmazione, permette di operare solamente sulle liste. In realtà questa piccola limitazione viene facilmente superata utilizzando la funzione for one_to_ten = range(1,11) for count in one_to_ten: print count range Tutto quello che si fa con i cicli for potete farlo anche con while ma grazie ai cicli for è più semplice scorrere tra tutti gli elementi di una lista o compiere un'azione un determinato numero di volte for i in range(-8, 20, 2): print i, i*i while (c’è anche assegnamento multiplo e funzione) es.: calcoliamo i numeri di Fibonacci # Serie di Fibonacci: # Ogni numero e‘ la somma # dei due numeri precedenti a, b = 0, 1 while b < 1000: print b a, b = b, a+b 1 1 2 3 5 8 13 # Fibonacci series up to n # def fib(n): a,b = 0,1 while a<n: print a, a,b = b,a+b fib(5000) 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 ► 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’ 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’ 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’ 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 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] 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] 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] 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 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} 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 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 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 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> 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) 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 Gli oggetti in Python 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. ► 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’ ‘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 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 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. 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. 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. 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() 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__’ 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à 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__). 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’) 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 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). 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 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. Ereditarietà multipla ► ► Persona In presenza di ereditarietà multipla le cose sono più complicate. Seguendo le regole appena descritte l’ordine diStudente Lavoratore 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 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. 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 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’:… }) 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 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 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; ... } 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); ... } 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. 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. 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. 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”. 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.