9. Klasy¶
Klasy umożliwiają łączenie danych i funkcjonalności. Tworzenie nowej klasy, tworzy nowy typ obiektu, umożliwiając tworzenie nowych instancji tego typu. Do każdej instancji klasy można przypisać atrybuty służące do utrzymywania jej stanu. Instancje klas mogą również posiadać metody (zdefiniowane przez klasę) umożliwiające modyfikację ich stanu.
W porównaniu do innych języków programowania, w Pythonie, mechanizm dodawania nowych klas wymaga niewielkiej ilości nowej składni i semantyki. Jest to połączenie mechanizmu klas, które można znaleźć w C++ i Modula-3. Klasy w Pythonie dostarczają wszystkie standardowe cechy programowania obiektowego: mechanizm dziedziczenia klas pozwala na wiele klas bazowych, klasy pochodne mogą nadpisać każdą metodę klasy lub klas bazowych i metoda może wywołać metody klas bazowych o tej samej nazwie. Obiekty mogą zawierać dowolną ilość i rodzaj danych. Zarówno klasy jak i moduły są częścią dynamicznej natury Pythona: są tworzone w trakcie działania programu i mogą być modyfikowane później, po stworzeniu.
Korzystając z terminologii C++, składniki klas (także pola) są publiczne (z wyjątkiem zobacz poniżej Zmienne prywatne), a wszystkie metody są wirtualne. Podobnie jak w Moduli-3, nie ma skrótów pozwalających na odnoszenie się do składników klas z ich metod: metoda jest deklarowana poprzez podanie wprost jako pierwszego argumentu obiektu, który w czasie wywołania metody zostanie jej przekazany niejawnie. Podobnie jak w Smalltalku, same klasy także są obiektami. Dostarcza nam to wyrażeń semantycznych pozwalających na importowanie i zmianę nazw klasy. Inaczej niż w C++ i Moduli-3 wbudowane typy mogą stanowić klasy, z których klasa użytkownika będzie dziedziczyć. Podobnie jak w C++, większość wbudowanych operatorów ze specjalną składnią (operatory arytmetyczne, indeksowanie) może być przedefiniowane przez instancje klasy.
(Z powodu braku ogólnie zaakceptowanej terminologii w kontekście klas, będę używał terminów ze Smalltalk i C++. Użyłbym Modula-3 ponieważ semantyka jego programowania obiektowego jest bliższa Pythonowi niż C++ ale zakładam, że mniej czytelników o nim słyszało.)
9.1. Kilka słów o nazwach i obiektach¶
Obiekty mają indywidualność, a wiele nazw (w wielu zakresach) może być powiązanych z tym samym obiektem. Jest to znane jako aliasing w innych językach. Zwykle nie jest to doceniane na pierwszy rzut oka w Pythonie i można je bezpiecznie zignorować, gdy mamy do czynienia z niezmiennymi typami podstawowymi (liczby, ciągi znaków, krotki). Jednak aliasing ma prawdopodobnie zaskakujący wpływ na semantykę kodu Pythona, który obejmuje zmienne obiekty, takie jak listy, słowniki i większość innych typów. Jest to zwykle wykorzystywane z korzyścią dla programu, ponieważ aliasy pod pewnymi względami zachowują się jak wskaźniki. Na przykład przekazanie obiektu jest tanie, ponieważ implementacja przekazuje tylko wskaźnik; a jeśli funkcja modyfikuje obiekt przekazany jako argument, wywołujący zobaczy zmianę — eliminuje to potrzebę stosowania dwóch różnych mechanizmów przekazywania argumentów, jak w Pascalu.
9.2. Zasięgi widoczności i przestrzenie nazw w Pythonie¶
Przed wprowadzeniem klas, najpierw muszę powiedzieć ci coś o zasadach zakresu Pythona. Definicje klas stosują kilka zgrabnych sztuczek z przestrzeniami nazw, a żeby w pełni zrozumieć, co się dzieje, trzeba wiedzieć, jak działają zakresy i przestrzenie nazw.
Zacznijmy od kilku definicji.
Przestrzeń nazw to odwzorowanie z nazw na obiekty. Większość przestrzeni nazw jest obecnie implementowana jako słowniki Pythona, ale to zwykle nie jest zauważalne w żaden sposób (z wyjątkiem wydajności), a to może się zmienić w przyszłości. Przykładami przestrzeni nazw są: zbiór nazw wbudowanych (zawierający funkcje np. abs()
i nazwy wbudowanych wyjątków); nazwy globalne w module; oraz nazwy lokalne w wywołaniu funkcji. W pewnym sensie zbiór atrybutów obiektu również tworzy przestrzeń nazw. Ważną rzeczą, którą należy wiedzieć o przestrzeniach nazw, jest to, że nie ma absolutnie żadnych relacji między nazwami w różnych przestrzeniach nazw; na przykład, dwa różne moduły mogą zdefiniować funkcję maximize
bez zamieszania — użytkownicy modułów muszą poprzedzić go nazwą modułu.
Nawiasem mówiąc, używam słowa atrybut dla każdej nazwy następującej po kropce — na przykład w wyrażeniu z.real
, real
jest atrybutem obiektu z
. Ściśle mówiąc, odniesienia do nazw w modułach są odniesieniami atrybutowymi: w wyrażeniu modname.funcname
, modname
jest obiektem modułu, a funcname
jest jego atrybutem. W tym przypadku istnieje proste odwzorowanie między atrybutami modułu i nazwami globalnymi zdefiniowanymi w module: mają tę samą przestrzeń nazw! 1
Attributes may be read-only or writable. In the latter case, assignment to
attributes is possible. Module attributes are writable: you can write
modname.the_answer = 42
. Writable attributes may also be deleted with the
del
statement. For example, del modname.the_answer
will remove
the attribute the_answer
from the object named by modname
.
Przestrzenie nazw są tworzone w różnych momentach i mają różny czas życia. Przestrzeń nazw zawierająca nazwy wbudowane jest tworzona podczas uruchamiania interpretera Pythona i nigdy nie jest usuwana. Globalna przestrzeń nazw dla modułu jest tworzona, gdy wczytywana jest definicja modułu; zwykle przestrzenie nazw modułu również trwają do zakończenia działania interpretera. Instrukcje wykonywane przez wywołanie interpretera najwyższego poziomu, zarówno odczytane z pliku skryptu, jak i interaktywnie, są uważane za część modułu o nazwie __main__
, więc mają swoją własną globalną przestrzeń nazw. (Nazwy wbudowane w rzeczywistości również znajdują się w module; nazwany jest on builtins
).
Lokalna przestrzeń nazw dla funkcji jest tworzona przy wywołaniu funkcji i usuwana, gdy funkcja zwraca wynik lub rzuca wyjątek, którego nie obsługuje. (Właściwie, zapominanie byłoby lepszym słowem na opisanie tego, co faktycznie się dzieje). Oczywiście, każde wywołanie rekurencyjne ma swoją własną lokalną przestrzeń nazw.
Zakres to tekstowy obszar programu Python, w którym przestrzeń nazw jest bezpośrednio dostępna. „Bezpośrednio dostępna” oznacza tutaj, że niekwalifikowane odwołanie do nazwy próbuje znaleźć ją w przestrzeni nazw.
Chociaż zakresy są określane statycznie, są używane dynamicznie. Cały czas w trakcie wykonywania programu istnieją 3 lub 4 zagnieżdżone zakresy, których przestrzenie nazw są bezpośrednio dostępne:
najbardziej wewnętrzny zakres, który jest przeszukiwany jako pierwszy, zawiera nazwy lokalne
zakresy wszystkich otaczających funkcji, które są przeszukiwane począwszy od najbliższego otaczającego zakresu, zawierają nazwy nielokalne, ale także nieglobalne
przedostatni zakres zawiera globalne nazwy bieżącego modułu
najbardziej zewnętrznym zakresem (przeszukiwanym jako ostatni) jest przestrzeń nazw zawierająca nazwy wbudowane
Jeśli nazwa jest zadeklarowana jako globalna, wtedy wszystkie referencje i przypisania przechodzą bezpośrednio do przedostatniego zakresu zawierającego globalne nazwy modułu. Aby ponownie powiązać zmienne znajdujące się poza najbardziej wewnętrznym zakresem, można użyć instrukcji nonlocal
; jeśli nie są zadeklarowane jako nielokalne, zmienne te są tylko do odczytu (próba zapisu do takiej zmiennej po prostu utworzy nową zmienną lokalną w najbardziej wewnętrznym zakresie, pozostawiając identycznie nazwaną zmienną zewnętrzną bez zmian).
Zazwyczaj zakres lokalny odwołuje się do nazw lokalnych (tekstowo) bieżącej funkcji. Poza funkcjami, zakres lokalny odwołuje się do tej samej przestrzeni nazw, co zakres globalny: przestrzeni nazw modułu. Definicje klas umieszczają jeszcze jedną przestrzeń nazw w zakresie lokalnym.
Ważne jest, aby zdać sobie sprawę, że zakresy są określane tekstowo: globalny zakres funkcji zdefiniowany w module jest przestrzenią nazw tego modułu, bez względu na to, skąd lub przez jaki alias funkcja jest wywoływana. Z drugiej strony, rzeczywiste wyszukiwanie nazw odbywa się dynamicznie, w czasie wykonywania — jednak definicja języka ewoluuje w kierunku statycznego rozpoznawania nazw, w czasie „kompilacji”, więc nie należy polegać na dynamicznym rozpoznawaniu nazw! (W rzeczywistości zmienne lokalne są już określane statycznie).
Szczególnym dziwactwem Pythona jest to, że – jeśli nie działa instrukcja global
lub nonlocal
– przypisanie do nazw zawsze trafia do najbardziej wewnętrznego zakresu. Przypisania nie kopiują danych – po prostu wiążą nazwy z obiektami. To samo dotyczy usuwania: instrukcja del x
usuwa wiązanie x
z przestrzeni nazw, do której odwołuje się zakres lokalny. W rzeczywistości wszystkie operacje, które wprowadzają nowe nazwy, używają zakresu lokalnego: w szczególności instrukcje import
i definicje funkcji wiążą nazwę modułu lub funkcji w zakresie lokalnym.
Instrukcja global
może być użyta do wskazania, że określone zmienne znajdują się w zakresie globalnym i powinny być tam ponownie wiązane; instrukcja nonlocal
wskazuje, że określone zmienne znajdują się w zakresie otaczającym i powinny być tam ponownie wiązane.
9.2.1. Przykład zakresów i przestrzeni nazw¶
Oto przykład pokazujący, jak odwoływać się do różnych zakresów i przestrzeni nazw oraz jak global
i nonlocal
wpływają na wiązanie zmiennych:
def scope_test():
def do_local():
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam"
do_local()
print("After local assignment:", spam)
do_nonlocal()
print("After nonlocal assignment:", spam)
do_global()
print("After global assignment:", spam)
scope_test()
print("In global scope:", spam)
Wyjście przykładowego kodu to:
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
Zauważ, że lokalne przypisanie (które jest domyślne) nie zmieniło wiązania spam w scope_test. Przypisanie nonlocal
zmieniło wiązanie spam w scope_test, a przypisanie global
zmieniło wiązanie na poziomie modułu.
Można również zauważyć, że nie było wcześniejszego powiązania dla spam przed przypisaniem global
.
9.3. Pierwsze spojrzenie na klasy¶
Klasy wprowadzają trochę nowej składni, trzy nowe typy obiektów i trochę nowej semantyki.
9.3.1. Składnia definicji klasy¶
Najprostsza forma definicji klasy wygląda następująco:
class ClassName:
<statement-1>
.
.
.
<statement-N>
Definicje klas, podobnie jak definicje funkcji (instrukcje def
), muszą zostać wykonane, zanim będą miały jakikolwiek efekt. (Można sobie wyobrazić umieszczenie definicji klasy w gałęzi instrukcji if
lub wewnątrz funkcji.)
W praktyce, instrukcje wewnątrz definicji klasy będą zwykle definicjami funkcji, ale inne instrukcje są dozwolone, a czasem przydatne — wrócimy do tego później. Definicje funkcji wewnątrz klasy zwykle mają specyficzną formę listy argumentów, podyktowaną konwencjami wywoływania metod — ponownie, zostanie to wyjaśnione później.
Po wejściu w definicję klasy tworzona jest nowa przestrzeń nazw i używana jako zakres lokalny — a zatem wszystkie przypisania do zmiennych lokalnych trafiają do tej nowej przestrzeni nazw. W szczególności, definicje funkcji wiążą nazwę nowej funkcji w tej przestrzeni nazw.
When a class definition is left normally (via the end), a class object is
created. This is basically a wrapper around the contents of the namespace
created by the class definition; we’ll learn more about class objects in the
next section. The original local scope (the one in effect just before the class
definition was entered) is reinstated, and the class object is bound here to the
class name given in the class definition header (ClassName
in the
example).
9.3.2. Obiekty klas¶
Obiekty klas obsługują dwa rodzaje operacji: odniesienia do atrybutów i tworzenie instancji.
Odniesienia do atrybutów używają standardowej składni używanej dla wszystkich odniesień do atrybutów w Pythonie: obj.name
. Prawidłowe nazwy atrybutów to wszystkie nazwy, które znajdowały się w przestrzeni nazw klasy, gdy obiekt klasy został utworzony. Tak więc, jeśli definicja klasy wyglądała tak:
class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
then MyClass.i
and MyClass.f
are valid attribute references, returning
an integer and a function object, respectively. Class attributes can also be
assigned to, so you can change the value of MyClass.i
by assignment.
__doc__
is also a valid attribute, returning the docstring belonging to
the class: "A simple example class"
.
Instancjonowanie klasy używa notacji funkcji. Wystarczy wyobrazić sobie, że obiekt klasy jest bezparametrową funkcją, która zwraca nową instancję klasy. Na przykład (zakładając powyższą klasę):
x = MyClass()
tworzy nową instancję klasy i przypisuje ten obiekt do zmiennej lokalnej x
.
The instantiation operation („calling” a class object) creates an empty object.
Many classes like to create objects with instances customized to a specific
initial state. Therefore a class may define a special method named
__init__()
, like this:
def __init__(self):
self.data = []
When a class defines an __init__()
method, class instantiation
automatically invokes __init__()
for the newly created class instance. So
in this example, a new, initialized instance can be obtained by:
x = MyClass()
Of course, the __init__()
method may have arguments for greater
flexibility. In that case, arguments given to the class instantiation operator
are passed on to __init__()
. For example,
>>> class Complex:
... def __init__(self, realpart, imagpart):
... self.r = realpart
... self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)
9.3.3. Obiekty instancji¶
Co możemy zrobić z obiektami instancji? Jedynymi operacjami rozumianymi przez obiekty instancji są odniesienia do atrybutów. Istnieją dwa rodzaje poprawnych nazw atrybutów: atrybuty danych i metody.
Data attributes correspond to „instance variables” in Smalltalk, and to „data
members” in C++. Data attributes need not be declared; like local variables,
they spring into existence when they are first assigned to. For example, if
x
is the instance of MyClass
created above, the following piece of
code will print the value 16
, without leaving a trace:
x.counter = 1
while x.counter < 10:
x.counter = x.counter * 2
print(x.counter)
del x.counter
The other kind of instance attribute reference is a method. A method is a function that „belongs to” an object. (In Python, the term method is not unique to class instances: other object types can have methods as well. For example, list objects have methods called append, insert, remove, sort, and so on. However, in the following discussion, we’ll use the term method exclusively to mean methods of class instance objects, unless explicitly stated otherwise.)
Prawidłowe nazwy metod obiektu instancji zależą od jego klasy. Z definicji wszystkie atrybuty klasy, które są obiektami funkcji definiują odpowiednie metody jej instancji. Tak więc w naszym przykładzie, x.f
jest poprawnym odwołaniem do metody, ponieważ MyClass.f
jest funkcją, ale x.i
nie jest, ponieważ MyClass.i
nie jest. Ale x.f
nie jest tym samym co MyClass.f
— jest obiektem metody, a nie obiektem funkcji.
9.3.4. Obiekty metod¶
Zazwyczaj metoda jest wywoływana zaraz po jej powiązaniu:
x.f()
In the MyClass
example, this will return the string 'hello world'
.
However, it is not necessary to call a method right away: x.f
is a method
object, and can be stored away and called at a later time. For example:
xf = x.f
while True:
print(xf())
będzie drukować hello world
do końca czasu.
What exactly happens when a method is called? You may have noticed that
x.f()
was called without an argument above, even though the function
definition for f()
specified an argument. What happened to the argument?
Surely Python raises an exception when a function that requires an argument is
called without any — even if the argument isn’t actually used…
Właściwie, być może zgadłeś odpowiedź: szczególną rzeczą w metodach jest to, że obiekt instancji jest przekazywany jako pierwszy argument z funkcja. W naszym przykładzie wywołanie x.f()
jest dokładnie równoważne wywołaniu MyClass.f(x)
. Ogólnie rzecz biorąc, wywołanie metody z listą n argument jest równoważne wywołaniu odpowiedniej funkcja z listą argument, która jest tworzona przez wstawienie obiektu instancji metody przed pierwszym argument.
If you still don’t understand how methods work, a look at the implementation can perhaps clarify matters. When a non-data attribute of an instance is referenced, the instance’s class is searched. If the name denotes a valid class attribute that is a function object, a method object is created by packing (pointers to) the instance object and the function object just found together in an abstract object: this is the method object. When the method object is called with an argument list, a new argument list is constructed from the instance object and the argument list, and the function object is called with this new argument list.
9.3.5. Zmienne klas i instancji¶
Ogólnie rzecz biorąc, zmienne instancji są dla danych unikalnych dla każdej instancji, a zmienne klasy są dla atrybutów i metod współdzielonych przez wszystkie instancje klasy:
class Dog:
kind = 'canine' # class variable shared by all instances
def __init__(self, name):
self.name = name # instance variable unique to each instance
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind # shared by all dogs
'canine'
>>> e.kind # shared by all dogs
'canine'
>>> d.name # unique to d
'Fido'
>>> e.name # unique to e
'Buddy'
Jak omówiono w Kilka słów o nazwach i obiektach, współdzielone dane mogą mieć zaskakujące efekty z udziałem mutable obiektów takich jak listy i słownik. Na przykład, lista sztuczki w poniższym kodzie nie powinna być używana jako zmienna klasowa, ponieważ tylko jedna lista byłaby współdzielona przez wszystkie instancje klasy Dog:
class Dog:
tricks = [] # mistaken use of a class variable
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks # unexpectedly shared by all dogs
['roll over', 'play dead']
Prawidłowy projekt klasy powinien zamiast tego używać zmiennej instancji:
class Dog:
def __init__(self, name):
self.name = name
self.tricks = [] # creates a new empty list for each dog
def add_trick(self, trick):
self.tricks.append(trick)
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks
['roll over']
>>> e.tricks
['play dead']
9.4. Uwagi losowe¶
Jeśli ten sam atrybut występuje zarówno w instancji, jak i w klasie, wówczas wyszukiwanie atrybut nadaje priorytet instancji:
>>> class Warehouse:
... purpose = 'storage'
... region = 'west'
...
>>> w1 = Warehouse()
>>> print(w1.purpose, w1.region)
storage west
>>> w2 = Warehouse()
>>> w2.region = 'east'
>>> print(w2.purpose, w2.region)
storage east
Do danych atrybut mogą odwoływać się zarówno metody, jak i zwykli użytkownicy („klienci”) obiektu. Innymi słowy, klasy nie nadają się do implementacji czysto abstrakcyjnych typów danych. W rzeczywistości nic w Python nie umożliwia wymuszenia ukrywania danych — wszystko opiera się na konwencji. (Z drugiej strony, implementacja Python, napisana w C, może całkowicie ukryć szczegóły implementacji i kontrolować dostęp do obiektu, jeśli to konieczne; może to być wykorzystane przez rozszerzenia Python napisane w C).
Klienci powinni używać danych atrybut z ostrożnością — klienci mogą zepsuć niezmienniki utrzymywane przez metody poprzez stemplowanie ich danych atrybut. Należy pamiętać, że klienci mogą dodawać własne dane atrybut do obiektu instancji bez wpływu na ważność metod, o ile unika się konfliktów nazw — ponownie, konwencja nazewnictwa może tutaj zaoszczędzić wiele problemow.
Nie ma skrótu do odwoływania się do danych atrybut (lub innych metod!) z poziomu metod. Uważam, że w rzeczywistości zwiększa to czytelność metod: nie ma szans na pomylenie zmiennych lokalnych i zmiennych instancji podczas przeglądania metody.
Często pierwsza argument metody jest nazywana self
. Jest to nic więcej niż konwencja: nazwa self
nie ma absolutnie żadnego specjalnego znaczenia dla Python. Należy jednak pamiętać, że nieprzestrzeganie tej konwencji może sprawić, że kod będzie mniej czytelny dla innych programistów Python, a także możliwe jest napisanie programu przeglądarki klas, który opiera się na takiej konwencji.
Każdy obiekt funkcja będący klasą atrybut definiuje metodę dla instancji tej klasy. Nie jest konieczne, aby definicja funkcja była tekstowo zawarta w definicja klasy: przypisanie obiektu funkcja do zmiennej lokalnej w klasie jest również w porządku. Na przykład:
# Function defined outside the class
def f1(self, x, y):
return min(x, x+y)
class C:
f = f1
def g(self):
return 'hello world'
h = g
Now f
, g
and h
are all attributes of class C
that refer to
function objects, and consequently they are all methods of instances of
C
— h
being exactly equivalent to g
. Note that this practice
usually only serves to confuse the reader of a program.
Metody mogą wywoływać inne metody przy użyciu metody atrybut z self
argument
class Bag:
def __init__(self):
self.data = []
def add(self, x):
self.data.append(x)
def addtwice(self, x):
self.add(x)
self.add(x)
Metody mogą odwoływać się do nazw globalnych w taki sam sposób jak zwykłe funkcje. Zakres globalny powiązany z metodą to moduł zawierający jej definicję. (Klasa nigdy nie jest używana jako zakres globalny.) Podczas gdy rzadko można napotkać dobry powód do używania danych globalnych w metodzie, istnieje wiele uzasadnionych zastosowań zakresu globalnego: po pierwsze, funkcja i moduł importowane do zakresu globalnego mogą być używane przez metody, a także funkcja i klasy w nim zdefiniowane. Zazwyczaj klasa zawierająca metodę sama jest zdefiniowana w tym globalnym zakresie, a w następnej sekcji znajdziemy kilka dobrych powodów, dla których metoda chciałaby odwoływać się do własnej klasy.
Każda wartość jest obiektem, a zatem ma klasę (zwaną również typem). Jest ona przechowywana jako object.__class__
.
9.5. Dziedziczenie¶
Oczywiście funkcja językowa nie byłaby godna nazwy „klasa” bez obsługi dziedziczenia. Składnia pochodnej definicja klasy wygląda tak:
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
The name BaseClassName
must be defined in a scope containing the
derived class definition. In place of a base class name, other arbitrary
expressions are also allowed. This can be useful, for example, when the base
class is defined in another module:
class DerivedClassName(modname.BaseClassName):
Wykonanie pochodnej definicja klasy przebiega tak samo, jak w przypadku klasa bazowa. Gdy konstruowany jest obiekt klasy, zapamiętywany jest klasa bazowa. Jest to wykorzystywane do rozwiązywania referencji atrybut: jeśli żądany atrybut nie zostanie znaleziony w klasie, wyszukiwanie jest kontynuowane w klasie bazowej. Ta reguła jest stosowana rekurencyjnie, jeśli sama klasa bazowa jest pochodną innej klasy.
Nie ma nic specjalnego w instancjonowaniu klas pochodnych: DerivedClassName()
tworzy nową instancję klasy. Odniesienia do metod są rozwiązywane w następujący sposób: odpowiednia klasa atrybut jest przeszukiwana, w razie potrzeby schodząc w dół łańcucha klas bazowych, a odniesienie do metody jest ważne, jeśli daje to obiekt funkcja.
Klasy pochodne mogą nadpisywać metody swoich klas bazowych. Ponieważ metody nie mają specjalnych przywilejów podczas wywoływania innych metod tego samego obiektu, metoda klasa bazowa, która wywołuje inną metodę zdefiniowaną w tym samym klasa bazowa może skończyć się wywołaniem metody klasy pochodnej, która ją nadpisuje. (Dla programistów C++: wszystkie metody w Python są efektywnie virtual
).
Metoda nadrzędna w klasie pochodnej może w rzeczywistości chcieć rozszerzyć, a nie tylko zastąpić metodę klasa bazowa o tej samej nazwie. Istnieje prosty sposób na bezpośrednie wywołanie metody klasa bazowa: wystarczy wywołać BaseClassName.methodname(self, arguments)
. Jest to czasami przydatne również dla klientów. (Należy pamiętać, że działa to tylko wtedy, gdy klasa bazowa jest dostępna jako BaseClassName
w zakresie globalnym).
Python ma dwa wbudowane funkcje, które pracuja z dziedziczeniem:
Użyj
isinstance()
do sprawdzenia typu instancji:isinstance(obj, int)
będzieTrue
tylko wtedy, gdyobj.__class__
jestint
lub jakąś klasą pochodną odint
.Użyj
issubclass()
do sprawdzenia dziedziczenia klas:issubclass(bool, int)
jestTrue
, ponieważbool
jest podklasa zint
. Jednakissubclass(float, int)
jestFalse
, ponieważfloat
nie jest podklasa zint
.
9.5.1. Dziedziczenie wielokrotne¶
Python obsługuje również dziedziczenie wielokrotne. Strona definicja klasy z wieloma klasami bazowymi wygląda tak:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
For most purposes, in the simplest cases, you can think of the search for
attributes inherited from a parent class as depth-first, left-to-right, not
searching twice in the same class where there is an overlap in the hierarchy.
Thus, if an attribute is not found in DerivedClassName
, it is searched
for in Base1
, then (recursively) in the base classes of Base1
,
and if it was not found there, it was searched for in Base2
, and so on.
W rzeczywistości jest to nieco bardziej skomplikowane; kolejność rozwiązywania metod zmienia się dynamicznie, aby wspierać kooperacyjne wywołania super()
. Podejście to jest znane w niektórych innych językach wielokrotnego dziedziczenia jako call-next-method i jest bardziej wydajne niż super wywołanie występujące w językach pojedynczego dziedziczenia.
Dynamic ordering is necessary because all cases of multiple inheritance exhibit
one or more diamond relationships (where at least one of the parent classes
can be accessed through multiple paths from the bottommost class). For example,
all classes inherit from object
, so any case of multiple inheritance
provides more than one path to reach object
. To keep the base classes
from being accessed more than once, the dynamic algorithm linearizes the search
order in a way that preserves the left-to-right ordering specified in each
class, that calls each parent only once, and that is monotonic (meaning that a
class can be subclassed without affecting the precedence order of its parents).
Taken together, these properties make it possible to design reliable and
extensible classes with multiple inheritance. For more detail, see
https://www.python.org/download/releases/2.3/mro/.
9.6. Zmienne prywatne¶
„Prywatne” zmienne instancje, do których nie można uzyskać dostępu inaczej niż z wnętrza obiektu, nie istnieją w Python. Istnieje jednak konwencja, której przestrzega większość kodu Python: nazwa poprzedzona podkreśleniem (np. _spam
) powinna być traktowana jako niepubliczna część API (niezależnie od tego, czy jest to funkcja, metoda czy członek danych). Należy ją traktować jako szczegół implementacji i może ona ulec zmianie bez powiadomienia.
Ponieważ istnieje uzasadniony przypadek użycia dla członków klasy-prywatnej (mianowicie, aby uniknąć kolizji nazw z nazwami zdefiniowanymi przez podklasa), istnieje ograniczone wsparcie dla takiego mechanizmu, zwanego name mangling. Każdy identyfikator w postaci __spam
(co najmniej dwa początkowe podkreślenia, co najwyżej jedno końcowe podkreślenie) jest tekstowo zastępowany przez _classname__spam
, gdzie classname
jest bieżącą nazwą klasy z usuniętymi początkowymi podkreśleniami. Zamiana ta jest wykonywana bez względu na pozycję składniową identyfikatora, o ile występuje on w definicji klasy.
Manipulowanie nazwami jest pomocne w umożliwieniu podklasa nadpisywania metod bez przerywania wywołań metod wewnątrzklasowych. Na przykład:
class Mapping:
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)
def update(self, iterable):
for item in iterable:
self.items_list.append(item)
__update = update # private copy of original update() method
class MappingSubclass(Mapping):
def update(self, keys, values):
# provides new signature for update()
# but does not break __init__()
for item in zip(keys, values):
self.items_list.append(item)
Powyższy przykład zadziała nawet wtedy, gdy MappingSubclass
wprowadzi identyfikator __update
, ponieważ zostanie on zastąpiony odpowiednio _Mapping__update
w klasie Mapping
i _MappingSubclass__update
w klasie MappingSubclass
.
Należy pamiętać, że reguły mieszania zostały zaprojektowane głównie w celu uniknięcia wypadków; nadal możliwy jest dostęp lub modyfikacja zmiennej, która jest uważana za prywatną. Może to być nawet przydatne w szczególnych okolicznościach, takich jak debugger.
Zauważ, że kod przekazany do exec()
lub eval()
nie uważa nazwy klasy wywołującej za bieżącą klasę; jest to podobne do efektu global
instrukcja , którego efekt jest również ograniczony do kodu, który jest kompilowany bajtowo. To samo ograniczenie dotyczy getattr()
, setattr()
i delattr()
, a także bezpośredniego odwoływania się do __dict__
.
9.7. Przypadki losowe¶
Czasami przydatne jest posiadanie typu danych podobnego do „rekordu” Pascala lub „struktury” C, łączącego kilka nazwanych elementów danych. Idiomatycznym podejściem jest użycie w tym celu dataclasses
:
from dataclasses import dataclass
@dataclass
class Employee:
name: str
dept: str
salary: int
>>> john = Employee('john', 'computer lab', 1000)
>>> john.dept
'computer lab'
>>> john.salary
1000
A piece of Python code that expects a particular abstract data type can often be
passed a class that emulates the methods of that data type instead. For
instance, if you have a function that formats some data from a file object, you
can define a class with methods read()
and readline()
that get the
data from a string buffer instead, and pass it as an argument.
Instance method objects have attributes, too: m.__self__
is the instance
object with the method m()
, and m.__func__
is the function object
corresponding to the method.
9.8. Iteratory¶
Prawdopodobnie zauważyłeś już, że większość obiektów kontenera można iterować za pomocą for
instrukcja
for element in [1, 2, 3]:
print(element)
for element in (1, 2, 3):
print(element)
for key in {'one':1, 'two':2}:
print(key)
for char in "123":
print(char)
for line in open("myfile.txt"):
print(line, end='')
Ten styl dostępu jest jasny, zwięzły i wygodny. Użycie iterators przenika i ujednolica Python. Za kulisami, for
instrukcja wywołuje iter()
na obiekcie kontenera. Obiekt funkcja zwracać i iterator definiują metodę __next__()
, która uzyskuje dostęp do elementów w kontenerze jeden po drugim. Gdy nie ma więcej elementów, __next__()
rzucić wyjątek StopIteration
, który informuje pętlę for
o zakończeniu. Metodę __next__()
można wywołać za pomocą next()
wbudowanej funkcja; ten przykład pokazuje, jak to wszystko działa:
>>> s = 'abc'
>>> it = iter(s)
>>> it
<str_iterator object at 0x10c90e650>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
next(it)
StopIteration
Having seen the mechanics behind the iterator protocol, it is easy to add
iterator behavior to your classes. Define an __iter__()
method which
returns an object with a __next__()
method. If the class
defines __next__()
, then __iter__()
can just return self
:
class Reverse:
"""Iterator for looping over a sequence backwards."""
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
... print(char)
...
m
a
p
s
9.9. Generatory¶
Generatory są prostym i potężnym narzędziem do tworzenia iteratorów. Są one pisane jak zwykłe funkcje, ale używają instrukcji yield
za każdym razem, gdy chcą zwracać dane. Za każdym razem, gdy next()
jest na nim wywoływany, generator wznawia działanie od miejsca, w którym zostało przerwane (pamięta wszystkie wartości danych i które instrukcje było ostatnio wykonywane). Przykład pokazuje, że tworzenie generatorów może być banalnie proste:
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
>>> for char in reverse('golf'):
... print(char)
...
f
l
o
g
Anything that can be done with generators can also be done with class-based
iterators as described in the previous section. What makes generators so
compact is that the __iter__()
and __next__()
methods
are created automatically.
Inną kluczową cechą jest to, że zmienne lokalne i stan wykonania są automatycznie zapisywane między wywołaniami. Sprawia to, że funkcja jest łatwiejszy do napisania i znacznie bardziej przejrzysty niż podejście wykorzystujące zmienne instancji, takie jak self.index
i self.data
.
Oprócz automatycznego tworzenia metod i zapisywania stanu programu, po zakończeniu generatory automatycznie rzucić StopIteration
. W połączeniu, funkcje te ułatwiają tworzenie iteratorów bez większego wysiłku niż napisanie zwykłego funkcja.
9.10. generator wyrażenia¶
Niektóre proste generatory mogą być kodowane w zwięzły sposób jako wyrażenia przy użyciu składni podobnej do list comprehensions, ale z nawiasami zamiast nawiasów kwadratowych. Wyrażenia te są przeznaczone do sytuacji, w których generator jest używane od razu przez otaczającą je funkcja. Wyrażenia generator są bardziej zwięzłe, ale mniej wszechstronne niż pełne definicje generator i zwykle są bardziej przyjazne dla pamięci niż równoważne wyrażenia listowe.
Przykłady:
>>> sum(i*i for i in range(10)) # sum of squares
285
>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec)) # dot product
260
>>> unique_words = set(word for line in page for word in line.split())
>>> valedictorian = max((student.gpa, student.name) for student in graduates)
>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']
Przypisy
- 1
Except for one thing. Module objects have a secret read-only attribute called
__dict__
which returns the dictionary used to implement the module’s namespace; the name__dict__
is an attribute but not a global name. Obviously, using this violates the abstraction of namespace implementation, and should be restricted to things like post-mortem debuggers.