Bentornati alla quarta lezione del nostro Python di Scratch serie. Questo tutorial assumerà alcune conoscenze pregresse su variabili, tipi di dati, funzioni e output di stampa. Se non sei aggiornato, dai un'occhiata ai tre precedenti articoli della serie.
Oggi approfondiremo l'argomento della programmazione orientata agli oggetti (OOP). OOP è un modo molto potente di organizzare il tuo codice e una solida comprensione dei concetti alla base può davvero aiutarti a ottenere il massimo dalla tua codifica.
Python è progettato principalmente come linguaggio di programmazione orientato agli oggetti, ma cosa significa in realtà "object oriented"?
Ci sono una varietà di definizioni per il termine, e potresti parlare letteralmente per ore cercando di spiegare i complicati dettagli, sfumature e differenze nelle implementazioni, ma cercherò di dare una rapida panoramica.
In generale, la programmazione orientata agli oggetti è il concetto che, nella programmazione, gli oggetti che stiamo manipolando sono più importanti della logica necessaria per manipolare quegli oggetti. Tradizionalmente, un programma è stato visto come una ricetta: un insieme di istruzioni che segui dall'inizio alla fine per completare un'attività. Questo può essere ancora vero, e per molti programmi semplici, è tutto ciò che è richiesto. Tale approccio è talvolta noto come programmazione procedurale.
OOP mette gli oggetti al centro del processo.
D'altra parte, man mano che i programmi diventano sempre più complessi e complicati, la logica necessaria per scriverli in modo puramente procedurale diventa sempre più distorta e difficile da comprendere. Spesso gli approcci orientati agli oggetti possono aiutare in questo.
Quando parliamo di approcci orientati agli oggetti, ciò che facciamo è mettere gli oggetti al centro del processo, invece di usarli semplicemente come contenitori necessari per le informazioni come parte delle nostre istruzioni procedurali. Per prima cosa, definiamo gli oggetti che vogliamo manipolare e in che modo si relazionano tra loro, e poi iniziamo ad approfondire la logica per far funzionare il programma.
Quando parlo di "oggetti", posso parlare di ogni sorta di cose. Un 'oggetto' può rappresentare una persona (come definito da proprietà come nome, età, indirizzo ecc.) O un'azienda (definita da cose come numero di dipendenti e così via), o anche qualcosa di molto più astratto, come un pulsante nell'interfaccia di un computer.
In questa introduzione, non copriremo tutti i concetti in questo argomento perché saremmo qui tutta la notte, ma entro la fine del tutorial, spero che tu abbia una solida comprensione dei principi di cui hai bisogno per iniziare subito usando alcune semplici tecniche orientate agli oggetti nei tuoi programmi Python. Ancora meglio, questi concetti sono abbastanza simili in molti ambienti di programmazione. La conoscenza si trasferisce dalla lingua alla lingua abbastanza bene.
Ho detto prima che la prima cosa che dovremmo fare quando stiamo andando per un approccio OOP è definire gli oggetti che useremo. Il modo in cui lo facciamo è definire innanzitutto le proprietà che possiede usando una classe. Puoi pensare a una classe come a una specie di modello; una guida per il modo in cui un oggetto dovrebbe essere strutturato. Ogni oggetto appartiene a una classe e eredita le proprietà di quella classe, ma agisce individualmente sugli altri oggetti di quella classe.
Qualche volta un oggetto viene definito "istanza" di una classe.
Come semplice esempio, potresti avere una classe chiamata 'persona' con, diciamo, un'età e una proprietà nome, e un'istanza di quella classe (un oggetto) sarebbe una singola persona. Quella persona potrebbe avere un nome di? Andy? e un'età di 23 anni, ma potresti avere contemporaneamente un'altra persona appartenente alla stessa classe con il nome di? Lucy? e un'età di 18 anni.
È difficile capirlo senza vederlo in pratica, quindi diamo un po 'di codice.
Per definire una classe, nel tipico modo semplice di Python, usiamo la parola 'classe', seguita dal nome della tua nuova classe. Ho intenzione di fare una nuova classe qui, chiamata 'pet'. Usiamo i due punti dopo il nome e quindi tutto ciò che è contenuto nella definizione della classe è rientrato. Tuttavia, con una classe, non ci sono parentesi:
animale domestico di classe:
Quindi ora abbiamo una lezione, ma è piuttosto inutile senza nulla in essa. Per iniziare, diamo un paio di proprietà. Per fare questo, devi semplicemente definire alcune variabili all'interno della classe - ho intenzione di andare con il numero di gambe per iniziare. Come al solito, dovresti sempre nominare le tue variabili in modo che sia facile dire quali sono. Siamo originali e chiamiamolo 'number_of_legs'. Dobbiamo definire un valore o otterremo un errore. Userò qui 0 (non importa troppo in questo caso poiché il numero di gambe sarà specifico per ogni istanza della classe - un pesce non ha la stessa quantità di zampe di un cane o di un'anatra, ecc. - quindi dovremo cambiare quel valore per ogni oggetto in ogni caso).
animale domestico di classe: number_of_legs = 0
Una classe a sé stante non è qualcosa che puoi manipolare direttamente; per prima cosa, dobbiamo creare un'istanza della classe con cui giocare. Possiamo memorizzare quell'istanza in una variabile. Al di fuori della classe (senza indentazione), creiamo un'istanza della classe e la memorizziamo nella variabile "doug". Per creare una nuova istanza di una classe, devi semplicemente digitare il nome della classe e quindi una coppia di parentesi. A questo punto, non c'è bisogno di preoccuparsi delle parentesi, ma in seguito vedrai che sono lì perché, come una funzione, c'è un modo per passare una variabile per essere usata dalla classe quando si crea l'istanza per la prima volta.
Una classe a sé stante non è qualcosa che puoi manipolare direttamente.
animale domestico di classe: number_of_legs = 0 doug = pet ()
Ora che abbiamo un'istanza di una classe, come possiamo accedere e manipolare le sue proprietà? Per fare riferimento a una proprietà di un oggetto, prima dobbiamo dire a Python quale oggetto (o quale istanza di una classe) di cui stiamo parlando, quindi inizieremo con "doug". Quindi, scriveremo un punto per indicare che stiamo facendo riferimento a qualcosa che è contenuto nella nostra istanza di doug. Dopo il periodo, aggiungiamo il nome della nostra variabile. Se stiamo accedendo al number_of_legs
variabile, assomiglierà a questo:
doug.number_of_legs
Possiamo trattare questo ora esattamente come tratteremmo qualsiasi altra variabile - qui assumerò che doug è un cane e assegnerò a tale variabile il valore di 4.
Per accedere a questa variabile, la useremo di nuovo esattamente come tratteremmo qualsiasi altra variabile, ma usando quella doug.number_of_legs
proprietà invece del nome della variabile normale. Mettiamo una riga per stampare quante gambe ha Doug in modo che possiamo dimostrare che funziona come dovrebbe:
classe pet: number_of_legs = 0 doug = pet () doug.number_of_legs = 4 stampa "Doug ha% s zampe". % doug.number_of_legs
Se esegui il codice sopra, vedrai che è stampato per noi. Definiva la nostra classe 'pet', creava una nuova istanza di quella classe e la memorizzava nella variabile 'doug', e quindi, all'interno di quell'istanza, veniva assegnato il valore di 4 alla number_of_legs
variabile che ha ereditato dalla sua classe.
Potete quindi vedere da questo esempio molto semplificato come iniziare a costruire strutture dati modulari e piacevoli che siano chiare e facili da usare e che possano iniziare a scalare in modo abbastanza graduale.
Ok, ecco le basi delle classi e degli oggetti, ma al momento possiamo solo usare le classi come strutture dati o contenitori per variabili. Va tutto bene, ma se vogliamo iniziare a svolgere compiti più complessi con i dati che stiamo manipolando, abbiamo bisogno di un modo per introdurre qualche logica in questi oggetti. Il modo in cui lo facciamo è con i metodi.
I metodi, essenzialmente, sono funzioni contenute all'interno di una classe. Ne definisci uno esattamente nello stesso modo in cui svolgeresti una funzione, ma la differenza è che lo inserisci in una classe e appartiene a quella classe. Se vuoi chiamare questo metodo, devi prima fare riferimento a un oggetto di quella classe, proprio come le variabili che stavamo guardando in precedenza.
I metodi, essenzialmente, sono funzioni contenute all'interno di una classe.
Ho intenzione di scrivere un breve esempio qui nella nostra classe di animali domestici per dimostrare; creiamo un metodo, chiamato 'sleep', che sta per stampare un messaggio quando viene chiamato per la prima volta. Proprio come una funzione, inserirò 'def' per 'define', e poi scriverò il nome del metodo che voglio creare. Quindi inseriremo le nostre parentesi e il punto e virgola e quindi avvieremo una nuova riga. Come al solito, tutto ciò che è incluso in questo metodo sarà indentato ad un livello extra.
Ora, c'è un'altra differenza tra un metodo e una funzione: un metodo sempre, sempre, deve sempre avere un argomento, chiamato 'sé' tra le parentesi. Quando Python chiama un metodo, ciò che fa è passare l'oggetto corrente a quel metodo come primo argomento. In altre parole, quando chiamiamo doug.sleep ()
, Python in realtà passerà l'oggetto "doug" come argomento al metodo sleep.
Vedremo perché è più tardi, ma per ora devi sapere che, con un metodo, devi sempre includere un argomento chiamato 'self' prima nell'elenco (se vuoi aggiungere più argomenti, puoi aggiungerli in seguito, esattamente come se si passassero più argomenti a una funzione). Se non includi questo argomento, quando esegui il codice, otterrai un errore generato perché Python sta passando un argomento (questo oggetto 'self'), e il metodo sta dicendo, 'Ehi, amico, Non prendo argomenti, di cosa stai parlando? '. È come se si tentasse di passare un argomento in una funzione che non accetta argomenti.
Quindi ecco quello che abbiamo finora:
classe pet: number_of_legs = 0 def sleep (self): doug = pet ()
All'interno di questo metodo, scriveremo una dichiarazione di stampa in questo modo:
classe pet: number_of_legs = 0 def sleep (self): stampa "zzz" doug = pet ()
Ora, se vogliamo usare questo metodo, usiamo semplicemente un'istanza della classe pet per fare riferimento a questo metodo. Proprio come il number_of_legs
variabile, scriviamo il nome dell'istanza (ne abbiamo uno chiamato doug), quindi un punto, quindi il nome del metodo includendo parentesi. Nota che stiamo chiamando sleep senza alcun argomento, ma Python aggiungerà questo argomento da solo, quindi finiremo con la giusta quantità di argomenti in totale.
classe pet: number_of_legs = 0 def sleep (self): stampa "zzz" doug = pet () doug.sleep ()
Se esegui questo codice, dovresti vedere che stampa il messaggio che abbiamo scritto.
Bene, quindi ora che ne dici di scrivere un nuovo metodo per stampare quante zampe ha l'animale domestico, per dimostrare come puoi usare i metodi per iniziare a manipolare i dati all'interno della classe e per dimostrare perché dobbiamo includere questo 'sé' confuso discussione. Facciamo un nuovo metodo, chiamato 'count_legs
'.
È qui che entra in gioco l'argomento "sé". Ricorda quando stavamo accedendo number_of_legs
dall'esterno della classe e abbiamo dovuto usare "doug.number_of_legs" invece di solo "number_of_legs"? Lo stesso principio si applica; se vogliamo sapere cosa è contenuto in quella variabile, dobbiamo fare riferimento a esso prima specificando l'istanza che contiene quella variabile.
Tuttavia, non sappiamo come verrà chiamata l'istanza quando scriviamo la classe, quindi ci aggiriamo usando la variabile 'self'. 'self' è solo un riferimento all'oggetto che viene attualmente manipolato. Quindi per accedere a una variabile nella classe corrente, devi semplicemente pre-impostarlo con 'self' e quindi un punto, in questo modo:
class pet: number_of_legs = 0 def sleep (self): stampa "zzz" def count_legs (self): stampa "I have% s legs"% self.number_of_legs doug = pet () doug.number_of_legs = 4 doug.count_legs ()
In pratica, ciò significa che ovunque tu scriva 'self' nel tuo metodo, quando esegui il metodo che self viene sostituito dal nome dell'oggetto, così quando chiamiamo 'doug.count_legs ()' il 'self' è sostituito da "doug". Per dimostrare come funziona con più istanze, aggiungiamo una seconda istanza, che rappresenta un altro animale domestico, chiamato "nemo":
class pet: number_of_legs = 0 def sleep (self): stampa "zzz" def count_legs (self): stampa "I have% s legs"% self.number_of_legs doug = pet () doug.number_of_legs = 4 doug.count_legs () nemo = pet () nemo.number_of_legs = 0 nemo.count_legs ()
Questo stamperà un messaggio per 4 e poi 0 gambe, proprio come volevamo, perché quando chiamiamo 'nemo.count_legs (),' il 'sé' è sostituito da 'nemo' invece di 'doug'.
In questo modo, il nostro metodo funzionerà esattamente come previsto perché il riferimento "auto" cambierà dinamicamente in base al contesto e ci consentirà di manipolare i dati solo all'interno dell'oggetto corrente.
Le cose principali che devi ricordare sui metodi è che sono esattamente come le funzioni, tranne che il primo argomento deve essere 'self' e che per fare riferimento a una variabile interna devi prefigurare il nome della variabile con 'self'.
Proprio come una nota: puoi effettivamente usare qualsiasi nome invece di "sé" per i tuoi metodi. -I metodi qui funzionano altrettanto bene se abbiamo rinominato la variabile 'self' in qualsiasi parola. Usare il nome 'self' è semplicemente una convenzione che è utile ai programmatori Python perché rende il codice molto più standard e facile da capire, anche se è scritto da qualcun altro. Il mio consiglio sarebbe quello di attenersi alle convenzioni.
Ora che abbiamo approfondito le nozioni di base, diamo un'occhiata ad alcune funzionalità più avanzate delle classi e a come possono semplificare la struttura della programmazione.
La prossima cosa di cui parleremo è l'ereditarietà. Come suggerisce il nome, l'ereditarietà è il processo di creazione di una nuova classe basata su una classe genitore e che consente alla nuova classe di ereditare le caratteristiche della classe genitore. La nuova classe può prendere tutti i metodi e le variabili dalla classe genitore (spesso chiamata la classe 'base').
L'ereditarietà è il processo di creazione di una nuova classe basata su una classe genitore.
Estendiamo il nostro esempio domestico per vedere come ciò potrebbe essere utile. Se usiamo 'pet' come nostra classe genitore, potremmo creare una classe figlia ereditata dalla classe pet. La classe bambino potrebbe essere qualcosa come "cane" o "pesce" - qualcosa che è ancora un "animale domestico", ma è più specifico di quello. Un cane è un animale domestico e fa le stesse cose che fanno tutti gli animali domestici - per esempio mangia e dorme e ha un'età e un numero di gambe - ma fa altre cose specifiche per essere un cane, o almeno più specifico piuttosto che essere un animale domestico: i cani hanno la pelliccia, ma non tutti gli animali domestici. Un cane potrebbe abbaiare o prendere un bastone, ma non tutti gli animali domestici lo farebbero.
Tornando al punto, diciamo che volevamo fare una lezione nel nostro programma per rappresentare un cane. Potremmo usare l'ereditarietà per ereditare i metodi e le variabili contenute negli "animali domestici" in modo che il nostro cane possa avere un "numero di zampe" e la capacità di "dormire", oltre a tutte le cose specifiche del cane che potremmo immagazzinare o fare.
Ora, ti starai chiedendo perché non mettiamo quei metodi e queste variabili nella classe dei cani e ti sbarazziamo completamente della classe degli animali domestici? Bene, l'ereditarietà ci dà due distinti vantaggi rispetto a questo approccio: uno, se vogliamo un oggetto che è un animale domestico, ma non è un cane - un animale domestico generico, se vuoi - possiamo ancora farlo. Due, forse più tardi, vogliamo aggiungere un secondo tipo di animale domestico, forse un pesce. Possiamo fare in modo che anche la seconda classe erediti dall'animale domestico, quindi entrambe le classi possono condividere tutto in pet, ma allo stesso tempo hanno i propri metodi e variabili più specifici che si applicano solo a quel tipo di oggetto.
Qui stiamo diventando un po 'impantanati, quindi scriviamo qualcosa per renderlo un po' più chiaro. In primo luogo, scriveremo una nuova classe, chiamata "dog", ma questa volta, tra il nome della classe e il colon, inseriremo alcune parentesi e, in esse, scriveremo il nome della classe da cui ereditiamo, come se stessimo passando a questa nuova classe un argomento, come se avessimo una funzione.
Quindi, diamo a questa classe un metodo semplice per dimostrare come funziona. Ho intenzione di aggiungere un 'abbaiare
'metodo che stamperà' woof ':
class pet: number_of_legs = 0 def sleep (self): stampa "zzz" def count_legs (self): stampa "I have% s legs"% self.number_of_legs class dog (animale domestico): def bark (self): stampa "Woof"
Quindi, ora vediamo cosa succede se creiamo un'istanza di questa classe. Chiamerò di nuovo il nostro nuovo cane "doug" doug.bark ()
:
class pet: number_of_legs = 0 def sleep (self): stampa "zzz" def count_legs (self): stampa "I have% s legs"% self.number_of_legs class dog (animale domestico): def bark (self): stampa "Woof" doug = dog () doug.bark ()
Come previsto, Doug abbaia. È fantastico, ma non abbiamo ancora visto nulla di nuovo - solo una classe con un metodo. Ciò che l'ereditarietà ha fatto per noi, però, è di rendere disponibili tutte le funzioni e le variabili dell'animale domestico attraverso il nostro oggetto "doug", quindi se faccio qualcosa del genere:
class pet: number_of_legs = 0 def sleep (self): stampa "zzz" def count_legs (self): stampa "I have% s legs"% self.number_of_legs class dog (animale domestico): def bark (self): stampa "Woof" doug = dog () doug.sleep ()
Quindi il metodo sleep verrà eseguito correttamente. In effetti, il nostro oggetto doug appartiene sia alla classe 'pet' che alla classe 'cane'. Per garantire che le variabili facciano lo stesso dei metodi, proviamo questo:
class pet: number_of_legs = 0 def sleep (self): stampa "zzz" def count_legs (self): stampa "I have% s legs"% self.number_of_legs class dog (animale domestico): def bark (self): stampa "Woof" doug = dog () doug.number_of_legs = 4 doug.count_legs ()
Potete vedere che il doug agisce esattamente come prima, dimostrando che le nostre variabili vengono ereditate. La nostra nuova classe figlio è semplicemente una versione specializzata di quella principale, con alcune funzionalità extra ma mantenendo tutte le funzionalità precedenti.
Quindi eccoci qui, una rapida introduzione alla programmazione orientata agli oggetti. Restate sintonizzati per la prossima puntata di questa serie, in cui lavoreremo con Python sul web!