Upload
others
View
16
Download
0
Embed Size (px)
Citation preview
1
Programmazione Orientata agli Oggetti Programmazione Orientata agli Oggetti e Scripting in Pythone Scripting in Python
Paradigma ad Oggetti - 1
DIEE Univ. di Cagliari
DIEE - Università degli Studi di Cagliari
2
Outline
Introduzione: riassunto caratteristiche OO
Classi
Metodi
Attributi
Creazione/inizializzazione
Eredità e overriding
Information Hiding
Property
Attributi e metodi speciali (overloading di funzioni e operatori)
3
Introduzione
Il Python è un linguaggio che supporta diversi paradigmi/stili di programmazione:
procedurale
funzionale
a oggetti
meta-programmazione
scripting
4
Riassunto funzionalità O.O.
ereditarietà: multipla
dispatching: singolo (un oggetto è proprietario del metodo, eventualmente tramite la classe)
binding: dinamico (e il controllo di esistenza di un metodo/attributo è fatto a run-time)
information hiding: forma "debole" di privatezza e property
polimorfismo: overriding e coercion (esplicita ed implicita)
5
Classi
Una classe è un oggetto che permette di creare altri oggetti (modello “object-factory”)
Gli oggetti creati sono definiti istanze della classe
Per definire una classe, è possibile seguire due approcci:
old-style
new-style
6
Classi “old-style”
Non derivano da alcuna classe base; saranno abbandonate nelle prossime revisioni del linguaggio.
Sintassi generale: class MyClass():
pass
#oppure
class MyClass:pass
7
Classi “new-style”
Derivano tutte, direttamente o indirettamente, dalla classe base object
Il fatto di ereditare da una classe base permette di avere delle funzionalità e comportamenti comuni in tutte le classi della gerarchia
Sintassi generale:
class MyClass(object): pass myobj = MyClass() # costruttore
8
Classe object
object
list str dict
mylist MyClass
9
Classe object
object è la classe di base da cui ereditano tutti i tipi built-in (list, dict, str...)
Una classe può ereditare direttamente o indirettamente da object, ad esempio:
class MyClass(object):
...
class MyList(list):
...
10
Metodi
Esistono tre tipi di metodi definibili in una classe:
di istanza
di classe
Statici
11
Metodi di istanza
Sono definiti come funzioni dentro il corpo di una classe
Il primo argomento è il riferimento all'oggetto che rappresenta l'istanza (self):class P(object):
...
def method(self):
print 'metodo di P'
instance = P()
instance.method()
12
Metodi di istanza:self
self è sempre esplicito nella definizione del metodo, a differenza di C++ e Java, in cui this è passato implicitamente.
La sintassi della chiamata del metodo sull'istanza è invece analoga a quella di Java e C++
Nota: il nome self è solitamente usato per convenzione, ma potrebbe essere utilizzato un altro nome
13
Metodi di istanza
I metodi di istanza sono dei metodi della classe che si applicano alle istanze:
class C(object):
def m(self): pass
x = C()
x.m() è interpretato come C.m(x)
il primo parametro deve essere necessariamente un oggetto di tipo C(ad esempio, C.m(10) sarebbe errato)
14
Metodi di classe
Un metodo di classe è un metodo che si applica alla classe stessa
Viene passato implicitamente un parametro, chiamato per convenzione cls, che rappresenta la classe:
class P(object):
...
def cmethod(cls):
print cls.__name__, 'chiama cmethod'
cmethod = classmethod(cmethod)
P.cmethod()
15
Metodi di classe
Un metodo di classe si genera a partire da un metodo di istanza o da una funzione tramite classmethod
In pratica, si sovrascrive il riferimento ad un metodo di istanza con un riferimento ad un metodo di classe che ha lo stesso nome:
cmethod = classmethod(cmethod)
16
Metodi di classe
E' possibile una sintassi alternativa, che sfrutta i decorators:
class P(object):
... @classmethod
def cmethod(cls):
print cls.__name__, 'chiama cmethod'
17
Metodi di classe
I metodi di classe possono essere chiamati sia dalla classe che da un'istanza della classe
Il riferimento alla classe viene passato come parametro implicito
Nota: non esiste un equivalente in C++/Java (non esiste per questi linguaggi un oggetto che rappresenta la classe!)
p = P()
P.cmethod() # le due chiamate sono
p.cmethod() # equivalenti
18
Metodi statici
Un metodo statico si comporta come una funzione globale dentro il namespace della classe.
Sono simili ai metodi statici di Java e C++, tuttavia non è possibile chiamarli su un'istanza:
class P(object):
...
def smethod():
print ''
smethod = staticmethod(smethod)
P.smethod()
19
Metodi statici
Un metodo statico si genera a partire da un metodo di istanza o da una funzione tramite staticmethod
La sintassi per definire un metodo statico è analoga a quella vista per i metodi di classe
smethod = staticmethod(smethod)
Oppure:
@staticmethod
def smethod()
20
Metodi statici
I metodi statici possono essere chiamati solo dalla classe e non dall'istanza
Non viene passato nessun parametro implicito al metodo! (non hanno quindi riferimento né alla classe né all'istanza)
Equivalgono a delle normali funzioni dentro il namespace della classe
P.smethod()
P().smethod() # sbagliato
21
Creazione/inizializzazione
A differenza di altri linguaggi (C++/Java), gli oggetti sono costruiti e inizializzati in due passi successivi separati:
creazione funzione membro statica __new__
inizializzazionefunzione membro di istanza __init__
22
__new__
La funzione __new__ crea e restituisce una nuova istanza di una classe non ancora inizializzata
E' richiamata automaticamente in fase di creazione di un'istanza di classe
Se non trovata, si risale la gerarchia fino a object. Esempio:
myobj = MyClass(...)
equivale a: myobj = MyClass.__new__(...)
myobj.__init__(...)
23
__new__
Sovrascrivendo la funzione __new__ si possono ottenere dei comportamenti molto particolari. Per esempio:
__new__ può restituire un oggetto già creato
__new__ può costruire un oggetto di una classe diversa da quella attuale
Solo in rari casi si ha effettivamente l'esigenza di riscrivere __new__ (In pratica si usa sempre la __new__ fornita dalla classe object).
24
__new__ example
class MYClass(object): def __new__(cls,x,y): print 'redefined the new class method' ob=object.__new__(cls,x,y) return ob def __init__(self,x,y): self.x=x self.y=y
25
__init__
La funzione __init__ inizializza l'istanza (oggetto) appena creata da __new__
Viene chiamata immediatamente dopo __new__
Nel caso più semplice, l'inizializzazione aggiunge alcuni attributi all'istanza stessa:
class MyClass(object): def __init__(self): self.x = 10 myobj = MyClass() print myobj.x # stampa 10
26
__init__
__init__, come tutte le funzioni, può avere un numero arbitrario di parametri in ingresso che possono essere usati per l'inizializzazione. Esempio:
class MyClass(object):
def __init__(self,x,y):
self.x = x
self.y = y
27
Attributi
Possono essere:
di istanza
di classe
28
Attributi
C++ e Javagli oggetti hanno un numero e un tipo di attributi predeterminati dalla classe.
Pythonoggetti della stessa classe possono avere attributi differenti__init__ serve solo per inizializzare ma non vincola il numero di attributi di un oggetto.
29
Attributi di istanza
L'aggiunta/rimozione/modifica di attributi può avvenire in qualunque momento durante la vita dell'oggetto
class MyClass(object):
def __init__(self):
self.x = 10 # aggiunta di x
myobj = MyClass()
myobj.y = 20 # aggiunta di y
del myobj.x # rimozione di x
myobj.y = 300 # modifica di y
30
Attributi di istanza
class MyClass(object):
def __init__(self):
self.x = 10
o1,o2 = MyClass(), MyClass()
o2.y=20
del o2.x
31
Attributi di classe
Anche le classi sono oggetti su cui è possibile aggiungere degli attributi
In questo caso, si parla di attributi di classe
class P(object):
a = 20
P.b = 10
print P.a, P.b # a,b sono attributi di # classe
32
Attributi di classe
Il modo più semplice di dichiarare un attributo di classe è assegnargli un valore nel corpo della classe
Agli attributi di classe si può accedere anche tramite le singole istanze
class P(object):
a = [1,2,3]
P.a += [4];
P().a += [5];
33
Eredità
Python supporta il meccanismo dell'eredità, che può essere:
singola
multipla
34
Eredità Singola
Eredità singola
C
B
A
object
D
35
Eredità Singola
Il caso di eredità singola è quello più semplice
class C(object): pass
class B(C): pass
class D(C): pass
class A(B): pass
print [cls.__name__ for cls in A.__mro__]
['A', 'B', 'C', 'object']
36
__mro__
__mro__ stands for Method Resolution Order. It returns a list of types the class is derived from, in
the order they are searched for methods.
37
Eredità Multipla
Python supporta anche l'ereditarietà multipla:
B C
A
object
D
38
Eredità Multipla
Esempio di eredità multipla: class B(object):
...
class C(object):
...
class D(object):
...
class A(B,C,D):
... print [cls.__name__ for cls in A.__mro__]
['A', 'B', 'C', 'D' 'object']
39
Eredità: Metodi
I metodi della classe base sono ereditati dalla classe derivata
I metodi statici e di classe si ereditano con le stesse regole dei metodi di istanza
Se si ridefinisce un metodo, la chiamata al metodo corrispondente della classe base non è automatica
Il metodo di istanza __init__ non fa eccezione: si comporta diversamente dai costruttori di C++ e Java
40
Eredità:Metodi
class A(object):
def who_am_I(self):
print "I am A"
class B(object):
def who_am_I(self):
print "I am B"
class C(object):
def who_am_I(self):
print "I am C"
class D(C,B,D):
Pass
>>> d=D()
>>> d.who_am_I()
I am C
41
Eredità: Metodi
class B(object):
def m(self): print 'B.m()'
def sm(): print 'B.sm()'
sm = staticmethod(sm)
def cm(cls): print 'B.cm()'
cm = classmethod(cm)
class D(B):
pass
D().m();
D().cm(); D.sm()
42
Eredità: Ordine di chiamata
Un metodo richiamato su un oggetto viene cercato in una lista ordinata di classi
La lista è univocamente determinata dalla gerarchia di eredità ed è contenuta nell'attributo di classe read-only __mro__
Nota: __mro__ esiste solo nelle classi derivate (anche se indirettamente) da object!
43
Eredità: Ordine di chiamata
Nel caso di eredità multipla, si inizializza __mro__ con la lista della classe più a destra; poi si inseriscono (in testa) le liste delle altre classi proseguendo verso sinistra eliminando i duplicati
C
B
A
object
D
44
Eredità: Ordine di chiamata
Di conseguenza le classi indicate per prime (a sinistra) compariranno nelle prime posizioni della lista
class C(object):pass
class D(object):pass
class B(C):pass
class A(B, D):pass>>>print [cls.__name__ for cls in A.__mro__]
['A', 'B', 'C', 'D', 'object']
45
Eredità multipla
L'eredità multipla può portare a delle gerarchie molto complesse in cui non è semplice determinare la lista di precedenza nella chiamata dei metodi
Potrebbero verificarsi situazioni non gestibili (ambigue): in tal caso è lanciata un'eccezione
46
Eredità: Overriding
A differenza di C++/Java la definizione di un metodo nasconde sempre le definizioni precedenti, anche in presenza di un numero diverso di parametri e all'interno della stessa classe (overriding, no overloading in senso stretto)
In un contesto di eredità, un metodo riscritto in una classe figlia nasconde quello della classe base
Il modo più semplice per richiamare un metodo di una classe base è:
def method(self,attr):
Base.method(self,attr)
47
Eredità: overriding
Spesso un metodo di una sottoclasse deve delegare una parte del lavoro al metodo corrispondente della superclasse.
Un caso tipico è quello dell'__init__: in caso di overriding NON viene richiamato automaticamente quello della classe base ma deve essere richiamato esplicitamente!
class MyList(list):
def __init__(self,L):
list.__init__(self,L)
48
Eredità: overriding
Questa soluzione può generare dei problemi nel caso di eredità multipla
E' possibile che alcuni metodi della classe base vengano chiamati più di una volta (spesso non è la cosa voluta)
Possibile soluzione: funzione built-in super
49
Eredità: super
Restituisce un wrapper della superclasse, intesa come classe successiva della gerarchia
Nell'uso più comune è passato un riferimento all'oggetto chiamante: super restituisce infatti l'elemento successivo alla classe corrente nella lista __mro__ della classe dell'oggetto chiamante
Usato opportunamente permette di gestire situazioni di doppia eredità
50
Eredità: super
class A(object):
def m(self):return "A"
class B(A):
def m(self):return "B" + super(B, self).m()
class C(A):
def m(self):return "C" + super(C, self).m()
class D(C, B):
def m(self):return "D" + super(D, self).m()
>>> print D().m()
DCBA #nota: D.__mro__= [D, C, B, A]
51
Eredità: Attributi
Gli attributi sono aggiunti/cancellati in maniera dinamica
Non si può parlare di eredità in senso classico degli attributi di istanza
Gli attributi di classe sono sempre ereditati
Una classe può avere o meno gli attributi di istanza della classe base a seconda di come si comporta l'inizializzatore
52
Eredità: Attributi
Nell'esempio seguente B.__init__ viene ereditato: in fase di costruzione, ad ogni istanza di D verranno aggiunti gli stessi attributi di B.
class B(object):
def __init__(self):
self.x=10
class D(B):
pass
D().x
53
Eredità: Attributi
Anche in questo caso gli attributi vengono “ereditati” in quanto D.__init__ è ridefinito ma richiama B.__init__.
class B(object):
def __init__(self):
self.x=10
class D(B):
def __init__(self):
B.__init__(self)
D().x
54
Eredità: Attributi
In questo caso invece non vengono aggiunti attributi in quanto B.__init__ non è richiamato automaticamente.
class B(object):
def __init__(self):
self.x=10
class D(B):
def __init__(self):
pass
D().x # errore
55
Information Hiding
Gli attributi sono normalmente considerati “pubblici”
Esiste un concetto di “privatezza” molto diverso rispetto a quello di altri linguaggi (visibilità public/private/protected in C++/Java)
Gli attributi che iniziano con __ (privati) sono trattati in maniera speciale dall'interprete, ma l'accesso non è ristretto
56
Information Hiding
gli attributi speciali iniziano e finiscono con __ e servono per rappresentare funzioni e operatori particolari (__new__, __init__, __add__, __call__, __slots__)
gli attributi privati iniziano con __L'interprete rinomina questi attributi usando il nome della classe di appartenenza
__x -> _ClassName__x
Questo essenzialmente permette alle sottoclassi di usare attributi con lo stesso nome senza modificare il comportamento di eventuali metodi che ne facevano uso
57
Information Hiding
class MyClass(object):
def __init__(self):
self.__x=10 # membro privato
def getX(self):
return self.__x
def setX(self,value):
self.__x=value
o = MyClass()
print o.getX()
print o._MyClass__x
58
Property
Consentono di costruire un data descriptor (ossia un “attributo virtuale”) che richiama delle funzioni durante il suo l'accesso (in lettura, scrittura, cancellazione)
Permettono di realizzare l'information hiding mantenendo una sintassi semplice e pulita
Si definiscono richiamando la funzione built-in property:
x = property(fget=None, fset=Nonem fdel=None, doc=None)
59
Property
class MyClass(object): def __init__(self): self.__x=10 def getX(self): return self.__x def setX(self,value): self.__x=value x=property(fget=getX, fset=setX)
o = MyClass() o.x = 10 # richiama setX print o.x # richiama getX
60
Property
Nell'esempio precedente non è stato specificato fdel, per cui l'attributo x sarà non cancellabile
In maniera analoga, è possibile definire attributi non modificabili (read-only) e/o non accessibili:
class MyClass(object): def __init__(self): self.__x=10 # non modificabile def getX(self): return self.__x x=property(fget=getX) #read-only o = MyClass(object) o.x = 10 # errore
61
Property
class MyClass():
def __init__(self):
self.x=0
def delete(self):
del self.x
x=property(fdel=delete)
>>> o=MyClass()
>>> o.x
0
>>> del o.x
62
Ereditarietà: property
Le property vengono ereditate come se fossero metodi anziché attributirichiamare una property è equivalente chiamare i metodi
fget/fset/fdelnon dipendono in maniera diretta dal comportamento di __init__.
X = property(fget, fset, fdel)
63
Eredità: property
class Persona(object):
def __init__(self,n):
self.nome = n
def getnome(self):
return self.nome
Nome = property(getnome) # read-only
p = Persona('Mario')
print p.Nome
(continua)
64
Ereditarietà: property
class Medico(Persona):
def __init__(self):
pass
m = Medico()
print m.Nome # errore!!!
# Nome esiste ma richiama nome che invece non esiste!
65
Eredità: property
Questo invece funziona. Perché ?
class Persona(object): def __init__(self,n): self.nome = n def getnome(self): return self.nome def setnome(self,n): self.nome = n Nome = property(getnome,setnome) class Medico(Persona): def __init__(self): pass m = Medico() m.Nome = 'Mario' print m.Nome
66
Attributi e metodi speciali
Tutti gli attributi/metodi che iniziano e finiscono con __ hanno un significato speciale in Python.Abbiamo già visto:
__new__ __init____mro__
altri ...
__class____name____dict____slots__...
67
Attributi e metodi speciali
__class__
E' un attributo che contiene un riferimento alla classe a cui appartiene l'oggetto. Tutti gli oggetti, comprese le classi, hanno l'attributo __class__
class P(object):
pass
p = P()
p.__class__
68
Attributi e metodi speciali
__name__
E' un attributo che contiene il nome di una classe. Tutte le classi (non le istanze!), comprese le built-in,
hanno l'attributo __name__
class P(object): pass
P.__name__ # 'P'
int.__name__ # 'int'
P().__name__ # errore
P().__class__.__name__ # 'P'
69
Attributi e metodi speciali
__dict__
E' un attributo molto importante che contiene un dizionario che associa i nomi degli attributi di un oggetto al loro valore. Tutti gli oggetti, comprese le built-in, hanno l'attributo __dict__.
aggiungere/rimuovere un attributo da un oggetto è equivalente ad aggiungere/rimuovere un elemento dal dizionario __dict__.
__dict__ non contiene le property!
70
Attributi e metodi speciali
Esempio:
class P(object):
def __init__(self):
self.x=10
p = P()
p.__dict__ # {'x': 10}
p.__dict__['y'] = 20
p.y # 20
del p.__dict__['y']
71
Attributi e metodi speciali
__slots__
E' possibile limitare il nome degli attributi a quelli presenti nell'attributo di classe __slots__.Migliora l'efficienza.Non è ereditabile
class P(object):
__slots__ = 'a', 'b'
p = P()
p.x = 10 # errore
72
Metodo per chiamata a funzione
__call__(self,...)
E' un metodo di istanza che corrisponde all'operatore di chiamata a funzione ().
Permette di creare “oggetti funzione” come in C++Tutte le funzioni hanno __call__ (chiamata)
Tutte le classi hanno __call__ (costruttore)
Gli altri oggetti possono avere __call__ se lo definisco in maniera esplicita.
73
Metodo per chiamata a funzione
Gli oggetti funzione sono più flessibili delle funzioni normali perché possono avere uno stato interno.
class Retta(object):
def __init__(self,P):
self.P = P
def __call__(self,x):
return self.P * x
retta = Retta(10) # oggetto funzione
print retta(1.2)
74
Metodo per chiamata a funzione
In alternativa posso aggiungere dinamicamente attributi ad una funzione normale in quanto è a tutti gli effetti un oggetto:
def retta(x):
return retta.P * x
retta.P = 2
print retta(10) # 20
Ovviamente il primo approccio è più generale!
75
Metodi per la rappresentazione
__str__
Serve per costruire una rappresentazione “concisa” dell'oggetto
E' utilizzata quando l'oggetto è stampato con print o convertito in stringa con str.
__repr__
Serve a costruire una rappresentazione “completa” dell'oggetto.
La stringa generata “dovrebbe” permettere di ricostruire completamente l'oggetto.
76
Metodi per i contenitori
Esistono alcuni metodi utili per definire il comportamento di classi “contenitore”__len__: chiamata dalla funzione len.
__contains__: operatore in.
__iter__: comportamento come iteratore (for)
__getitem__: accesso in lettura ad un elemento
__setitem__: accesso in scrittura ad un elemento
__delitem__: cancellazione di un elemento
Tutti contenitori built-in (str, list, dict) hanno questi metodi già definiti.
77
Metodi per I contenitori
__len__( self) Implementa la funzione built-in len(). Dovrebbe restituire la lunghezza dell'oggetto, un numero intero >= 0.
__getitem__( self, key) Implementa la valutazione di self[key]. Per i tipi sequenza, le chiavi accettate dovrebbero essere numeri interi ed oggetti slice.
78
Metodi per I contenitori
__setitem__( self, key, value) implementa l'assegnamento a self[key].
__delitem__( self, key) implementa l'eliminazione di self[key]
__iter__( self) Restituisce un oggetto iteratore che può iterare su tutti gli oggetti nel contenitore.
79
Metodi per i contenitori
__getitem__(self, key)__setitem__(self, key, value)__delitem__(self, key)
Queste funzioni servono per leggere, scrivere e cancellare un elemento del contenitore.
L'elemento viene identificato tramite una chiave key.
La chiave può essere per esempio un indice (liste), un valore (dizionari), un oggetto slice
80
Metodi per i contenitori
L = [1,2,3]
L.__getitem__(1) # L[1]
L.__setitem__(0,'a') # L[0]='a'
L.__getitem__(slice(1,2)) # L[1:2]
D = {'a': 0, 'b': 2}
D.__delitem__('a') # del D['a']
81
Metodi per i contenitori
slice E' una classe built-in si usa per rappresentare in maniera
compatta delle sequenze di indici.slice(start,end,step) rappresenta tutti gli indici che
partono da start e terminano in end-1 con passo step.
Le classi str e list hanno già implementato il meccanismo di slicing.
Quando viene creato un nuovo contenitore posso ridefinire il comportamento dello slicing.
82
Metodi per i contenitori
Vediamo alcuni esempi:Lista circolareSetContatore di caratteriComposizione di funzioni
83
Metodi per l'accesso agli attributi
Nella maggior parte dei casi l'accesso agli attributi di un'istanza può essere gestito con le property.
Il Python presenta un ulteriore meccanismo molto più flessibile per l'accesso agli attributi che consiste nel definire alcuni metodi speciali:__getattr____setattr____delattr__
84
Metodi per l'accesso agli attributi
__getattr__(self,name)
Viene chiamato quando si accede all'attributo di nome name e questo non viene trovato in self.
__setattr__(self,name,value)
Viene chiamato quando si cerca di settare l'attributo di nome name con un valore value.
__delattr__(self,name)
Viene chiamato quando si cerca di eliminare l'attributo di nome name.
85
Metodi per l'accesso agli attributi
p.x chiama p.__getattr__('x')
p.x = 100 chiama p.__setattr__('x',100)
del p.x chiama p.__delattr__('x')
Nota: quando si ridefiniscono questi metodi bisogna sempre fare riferimento all'attributo __dict__ in modo da evitare chiamate ricorsive.
86
Metodi per la comparazione
Ci sono diversi metodi che implementano gli operatori matematici di comparazione__eq__ ==__ne__ !=__ge__ >=__gt__ >__le__ <=__lt__ <__cmp__ comparazione per differenza
87
Metodi per la comparazione
__eq__ e __ne__
Ogni oggetto ha un id univoco durante l'esecuzione del programma (solitamente l'indirizzo di memoria)
Normalmente gli operatori == e != controllano se due variabili sono uguali/differennti per id cioè se si riferiscono o meno allo stesso oggetto
Se riscrivo questi metodi posso effettuare un controllo sul valore anziché sull'id
88
Metodi per la comparazione
class P(object): pass class Persona(object): def __init__(self,n): self.nome=n def __eq__(self,other): return self.nome==other.nome
p1,p2 = P(),P() print p1==p2 # diversi p1,p2=Persona('Ale'),Persona('Ale') print p1==p2 # uguali
89
Metodi per i tipi numerici
Ci sono infine diversi metodi che implementano gli operatori matematici più comuni
__add__ +
__sub__ -
__mul__ *
__div__ /
__pow__ **
...
90
Metodi per i tipi numerici
Operatori “aumentati”
__iadd__ +=
__isub__ -=
__imul__ *=
__idiv__ /=
__ipow__ **=
...
91
Metodi per i tipi numerici
In tutto sono circa 50:
Operatori booleani
Operatori di shift
Operatori di conversione
...
(Vedere il manuale di riferimento)