2.21. Metodi e attributi¶
Un metodo è una funzione definita all’interno di una classe. Il primo parametro è l’istanza su cui il metodo viene chiamato; per convenzione lo si chiama self.
2.21.1. Metodi di istanza¶
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def magnitude(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
p = Point(3, 4)
print(p.magnitude())
Output:
5.0
La chiamata p.magnitude() passa automaticamente p come self. I corpi dei metodi leggono e aggiornano gli attributi tramite self allo stesso modo in cui le funzioni leggono e aggiornano i nomi tramite i parametri.
2.21.2. Attributi di istanza vs di classe¶
Gli attributi assegnati a self – tipicamente all’interno di __init__ – appartengono a quella singola istanza:
a = Point(1, 2)
b = Point(10, 20)
a.x = 99
print(a.x, b.x) # 99 10
a.x e b.x sono archiviazioni diverse; modificare uno non influisce sull’altro.
I nomi assegnati nel corpo della classe, al di fuori di qualsiasi metodo, sono condivisi da ogni istanza della classe:
class Counter:
kind = "tally" # class attribute -- shared
def __init__(self):
self.value = 0 # instance attribute -- per instance
a = Counter()
b = Counter()
print(a.kind, b.kind) # tally tally
Gli attributi di classe risiedono sulla classe stessa e sono condivisi. Gli attributi di istanza risiedono su ciascuna istanza.¶
Un attributo di classe si raggiunge nello stesso modo di uno di istanza (a.kind); Python cerca prima sull’istanza, poi sulla classe. Assegnare a a.kind creerebbe un nuovo attributo di istanza che oscura quello di classe, lasciando b.kind intatto.
Usa gli attributi di classe per costanti e valori predefiniti che ogni istanza dovrebbe vedere allo stesso modo. Usa gli attributi di istanza per lo stato di cui ogni oggetto dovrebbe possedere la propria copia.
2.21.3. __str__ e __repr__¶
Due metodi speciali controllano come un’istanza viene stampata. __str__ restituisce la stringa «amichevole» usata da print(); __repr__ restituisce la stringa «per sviluppatori» usata al REPL e all’interno delle visualizzazioni dei contenitori:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return "(" + str(self.x) + ", " + str(self.y) + ")"
def __repr__(self):
return "Point(" + str(self.x) + ", " + str(self.y) + ")"
p = Point(3, 4)
print(p) # uses __str__
print([p, p]) # uses __repr__ inside the list
Output:
(3, 4)
[Point(3, 4), Point(3, 4)]
Se è definito solo __repr__, print() ricade su di esso – quindi un singolo __repr__ ben scritto è di solito sufficiente. Definisci anche __str__ solo quando la forma amichevole dovrebbe apparire diversa da quella per sviluppatori. Punta a un __repr__ che assomigli alla chiamata che ricreerebbe l’oggetto; il debug diventa molto più facile quando i valori stampati sono inequivocabili.