2.21. Metody i atrybuty

Metoda to funkcja zdefiniowana wewnątrz klasy. Pierwszym parametrem jest instancja, na której metoda jest wywoływana; zgodnie z konwencją nazywa się go self.

2.21.1. Metody instancji

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())

Wynik:

5.0

Wywołanie p.magnitude() automatycznie przekazuje p jako self. Ciała metod odczytują i aktualizują atrybuty poprzez self w taki sam sposób, w jaki funkcje odczytują i aktualizują nazwy poprzez parametry.

2.21.2. Atrybuty instancji a atrybuty klasy

Atrybuty przypisane do self – zazwyczaj wewnątrz __init__ – należą do tej jednej instancji:

a = Point(1, 2)
b = Point(10, 20)
a.x = 99
print(a.x, b.x)           # 99 10

a.x i b.x to różne miejsca przechowywania; modyfikacja jednego nie wpływa na drugie.

Nazwy przypisane w ciele klasy, poza jakąkolwiek metodą, są współdzielone przez każdą instancję klasy:

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
Blok klasy na górze zawierający współdzielone metody oraz atrybut klasy "kind"; pod nim dwie ramki instancji, każda zawierająca własny atrybut "value", ze strzałkami od każdej instancji w górę do klasy.

Atrybuty klasy żyją na samej klasie i są współdzielone. Atrybuty instancji żyją na każdej instancji.

Atrybut klasy jest osiągany w taki sam sposób jak atrybut instancji (a.kind); Python szuka najpierw na instancji, a potem na klasie. Przypisanie do a.kind utworzyłoby nowy atrybut instancji, który przesłania atrybut klasy, pozostawiając b.kind nietknięte.

Używaj atrybutów klasy dla stałych i wartości domyślnych, które każda instancja powinna widzieć tak samo. Używaj atrybutów instancji dla stanu, którego własną kopię powinien posiadać każdy obiekt.

2.21.3. __str__ i __repr__

Dwie specjalne metody kontrolują sposób, w jaki instancja jest drukowana. __str__ zwraca „przyjazny” łańcuch używany przez print(); __repr__ zwraca łańcuch „deweloperski” używany w REPL oraz wewnątrz wyświetlania kontenerów:

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

Wynik:

(3, 4)
[Point(3, 4), Point(3, 4)]

Jeśli zdefiniowane jest tylko __repr__, print() korzysta z niego jako rozwiązania zastępczego – więc pojedyncze dobrze napisane __repr__ zazwyczaj wystarcza. Definiuj również __str__ tylko wtedy, gdy forma przyjazna powinna wyglądać inaczej niż forma deweloperska. Dąż do __repr__, które wygląda jak wywołanie odtwarzające obiekt; debugowanie staje się dramatycznie łatwiejsze, gdy drukowane wartości są jednoznaczne.