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
A class block at the top holding shared methods and the class attribute "kind"; below it, two instance boxes each holding their own "value" attribute, with arrows from each instance up to the class.

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.