4.4. Rette ed estensione della classe Plot Come risolvere problemi di geometria analitica e come ampliare le capacità dei già potenti strumenti messi a disposizione da Pygraph. Finora abbiamo pasticciato scrivendo comandi nella finestra interattiva di IDLE, ora è giunto il momento di scrivere un programma. Scriviamo il programma che risolva un tipico problema di geometria analitica: calcolare l’equazione della retta passante per due punti e disegnarla. Prima di affrontare i problemi però decidiamo come rappresentare in Python una retta. La solita equazione in due variabili in forma esplicita “y=mx+q” o implicita: “ax+by+c=0” non è comoda: è complicato estrarre le informazioni da una stringa scritta in questo modo. Come per rappresentare un punto usiamo una coppia di numeri, così possiamo usare una coppia di numeri (m, q) per rappresentare una retta. Dal capitolo precedente possiamo recuperare la funzione retta(m, q) che, dati i due coefficienti produca l’espressione eseguibile della funzione retta. Altra osservazione. Sarebbe comodo che gli oggetti della classe Plot avessero la capacità di disegnare rette, ma purtroppo non è così, hanno il metodo drawpoint, drawsegment, drawpoly, ma non un metodo drawline. Python permette di estendere facilmente le capacità di un oggetto. Possiamo costruire una classe che deriva tutte le caratteristiche della classe Plot e aggiunge ad essa il metodo drawline(retta). La nuova classe la chiameremo Mioplot. Una convenzione, accettata da tutti i pitonisti, prevede che i nomi delle classi inizino con la lettera maiuscola e noi ci adeguiamo volentieri, per rendere più comprensibile il nostro codice. Per dire a Python che vogliamo creare una nuova classe che estende le capacità di un’altra preesistente, possiamo usare la seguente sintassi: class Nuovaclasse(vecchiaclasse):

<nuovi metodi>

Non ci resta che incominciare a scrivere. Predisponiamo un nuovo file, salviamolo con il nome rette.py, scriviamo alcune righe di commento con le solite informazioni: autore, data, contenuto del file, ... Poiché la nostra nuova classe deriva dalla classe Plot contenuta nella libreria pyplot.py dobbiamo innanzitutto far caricare questa liberia con il comando: from pyplot import Plot Ora siamo pronti per creare la nuova classe: class Mioplot(Plot): La classe Mioplot ha tutte le caratteristiche e le capacità della classe Plot, ma noi vogliamo estenderle con dei nuovi metodi. Il primo serve per trasformare la coppia di numeri che rappresenta una retta in un’espressione lineare interpretabile da Python. I metodi di una classe hanno sempre, come primo parametro una variabile che conterrà il riferimento all’oggetto su cui il metodo deve lavorare. Per consuetudine a questo parametro viene dato il nome “self”. Quindi il metodo retta deve far precedere i due parametri: coefficiente angolare e termine noto dal parametro self:

def retta(self, r):
“”“Restituisce la stringa che contiene la funzione
di una retta.”“”

return “%s*x+%s” % r

L’altro metodo che aggiungiamo a Plot serve per disegnare la retta rappresentata da una coppia di punti:
def drawline(self, r):
“”“Disegna una retta dati
(coeff.angolare, termine noto).”“”

self.xy(self.retta(r))

Bene non ci resta che scrivere la funzione che calcola la retta per due punti e poi provare tutto. Recuperiamo da qualche parte il modo per calcolare coefficiente angolare e termine noto di una retta che passi per due punti, qualcosa come: e : def retta2p(a, b):

“”“Restituisce m e q della retta passante per a e b.”“” xa, ya = a xb, yb = b m=float(yb-ya)/(xb-xa) q=ya-m*xa return m, q

Ed ora possiamo scrivere un test per le nostre funzioni: def test():

piano=Mioplot() # Crea un oggetto della classe Mioplot piano.axes(True) # chiede a piano di disegnare gli assi p0=(-4, -5) # Associa alle var. p0, p1, p2 tre p1=(4, -7) # coppie ordinate rappr. tre punti p2=(-4, 5) piano.setwidth(3) # aumenta le dim. degli ogg. disegnati piano.drawpoint(p0)#disegna i tre punti piano.drawpoint(p1) piano.drawpoint(p2) piano.setwidth(1) # riporta a 1 la dim. della “penna” piano.drawline(retta2p(p0, p1)) # disegna le tre rette piano.drawline(retta2p(p1, p2)) piano.drawline(retta2p(p2, p0))

Quando eseguiamo questo programma vogliamo che venga eseguita automaticamente la funzione test, aggiungiamo, infondo al file, la riga: if __name__ == “__main__”: test () Bene dopo tutta questa attività di “coding” godiamoci il funzionamento del programma: premiamo <F5>, correggiamo gli eventuali errori di sintassi, se non succede niente controlliamo il testo del programma, se tutto va, vengono disegnati i tre punti e le tre rette. No, accidenti, non tre rette, ma solo due, al posto delle terza appare nella shell un messaggio di errore. (Se appare un messaggio di errore ma non appaiono i tre punti e le due rette nella finestra grafica, bisogna cercare di capire, trovare e correggere gli errori confrontando il programma eseguito con quello descritto sopra.) Leggiamo le ultime righe del messaggio:

File “/mnt/dati/daniele/06-07/scuola/materiali/informatica/pyplot/rette.py”, line 49, in retta2p
m=float(yb-ya)/(xb-xa)

ZeroDivisionError: float division ci danno importanti informazioni sull’errore riscontrato. L’ultima riga ci informa che il nostro codice ha chiesto a Python di eseguire una divisione per zero, cosa non buona... La penultima riga riporta l’istruzione in cui si è verificato il fattaccio, la terzultima il numero di riga e la funzione in cui è scritto questo comando. Cerchiamo di capire cosa non va in questa funzione. Il problema non è di sintassi e non è un problema informatico ma geometrico. Veramente se si sceglie un’altra terna di punti magari il programma funziona senza errori. È un errore logico, gli errori logici sono i più difficili da individuare e da correggere, perché si manifestano solo in particolari condizioni non sempre facili da riprodurre. Nel nostro caso la retta passante per p0 e p2 è parallela all’asse y e non si può rappresentare con un’equazione esplicita, non è possibile calcolare il suo coefficiente angolare. Prima di eseguire la divisione conviene controllare che le ascisse dei due punti, xa e xb non siano uguali. Modifichiamo quindi la funzione retta2p. Dopo aver spacchettato le coordinate di a e b, controlliamo se xa è uguale a xb. In questo caso la coppia restituita conterrà come primo elemento, al posto del numero che rappresenta il coefficiente angolare, l’oggetto “None”. Se invece xa e xb sono diversi, procede esattamente come prima: def retta2p(a, b):

“”“Restituisce m e q della retta passante per a e b.”“” xa, ya = a xb, yb = b if xa==xb:

return None, xa
else:
m=float(yb-ya)/(xb-xa) q=ya-m*xa return m, q
Ora dobbiamo modificare anche i metodi della classe Mioplot in modo che tengano conto della possibilità che il coefficiente angolare invece di essere un numero sia l’oggetto None. In questo caso il metodo retta restituirà il valore dell’ascissa di un punto qualunque della retta, la parte destra dell’equazione: “x=xa”, cioè il secondo elemento della coppia rappresentante la retta:
def retta(self, r):
“”“Restituisce la stringa che contiene la funzione
di una retta.”“”

m, q = r if m==None:

return “%s” % q
else:
return “%s*x+%s” % r
Il metodo che disegna la retta deve chiamare il metodo xy() nel caso di una funzione y=f(x) e il metodo yx() nel caso di una funzione x=f(y), quindi anche questo metodo deve controllare il valore del primo elemento della “retta” e chiamare il “tracciatore di funzioni” adatto:
def drawline(self, r):
“”“Disegna una retta dati
(coeff.angolare, termine noto).”“”

m, q = r if m==None:

self.yx(self.retta(r))
else:
self.xy(self.retta(r))

Finalmente il problema di partenza è completamente risolto, non solo, abbiamo anche esteso le capacità dell’oggetto Plot presente nella libreria Pygraph. Forse si potrebbe scrivere all’autore della libreria per invitarlo ad aggiungere questa nuova funzionalità al modulo pyplot.py ;)

Riassumendo Con una coppia di numeri possiamo rappresentare quasi tutte le rette del piano cartesiano. Utilizzando anche un valore non numerico per il primo elemento della coppia, possono essere rappresentate tutte le rette. È possibile modificare le classi già definite nelle librerie estendendone le funzionalità. Ogni metodo di una classe ha come primo parametro un riferimento all’oggetto stesso normalmente chiamato “self”. Abbiamo derivato dalla classe Plot la classe Mioplot che aggiunge due metodi: retta() e drawline(). Abbiamo costruito la funzione che date due coppie, le coordinate di due punti, restituisce una coppia, la retta passante per i due punti.