Programmazione Orientata agli Oggetti e Scripting in Python...Programmazione Orientata agli Oggetti...

Preview:

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)

Recommended