Creare classi e attributi

Questa introduzione andrebbe letta da chi conosce come si usano e si scrivono le funzioni, ma non conosce come si usano gli oggetti e le classi.

Anche gli oggetti, come le funzioni, sono pezzi di programma che possono essere riutilizzati, ma a differenza delle tradizionali funzioni, questi trattano i dati in modo più “responsabile”. Immaginando che il programma sia un insegnante che interroga gli studenti, e che come prima cosa debba conoscere il nome dello studente:

  • Un insegnante orientato alle funzioni, prenderebbe in mano il documento dello studente, per poter leggere il nome dal suo documento.
  • Un insegnante orientato agli oggetti, chiederebbe gentilmente allo studente di fornirgli il proprio nome.

Il primo insegnante, rispetto al secondo, agisce utilizzando un eccesso di forza e si comporta in modo sgarbato. Questo è il modo con cui si comporta una tradizionale funzione con tutti i dati. Infatti il programmatore può creare una funzione così forte che può modificare i parametri che gli vengono passati, questo perché non esiste nessun controllo che limita la volontà del programmatore. Questo permette di scrivere rapidamente le funzioni nel programma, ma rende difficile scoprire eventuali errori presenti nel codice di una funzione, perché non esiste una sola funzione responsabile delle modifiche fatte su un certo dato.

Il secondo insegnante pensa che gli studenti abbiano diritto alla loro privacy e che inoltre sia più sicuro chiedere il nome invece che sperare di trovare, chissà in quale tasca, un documento. Anche il programmatore, usando gli oggetti, assegna a loro delle responsabilità, e se c'è un errore nel funzionamento del programma, sa immediatamente dove trovare il responsabile. Perché, di solito, per ogni dato esiste un solo responsabile.

Ci sono anche altri vantaggi nell'uso della programmazione orientata agli oggetti, come la facilità della manutenzione, dello sviluppo e del riutilizzo del codice, e altri che saranno compresi attraverso altri esempi. Per alcuni tipi di problemi la programmazione orientata agli oggetti invece non è la soluzione ideale…. Di solito l'approccio migliore per descrivere la soluzione di un problema dipende anche dal problema… Un po' come quando alcuni problemi si risolvono meglio usando la ricorsione e altri usando l'iterazione.

Gli oggetti sono strumenti di lavoro

Gli oggetti sono strumenti che realizzano o forniscono qualcosa sui dati. In questo modo si evita di occuparsi direttamente dei dati e si lascia che sia l'oggetto ad occuparsene.

Gli oggetti hanno una privacy

Gli oggetti sono responsabili dei propri dati, ma pretendono riservatezza. (vedere data_hiding) Non si deve pretendere di sapere come un oggetto esegue il proprio lavoro, ma chiedere solo che produca il risultato aspettato. Non c'è bisogno di capire qualcosa quando non siamo noi ad occuparcene.

Gli ogetti hanno una propria individualità, ma possono avere molti soprannomi…

La terminologia in Python è leggermente diversa da quella usata in Java o in C++

  • classe = una categoria, un modello, un tipo di dato usato per creare oggetti. Una classe crea un namespace che contiene dati e funzioni. In Python, al termine della def di una classe, viene crato un wrapper (un astratto “oggetto-classe”) che descrive il contenuto di tale classe.
    • l'oggetto-classe viene indicato col nome della classe (maiuscolo) e dà accesso agli attributi di classe e ai metodi di classe. Si usa sempre la notazione con il punto.
      • un attributo di classe è un dato appartenente all'oggetto-classe.
      • un metodo di classe è un oggetto-funzione appartenente all'oggetto-classe.
  • istanza = i comuni oggetti sono un'instanza di una classe, e si ottengono chiamando il nome della classe: ogg=NomeClasse(). Tale funzione NomeClasse() non esiste, ma chiama a sua volta la funzione di inizializzazione __init__().
  • attributi (dati e metodi) = tutto quello che viene dopo un punto (anche una funzione math.…).
    • attributo dato = sono le variabili di un particolare oggetto istanza. Vivono nel namespace dell'oggetto istanza. Poiché in Python le variabili non si dichiarano in anticipo, nemmeno questi vengono dichiarati nella def della classe. Essi vengono dichiarati/inizializzati all'interno del costruttore __init__().
    • attributo metodo = sono le funzioni di un particolare oggetto istanza. Vivono nel namespace dell'oggetto istanza. Vengono definite dentro il namespace della classe e il loro primo attributo deve essere sempre self, il riferimento all'oggetto su cui sono state chiamate. Per la stessa ragione, quando in un metodo si chiama un altro medoto o si usa un attributo dato, si deve aggiungere il prefisso “self.attributo”. Si può chiamare oggetto.metodo(), o equivalentemente, Classe.metodo(oggetto).
    • in Python è possibile rinominare i metodi (detti attributi metodi) perciò nella classe bisogna evitare conflitti di nome tra dati e funzioni. Si possono usare sostantivi per i primi e verbi per i secondi…

Nota: self non è una keyword del linguaggio Python, ma il suo uso è una vecchia consuetudine e cambiarla sarebbe poco utile…

TO DO: Come si divide il codice in più file.py?

La keyword global si usa per definire oggetti globali (meglio usarla solo per le funzioni)

Come si possono definire nuovi namespace? Procedendo dal livello di accessibilità più esterno al più interno, possiamo definire un nuovo namespace usando un modulo, poi usando una classe e, infine, usando una funzione.

Come si possono creare nuovi oggetti locali? Instanziando nuovi oggetti oppure usando import.

Come si possono distruggere oggetti locali? Usando del.

Diversamente da altri linguaggi, come il C++, in Python non esistono specificatori di accesso (public,private,…) e tutti gli oggetti sono public. Si può suggerire al programmatore di evitare l'accesso diretto ad alcuni attributi (dati e metodi) usando una convenzione sul nome: aggiungendo un prefisso di dun underscore _ .

Ricordate la suddivisione tra programmatori e sviluppatori? In questo momento dobbiamo immaginare di essere diventati degli sviluppatori che costruiscono classi e metodi perchè siano usate da altri programmatori!

Esempio 1

Siamo degli sviluppatori e dobbiamo preparare le librerie che verranno usate dal programmatore in un programma che conterrà il catalogo di un negozio di auto usate. Si deve descrivere ogni caratteristica dei veicoli: targa, kilometraggio, prezzo,… Queste descrizioni sono gli attributi dato che sono contenuti dentro oggetti nella memoria del computer. Tali oggetti sono creati tutti a partire dallo stesso modello: la classe Auto.

Questa classe servirà al futuro programmatore per creare, ogni volta che serve, un oggetto auto. Il programmatore, per creare il primo oggetto può scrivere semplicemente:

auto1 = Auto()

Ora nella memoria del computer c'è una auto, ma è strana perché non si conosce nulla di quest'auto, come la targa, il prezzo, ecc. Come può il futuro programmatore memorizzare la targa, il prezzo e i restanti attributi di questa auto? Li può specificare tra parentesi in questo modo:

auto1 = Auto("AZ140WW", 100000, 9000000.00)

Ciò deve essere previsto dallo sviluppatore, realizzando quest'apposita funzione di inizializzazione dell'oggetto

auto.py
class Auto:
"""Questa classe definisce le caratteristiche di un auto in vendita"""
    def __init__(self, targa, km, prezzo):
        self.targa = targa
        self.km = km
        self.prezzo = prezzo

__init__() è un attributo metodo (una funzione) che descrive gli attributi dato e la loro inizializzazione. È uno degli attributi metodo più importanti che lo sviluppatore deve preparare. Il programmatore lo usa (senza saperlo) ogni volta che crea un oggetto. Notare che mentre la chiamata del metodo contiene 3 parametri, nella funzione __init__() ce ne sono 4… Notare anche l'ordine dei parametri tra parentesi.

Esempio 2 da terminare

Se vogliamo scrivere un programma per una banca che possa gestire i dati anagrafici dei clienti e i dati dei loro conti correnti, ci vorranno come minimo due classi: Cliente e ContoCorrente. Queste due classi serviranno al programmatore per creare, ogni volta che serve, un oggetto cliente e un oggetto contocorrente. Dentro questi oggetti sarà possibile trovare i dati come nome e cognome del cliente e numero e saldo del conto corrente. Per ora trascuriamo il problema di conoscere a chi è intestato il conto…

Questi oggetti possono essere considerate delle strutture dove memorizzare i dati.

Il problema successivo è il seguente: come operare sui dati? Si può agire in due modi:

  • lasciare operare il programmatore direttamente sui dati per mezzo di istruzioni di assegnazione, come conto.saldo += 40.00
  • oppure, creare dei metodi che operino in sicurezza sui dati, tutte le volte che serve al programmatore, come conto.versamento(40.00)

La programmazione orientata agli oggetti di solito persegue la seconda strategia, e consiglia di nascondere i dati al programmatore (data hiding) per evitare che vi acceda direttamente (combinando qualche guaio). In Python non è possibile modificare la visibilità degli attributi degli oggetti, si può solo agire sul loro nome, aggiungendo due underscore _ .

Quindi nella classe devo avere:

  • attributi dato (ma non si dichiarano)
  • attributi metodo: def dell'inizializzatore __init()__ e di altri metodi
class ContoCorrente:
    __numero_conto=int(0)
    __saldo=float(0)
    def __init__(self):
        __numero_conto=int(0)
        __saldo=float(0)
    def versamento(self, importo):
        self.__saldo+=float(importo)
        return __saldo
bind01.py
#!/usr/bin/python
 
class ContoCorrente:
"""questa classe e' solo un esempio didattico"""
# gli attributi vanno cercati, perche' si trovano sparsi dentro i metodi
# ad esempio , due attributi sono i due pulsanti
 
    def __init__(self, master):
 
        frame = Frame(master, bg='white', width=200, height=200) 
                          # container bianco # e' una var. locale di una funzione 
                          # non viene distrutta solo grazie ad un sistema di Tkinter 
                          # che la mantiene in vita...
        frame.bind("<Button-1>", self.say_click) # evento del click
        frame.pack() # deve essere l'ultima cosa da fare su frame?
 
    def say_click(self, event):       # metodo di app
        print "clicked at", event.x, event.y     
 
# programma 
 
root = Tk()                # crea la finestra genitore
root.geometry("250x250+300+300")
 
app = App(root) # crea altri elementi della finestra genitore
 
root.mainloop() # loop degli eventi eseguito fino alla chiusura

Esempio 3 da terminare

import tkFileDialog
#creare prima una finestra e usare questo metodo per aprire una finestra di dialogo
percorso = tkFileDialog.askopenfilename

Applicazione con event

bind01.py
#!/usr/bin/python
from Tkinter import *
 
# definire una classe App (di solito una sottoclasse)
# permette di personalizzare la finestra
 
class App:
"""questa classe e' solo un esempio didattico"""
# gli attributi vanno cercati, perche' si trovano sparsi dentro i metodi
# ad esempio , due attributi sono i due pulsanti
 
    def __init__(self, master):
 
        frame = Frame(master, bg='white', width=200, height=200) 
                          # attributo dato: un container bianco 
 
        frame.bind("<Button-1>", self.say_click) # evento del click
        frame.pack() # deve essere l'ultima cosa da fare su frame?
 
    def say_click(self, event):       # metodo di app
        print "clicked at", event.x, event.y     
 
# programma 
 
root = Tk()                # crea la finestra genitore
root.geometry("250x250+300+300")
 
app = App(root) # crea altri elementi della finestra genitore
 
root.mainloop() # loop degli eventi eseguito fino alla chiusura

Se inserisco due pulsanti nel frame, il frame si stringe e diventa invisibile…

Applicazione a finestra con costruttore personalizzato. Tk ha il metodo destroy()?? Frame ha il metodo quit()??

hello2.py
#!/usr/bin/python
from Tkinter import *
 
# definire una classe App (di solito una sottoclasse)
# permette di personalizzare la finestra
 
class App:
"""questa classe e' solo un esempio didattico"""
# gli attributi vanno cercati, perche' si trovano sparsi dentro i metodi
# ad esempio , due attributi sono i due pulsanti
 
    def __init__(self, master):
 
        frame = Frame(master, bg='white', width=200, height=200) 
                          # container ora invisibile per posizionare pulsanti
                          # e' una var. locale di una funzione (come button) 
                          # non viene distrutta solo grazie ad un sistema di Tkinter 
                          # che la mantiene in vita...
 
        frame.bind("<Button-1>", self.say_click) # evento del click
        frame.pack() # deve essere l'ultima cosa da fare su frame...
 
        self.buttonQuit = Button(frame, text="Quit", fg="red", command=frame.quit)
        self.buttonQuit.pack(side=LEFT)
        self.buttonHello = Button(frame, text="Hello", command=self.say_hello)
        self.buttonHello.pack(side=LEFT)
 
    def say_hello(self):       # metodo di app
        print "hello in the terminal!"
 
# programma 
 
root = Tk()                # crea la finestra genitore
root.geometry("250x250+300+300")
 
app = App(root) # crea altri elementi della finestra genitore
 
root.mainloop() # loop degli eventi eseguito fino alla chiusura
  • appunti3s/creare_classi_e_funzioni_in_python.txt
  • Last modified: 2018/10/18 21:55
  • by profpro