Se hai programmato in stile funzionale, dichiarativo o imperativo, spostare l'attenzione sulla programmazione orientata agli oggetti (OOP) può sembrare un po' estraneo.
Un approccio OOP chiede a chi programma di pensare a modellare un problema come uno o più objects che interagiscono tra loro, mantengono uno stato e agiscono sui dati.
Gli oggetti possono rappresentare entità del mondo reale (come automobili o gatti), oppure concetti più astratti (come numeri interi, veicoli o mammiferi).
Ogni oggetto diventa un'istanza unica nella memoria del computer e rappresenta una parte del modello complessivo.
Le Classes sono le definizioni di nuovi tipi di oggetto, e da esse si creano nuove instances di oggetti.
Spesso raggruppano dati insieme a codice o a funzioni che operano su quei dati.
In questo senso, le classi sono progetti o insiemi di istruzioni da cui si possono costruire e usare molti oggetti di tipo simile.
Un programma complesso può avere molte classi, ognuna delle quali costruisce molti tipi diversi di oggetti.
Il processo di costruzione di un oggetto a partire da una classe è noto come instantiation (o creazione di un'istanza della classe).
La definizione di una classe in Python è semplice:
class MyClass:
# Class body goes here
I campi di una classe (altrimenti noti come properties, attributes, data members o variables) possono essere aggiunti al corpo della classe:
class MyClass:
number = 5
string = "Hello!"
Un'istanza (oggetto) di MyClass può essere creata e associata a un nome chiamando la classe (allo stesso modo in cui si chiama una funzione):
>>> new_object = MyClass()
# Class is instantiated and resulting object is bound to the "new_object" name.
# Note: the object address 'at 0x15adc55b0' will vary by computer.
>>> new_object
<__main__.MyClass at 0x15adc55b0>
Gli Class attributes sono condivisi tra tutti gli oggetti (o istanze) creati da una classe, e vi si può accedere tramite la dot notation: un . posto dopo il nome dell'oggetto e prima del nome dell'attributo:
>>> new_object = MyClass()
# Accessing the class attribute "number" via dot-notation.
>>> new_object.number
5
# Accessing the class attribute "string" via dot-notation.
>>> new_object.string
'Hello!'
# Instantiating an additional object and binding it to the "second_new_object" name.
>>> second_new_object = MyClass()
>>> second_new_object
# Note: the object address "at 0x15ad99970" will vary by computer.
<__main__.MyClass at 0x15ad99970>
# Second_new_object shares the same class attributes as new_object.
>>> new_object.number == second_new_object.number
True
Gli attributi di classe sono definiti nel corpo della classe stessa, prima di qualsiasi altro metodo. Appartengono alla classe: questo permette di condividerli tra le istanze della classe. Poiché questi attributi sono condivisi, il loro valore può essere letto e modificato direttamente dalla classe. Modificare il valore degli attributi di classe modifica il valore per tutti gli oggetti istanziati dalla classe:
>>> obj_one = MyClass()
>>> obj_two = MyClass()
# Accessing a class attribute from an object.
>>> obj_two.number
5
# Accessing the class attribute from the class itself.
>>> MyClass.number
5
# Modifying the value of the "number" class attribute.
>>> MyClass.number = 27
# Modifying the "number" class attribute changes the "number" attribute for all objects.
>>> obj_one.number
27
>>> obj_two.number
27
Avere un mucchio di oggetti con dati sincronizzati in ogni momento non è particolarmente utile.
Per fortuna, gli oggetti creati da una classe possono essere personalizzati con i propri instance attributes (o proprietà, variabili o campi di istanza).
Come suggerisce il nome, gli attributi di istanza sono unici per ogni oggetto e possono essere modificati in modo indipendente.
__init__()
Lo speciale «dunder method» (abbreviazione di «double underscore method») __init__() serve a personalizzare le istanze di una classe, e può essere usato per inizializzare gli attributi o le proprietà di istanza degli oggetti.
Per il suo ruolo nell'inizializzazione degli attributi di istanza, __init__() è anche chiamato class constructor o initializer.
__init__() accetta un parametro obbligatorio chiamato self, che si riferisce all'oggetto appena inizializzato o creato.
I dati per gli attributi o le proprietà di istanza possono poi essere passati come argomenti di __init__(), dopo il parametro self.
Qui sotto, MyClass ha ora attributi di istanza chiamati location_x e location_y.
Come puoi vedere, i due attributi sono stati assegnati al primo e al secondo indice dell'argomento location (una tupla) passato a __init__().
Gli attributi location_x e location_y di un'istanza della classe verranno ora inizializzati quando istanzi la classe e viene creato un oggetto:
class MyClass:
# These are class attributes, variables, or fields.
number = 5
string = "Hello!"
# This is the class "constructor", with a "location" parameter that is a tuple.
def __init__(self, location):
# This is an instance or object property, attribute, or variable.
# Note that we are unpacking the tuple argument into two separate instance variables.
self.location_x = location[0]
self.location_y = location[1]
# Create a new object "new_object_one", with object property (1, 2).
>>> new_object_one = MyClass((1, 2))
# Create a second new object "new_object_two" with object property (-8, -9).
>>> new_object_two = MyClass((-8, -9))
# Note that new_object_one.location_x and new_object_two.location_x two different values.
>>> new_object_one.location_x
1
>>> new_object_two.location_x
-8
Tieni presente che devi passare un solo argomento quando inizializzi MyClass qui sopra: Python si occupa di passare self quando la classe viene chiamata.
Un method è una function legata alla classe stessa (noto come metodo di classe, di cui parleremo in un esercizio successivo) oppure a un'instance della classe (oggetto).
I metodi che operano su un oggetto (istanza) devono essere definiti con self come primo parametro.
Puoi successivamente definire il resto dei parametri come faresti per una funzione «normale» o non legata:
class MyClass:
number = 5
string = "Hello!"
# Class constructor.
def __init__(self, location):
# Instance properties
self.location_x = location[0]
self.location_y = location[1]
# Instance method. Note "self" as first parameter.
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
Come per l'accesso agli attributi, chiamare un metodo richiede semplicemente di mettere un . dopo il nome dell'oggetto e prima del nome del metodo.
Il metodo chiamato non ha bisogno di una copia dell'oggetto come primo parametro: Python inserisce self automaticamente:
class MyClass:
number = 5
string = "Hello!"
def __init__(self, location):
self.location_x = location[0]
self.location_y = location[1]
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
# Make a new test_object with location (3,7)
>>> test_object = MyClass((3,7))
>>> (test_object.location_x, test_object.location_y)
(3,7)
# Call change_location to increase location_x and location_y by 7.
>>> test_object.change_location(7)
(10, 14)
Si può accedere agli attributi di classe dall'interno dei metodi di istanza allo stesso modo in cui vi si accede dall'esterno della classe:
class MyClass:
number = 5
string = "Hello!"
def __init__(self, location):
self.location_x = location[0]
self.location_y = location[1]
# Alter instance variable location_x and location_y
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
# Alter class variable number for all instances from within an instance.
def increment_number(self):
# Increment the 'number' class variable by 1.
MyClass.number += 1
>>> test_object_one = MyClass((0,0))
>>> test_object_one.number
5
>>> test_object_two = MyClass((13, -3))
>>> test_object_two.increment_number()
>>> test_object_one.number
6
Negli esercizi sui concetti e negli stub degli esercizi di pratica precedenti, avrai visto la parola chiave pass usata all'interno del corpo delle funzioni al posto del codice vero e proprio.
La parola chiave pass è un segnaposto sintatticamente valido: impedisce a Python di generare un errore di sintassi per una funzione o una definizione di classe vuota.
In sostanza, è un modo per dire all'interprete Python: «Non preoccuparti! Prima o poi metterò qui del codice, semplicemente non l'ho ancora fatto».
class MyClass:
number = 5
string = "Hello!"
def __init__(self, location):
self.location_x = location[0]
self.location_y = location[1]
# Alter instance variable location_x and location_y
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
# Alter class variable number for all instances
def increment_number(self):
# Increment the 'number' class variable by 1.
MyClass.number += 1
# This will compile and run without error, but has no current functionality.
def pending_functionality(self):
# Stubbing or placholding the body of this method.
pass
Ellen sta creando un gioco in cui il giocatore deve combattere contro gli alieni.
Ha appena imparato la programmazione orientata agli oggetti (OOP) e non vede l'ora di sfruttare quello che l'uso delle classes potrebbe offrire al suo programma.
Con grande gioia di Ellen, ti sei offerto di aiutarla e lei ti ha affidato il compito di programmare gli alieni che il giocatore deve combattere.
Definisci la classe Alien con un costruttore che accetta due parametri <x_coordinate> e <y_coordinate>, inserendoli nelle variabili di istanza x_coordinate e y_coordinate.
Ogni alieno partirà anche con un livello di salute pari a 3, quindi va inizializzata anche la variabile health.
>>> alien = Alien(2, 0)
>>> alien.x_coordinate
2
>>> alien.y_coordinate
0
>>> alien.health
3
Ora ogni alieno dovrebbe essere in grado di tenere traccia internamente della propria posizione e della propria salute.
hit
Ellen vorrebbe che la class Alien avesse un metodo hit che, quando viene chiamato, decrementa di 1 la salute di un oggetto alieno.
Così può semplicemente chiamare <alien>.hit() invece di dover modificare manualmente la salute di un alieno.
Sta a te decidere se hit() porta i punti salute a o sotto lo zero.
>>> alien = Alien(0, 0)
# Initialized health value.
>>> alien.health
3
# Decrements health by 1 point.
>>> alien.hit()
>>> alien.health
2
is_alive
Ti rendi conto che, se la salute continua a diminuire, a un certo punto arriverà probabilmente a 0 (o anche meno!).
Sarebbe una buona idea aggiungere un metodo is_alive che Ellen possa chiamare rapidamente per controllare se l'alieno è... beh... vivo. 😉
<alien>.is_alive() dovrebbe restituire un valore booleano.
>>> alien.health
1
>>> alien.is_alive()
True
>>> alien.hit()
>>> alien.health
0
>>> alien.is_alive()
False
teleport
Nel gioco di Ellen, gli alieni hanno la capacità di teletrasportarsi!
Dovrai scrivere un metodo teleport che accetta nuovi valori x_coordinate e y_coordinate e cambia di conseguenza le coordinate dell'alieno.
>>> alien.teleport(5, -4)
>>> alien.x_coordinate
5
>>> alien.y_coordinate
-4
collision_detection
Ovviamente, se gli alieni possono essere colpiti da qualcosa, allora devono essere in grado di rilevare quando si verifica una collisione del genere.
Tuttavia, gli algoritmi di rilevamento delle collisioni possono essere complicati e non sai ancora come implementarne uno.
Ellen ha detto che lo farà più tardi, ma vorrebbe comunque che il metodo collision_detection comparisse nella classe come promemoria per sviluppare la funzionalità.
Dovrà accettare una variabile di qualche tipo (probabilmente un altro oggetto), ma questo è davvero tutto quello che sai.
Dovrai assicurarti che inserire la definizione del metodo nella classe non causi errori quando viene chiamato:
>>> alien.collision_detection(other_object)
>>>
Ellen è tornata con una nuova richiesta per te. Vuole tenere traccia di quanti alieni sono stati creati durante l'intera partita. Dice che ha a che fare con il sistema di punteggio.
Per esempio:
>>> alien_one = Alien(5, 1)
>>> alien_one.total_aliens_created
1
>>> alien_two = Alien(3, 0)
>>> alien_two.total_aliens_created
2
>>> alien_one.total_aliens_created
2
# Accessing the variable from the class directly
>>> Alien.total_aliens_created
2
Ellen adora quello che hai fatto finora, ma ha ancora un favore da chiederti.
Vorrebbe una funzione a sé stante (esterna alla classe Alien()) che crei un list di oggetti Alien(), dato un array di posizioni (come tuples).
Per esempio:
>>> alien_start_positions = [(4, 7), (-1, 0)]
>>> aliens = new_aliens_collection(alien_start_positions)
...
>>> for alien in aliens:
... print(alien.x_coordinate, alien.y_coordinate)
(4, 7)
(-1, 0)
Iscriviti a Exercism per imparare e padroneggiare Python con 17 concetti146 esercizi e il mentoring di persone reali, tutto gratis.