Una nuova classe¶
Dove definiamo la nostra prima classe
Classi e oggetti¶
Riassumiamo quello che avviene quando si esegue il programma man1.py
:
- viene caricata la libreria
pyturtle.py
; - viene creato un oggetto della classe
TurtlePlane()
della libreriapyturtle.py
; - viene creata una tartaruga cioè un oggetto della classe
Turtle
della libreriapyturtle.py
; - vengono definite tre funzioni (
sposta()
,quadrato()
,muro()
); - viene eseguito il programma principale che sposta la tartaruga e chiama
la funzione
muro
che, a sua volta, chiama le funzioniquadrato
esposta
.
Nel programma precedente abbiamo già creato e utilizzato un oggetto
della classe TurtlePlane
e un oggetto della classe Turtle
.
Abbiamo crearto una tartaruga con l’istruzione:
tina = tg.Turtle()
tina
è un nome (un identificatore) che ci siamo inventati noi;- al nome
tina
è associato un oggetto della classeTurtle
presente nella libreriatg
(che sta perpyturtle
).
Quindi tina
è un riferimento ad un oggetto della classe Turtle
.
Nella classe vengono definite le caratteristiche e i comportamenti degli
oggetti appartenenti a quella classe.
In questo capitolo vogliamo creare una nuova classe di oggetti.
La libreria pyturtle
definisce le classi TurtlePlane
e
Turtle
, concentriamoci sulla seconda e, per semplicità, chiamiamo
tartaruga un oggetto della classe Turtle
.
Una classe può essere utilizzata per:
- costruire oggetti con le proprietà e i metodi di quella classe;
- costruire altre classi che ampliano quella classe.
Metodi¶
- Ogni tartaruga è in grado di eseguire alcuni comandi,
sono i suoi metodi:
forward(<numero>)
,back(<numero>)
, …
I metodi sono funzioni che possono essere eseguite
da tutti gli oggetti di quella classe.
Per dire ad un oggetto di una classe
di eseguire un proprio metodo, devo scrivere il nome dell’oggetto seguito dal
punto, dal nome del metodo e da una coppia di parentesi contenenti,
eventualmente, gli argomenti necessari.
In pratica se tina
è un oggetto della classe Turtle
l’istruzione:
tina.forward(97)
chiede all”oggetto collegato al nome tina
di eseguire il proprio metodo
forward
e 97
è l”argomento passato a questo metodo.
Quindi l’istruzione precedente comanda alla tartaruga tina
di avanzare
di 97 passi.
Anche la classe TurtlePlane definisce dei propri metodi che possono essere
eseguiti da ogni oggetto di questa classe.
piano.reset()
comanda a piano
di ripulire tutto il piano e di riportare la situazione
allo stato iniziale.
Perché ogni metodo deve essere preceduto dal nome dell’oggetto?
Non sarebbe più semplice scrivere solo: forward(97)
o reset()
?
Il fatto è che possiamo creare quanti oggetti vogliamo della classe Turtle
quindi quando diamo un comando, dobbiamo specificare a quale oggetto quel
comando è diretto:
piano = tg.TurtlePlane()
tina = tg.Turtle(color='red')
pina = tg.Turtle(color='blue')
gina = tg.Turtle(color='green')
pina.left(120)
gina.left(240)
tina.forward(50)
pina.forward(100)
gina.forward(200)
In questo caso vengono create tre tartarughe, vengono sfasate di 120 gradi l’una dall’altra e infine vengono fatte avanzare di tre lunghezze diverse.
Attributi¶
- Ogni tartaruga ha diverse caratteristiche proprie,
- sono i suoi attributi:
color
,width
,position
,direction
, …
Gli attributi definiscono lo stato di un oggetto. Ad esempio, per cambiare
il colore della tartaruga e della sua penna si deve modificare il suo
attributo color
:
color = <colore>
dove <colore>
è una stringa che contiene il nome di
un colore, o color = (<rosso>, <verde>, <blu>)
dove <rosso>
,
<verde>
, <blu>
sono tre numeri decimali compresi tra 0 e 1:
tina.color = "purple"
o
tina.color = (0.4, 0.4, 0.4) # grigio
Viceversa se voglio memorizzare in una variabile l’attuale colore di una
tartaruga potrò assegnare ad una variabile il valore di color
:
colore_attuale_di_tina = tina.color
Come visto sopra si possono definire gli attributi di una tartaruga anche al momento della sua creazione.
Nota
per l’elenco completo di attributi e metodi si vedano i capitoli dedicati alle singole librerie.
Nuove classi¶
Una caratteristica importante delle classi è l’ereditarietà. Una classe può venir derivata da un’altra classe essere cioè figlia di un’altra classe; la classe figlia eredita dalla classe genitrice tutti gli attributi e i metodi e può:
- aggiungere altri attributi,
- aggiungere altri metodi,
- modificare i metodi della classe genitrice.
Buona parte della programmazione OOP consiste nel progettare e realizzare classi e gerarchie di classi di oggetti. Realizzare una nuova classe può essere un lavoro molto complicato ma potrebbe essere anche molto semplice quando la classe che realizziamo estende qualche altra classe già funzionante.
Come esercizio proviamo a costruire la classe di una tartaruga che sappia
costruire un muro. Come al solito, prima di mettere mano a grandi opere,
iniziamo a lavorare ad un problema abbastanza semplice e conosciuto.
Voglio avere una tartaruga che oltre a saper fare tutto quello che sanno
fare le altre tartarughe sappia anche disegnare mattoni quadrati:
la nuova tartaruga deve quindi estendere le capacità di Turtle
.
La sintassi che Python
mette a disposizione per estendere la gerarchia di
una classe è:
class <nome di una nuova classe>(<nome di una classe esistente>):
Per iniziare gli esperimenti avviamo IDLE
, aprimao un nuovo file:
menu-File-New File
esalviamo questo file nella cartella dove mettiamo
tutti i nostri lavori: menu-File-Save As
.
Come sempre scriviamo le informazioni relative al programma e la docstring con una sintetica descrizione di cosa deve fare il programma stesso.
Poi:
carichiamo la libreria
pyturtle
;definiamo una nuova classe derivata dalla classe
tg.Turtle
, che chiameremoIngegnere
. Sarà una classe che, per ora, non fa niente (istruzionepass
);infine scriviamo il programma principale che:
- crea un piano,
- crea un
Ingegnere
, - cambia qualche attributo a questo ingegnere,
- gli chiede di tracciare una linea in avanti di 100 passi,
- rende attiva la finestra grafica.
# man2.py
# 3 gennaio 2018
# Daniele Zambelli
"""
Il programma deve creare una classe di oggetti
capaci di disegnare un muro di mattoni quadrati.
"""
# Lettura delle librerie
import pygraph.pyturtle as tg
# Definizione di classi
class Ingegnere(tg.Turtle):
"""Una tartaruga che sa costruire muri."""
pass
# Programma principale
piano = tg.TurtlePlane()
leonardo = Ingegnere()
leonardo.color = 'red'
leonardo.width = 6
leonardo.forward(100)
# Rende attiva la finestra grafica
piano.mainloop()
Corretti eventuali errori di battitura possiamo osservare che la classe
Ingegnere
derivata dalla classe tg.Turtle
e che contiene solo
l’istruzione pass
, si comporta esattamente come una tartaruga.
Infatti leonardo
è un oggetto della classe Ingegnere
e
Ingegnere
è un discendente di tg.Turtle
e quindi, già alla nascita, sa fare tutto
quello che sa fare tg.Turtle
.
Ora estendiamo le capacità di Ingegnere
in modo che sappia disegnare
mattoni quadrati.
Modifichiamo il programma sostituendo pass
con la definizione del
metodo quadrato
e modificando anche il programma principale:
# Definizione di classi
class Ingegnere(tg.Turtle):
"""Una tartaruga che sa costruire muri."""
def quadrato(lato):
"""Disegna un quadrato di dato lato."""
for i in range(4):
forward(lato)
left(90)
# Programma principale
piano = tg.TurtlePlane()
leonardo = Ingegnere()
leonardo.quadrato(80)
ed eseguiamo il programma:
Traceback (most recent call last):
File "/dati/.../prove/man2.py", line 25, in <module>
leonardo.quadrato(80)
TypeError: quadrato() takes 1 positional argument but 2 were given
Accidenti, non va!!! E non solo non funziona, ma ci dà un errore decisamente
assurdo: Python
si lamenta che quadrato vuole un argomento e noi gliene
avremmo passati due!? È strabico? Non sa contare?? È stupido???
Boh, mah, forse…
Chi ha programmato questo linguaggio ha deciso che l’oggetto che deve
eseguire un metodo viene passato come primo parametro del metodo stesso.
Nota
Noi scriviamo:
leonardo.quadrato(80)
in realtà viene eseguito:
Ingegnere.quadrato(leonardo, 80)
provare per credere.
Quindi se quadrato
è un metodo di una classe deve avere un primo
parametro dentro il quale viene messo il riferimento all’oggetto che deve
eseguire il metodo stesso.
Per convenzione questo parametro viene chiamato self
e noi seguiamo
questa convenzione.
Modifichiamo il metodo quadrato
mettendo self
come primo parametro:
class Ingegnere(Turtle):
"""Una tartaruga che sa costruire muri."""
def quadrato(self, lato):
"""Disegna un quadrato di dato lato."""
for i in range(4):
forward(lato)
left(90)
Ora quadrato
ha i due parametri: uno per contenere l’oggetto che deve
eseguire il metodo e uno per contenere la lunghezza del lato.
Fatti questi cambiamenti eseguiamo il programma ottenendo:
Traceback (most recent call last):
File
File "/dati/.../prove/man2.py", in <module>
leonardo.quadrato(80)
File "/dati/.../prove/man2.py", line 18, in quadrato
forward(lato)
NameError: name 'forward' is not defined
Ancora qualcosa che non va…
Eppure questa volta quadrato ha i due parametri richiesti!
Infatti l’errore è cambiato: ci dice che non esiste un nome globale
forward
.
Infatti forward
è un metodo della classe Turtle
e quindi
può essere eseguito solo da un oggetto di questa classe. Ma dove lo trovo un
oggetto della classe Turtle
mentre sto definendo la mia nuova classe?
Se osserviamo bene, la soluzione al precedente errore ce l’ha messo
a disposizione, è proprio l’oggetto contenuto in self
.
Dobbiamo modificare la chiamata a forward
scrivendo:
self.forward(lato)
. Terzo tentativo:
class Ingegnere(Turtle):
"""Una tartaruga che sa costruire muri."""
def quadrato(self, lato):
"""Disegna un quadrato di dato lato."""
for i in range(4):
self.forward(lato)
self.left(90)
Ovviamente quello che facciamo per forward
lo dobbiamo fare anche per
left
.
Modifichiamo l”Ingegnere e proviamo ancora il metodo ricorretto.
Va!!! Ora abbiamo una classe Ingegnere
che oltre a saper fare tutto
quello che sanno fare tutte le Tartarughe sa anche disegnare quadrati.
Ora possiamo prendere le altre funzioni del programma man1.py e
trasformarle in metodi aggiungendo il parametro self
dove serve
modificando opportunamente il programma principale.
Di seguito riporto l’intero programma
# man2.py
# 3 gennaio 2018
# Daniele Zambelli
"""
Il programma deve creare una classe di oggetti
capaci di disegnare un muro di mattoni quadrati.
"""
# Lettura delle librerie
import pygraph.pyturtle as tg
# Definizione di classi
class Ingegnere(tg.Turtle):
"""Una tartaruga che sa costruire muri."""
def sposta(self, o=0, v=0):
"""Effettua uno spostamento orizzontale e verticale di
Tartaruga senza disegnare la traccia."""
self.up()
self.forward(o); self.left(90)
self.forward(v); self.right(90)
self.down()
def quadrato(self, lato):
"""Disegna un quadrato di dato lato."""
for i in range(4):
self.forward(lato)
self.left(90)
def muro(self, lato, righe, colonne,
spazio_righe=5, spazio_colonne=5):
"""Disegna un muro di mattoni quadrati."""
for i in range(righe):
for j in range(colonne):
self.quadrato(lato)
self.sposta(o=lato+spazio_colonne)
self.sposta(o=-colonne*(lato+spazio_colonne),
v=lato+spazio_righe)
self.sposta(v=-righe*(lato+spazio_righe))
# Programma principale
piano = tg.TurtlePlane()
leonardo = Ingegnere()
leonardo.sposta(-250, -190)
leonardo.muro(20, 15, 20)
# Rende attiva la finestra grafica
piano.mainloop()
Salviamo, eseguiamo, … correggiamo gli errori che inevitabilmente sono stati fatti, …, rieseguiamo…
A parte la complicazione del parametro self
, possiamo vedere come
Python
ci permetta di definire nuove classi in modo estremamente
semplice.
Utilizzando l’ereditarietà, cioè scrivendo classi derivate da altre già
realizzate da altri, possiamo ottenere, con poche righe di codice, classi:
- potenti, perché estendono altre classi;
- sicure, perché le classi genitrici sono utilizzate da molti altri programmatori ed eventuali errori sono sicuramente già stati trovati e corretti;
- ulteriormente estendibili, ma questo lo vedremo nel prossimo capitolo.
Riassumendo¶
Per definire una classe si usa il comando
class <nome della classe>():
Per definire una classe discendente da un’altra si utilizza il comando:
class <nome della classe figlia>(<nome della classe genitrice>):
Quando si scrive una classe discendente da un’altra basta scrivere i metodi che si aggiungono ai metodi della classe genitrice o che li sostituiscono.
Quando si definisce un metodo di una classe si deve mettere come primo parametro il nome di una variabile che conterrà l’oggetto stesso, di solito
self
.All’interno di una classe, il parametro
self
permette di richiamare i metodi e le proprietà dell’oggetto stesso.