Podsekcje

 
9. Klasy

System klas w Pythonie wyrażony jest minimalnym zestawem nowych środków składniowych i semantycznych. Jest to mieszanka mechanizmów znanych w C++ i Moduli-3. Tak jak w przypadku modułów, klasy w Pythonie nie stawiają żadnych barier pomiędzy swoją definicją a programistą. Polega się tutaj raczej na ,,grzeczności'' programisty, którego dobre wychowanie nie pozwala na ,,włamywanie się do definicji'' klasy. Najważniejsze cechy systemu klas zachowane są tu w całej swej mocy: mechanizm dziedziczenia klas pozwala na posiadanie wielu klas bazowych, klasa pochodna jest w stanie przesłonić definicje metod klas bazowych, jej metoda może wywoływać metodę klasy bazowej o tej samej nazwie. Obiekty mogą posiadać dowolną liczbę danych prywatnych.

W terminologii przejętej w C++, wszystkie składowe klasy pythonowej (włącznie z atrybutami) są publiczne, a wszystkie funkcje składowe są wirtualne. Nie istnieją specjalne funkcje destruktora i konstruktora. Podobnie jak w Moduli-3 nie istnieją skrótowe sposoby zapisu odniesienia do składowej obiektu we wnętrzu metody: metoda deklarowana jest z wymienionym wprost pierwszym argumentem reprezentującym obiekt, na rzecz którego zostanie wywołana. Podobnie jak w Smalltalku, klasy same są obiektami, aczkolwiek w szerszym tego słowa znaczeniu: w Pythonie wszystkie typy danych to obiekty. Ta cecha określa semantykę mechanizmu importowania i zmiany nazw. Jednak, podobnie jak w C++ i Moduli-3, typy wbudowane nie mogą być użyte jako klasy bazowe klas zdefiniowanych przez użytkownika. Tak jak w C++, ale w odróżnieniu od Moduli-3, większość wbudowanych operatorów mogą zostać za pomocą specjalnej składni przedefiniowane dla różnych typów klasowych.

 
9.1 Słowo na temat terminologii

Z powodu braku ogólnie przyjętej terminologii, w odniesieniu do klas używać będę terminów z języka Smalltalk i C++. (Chętnie używałbym terminologii zaczerpniętej z Moduli-3, ponieważ obiektowa semantyka tego języka jest bliższa temu, co reprezentuje w tym względzie Python, ale sądzę, że o Moduli-3 słyszało niewielu czytelników).

Muszę także ostrzec, że istnieje pewna terminologiczna pułapka dla czytelników obeznanych z technologią obiektową: słowo «obiekt» w Pythonie nie oznacza koniecznie konkretu (instancji) klasy. Podobnie jak w C++ i Moduli-3, w odróżnieniu od Smalltalka, nie wszystkie typy w Pythonie są klasowe. Podstawowe, wbudowane typy danych,, jak liczby całkowite i listy nie są klasowe (tzn. nie posiadają klasy), nawet tak egzotyczne typy jak pliki też nie są. Jednakże, wszystkie typy Pythona przejawiają w swym zachowaniu to, co najlepiej można by wyrazić słowem «obiekt».

Obiekty posiadają swoją indywidualność, mogąc posiadać jednocześnie wiele nazw (w wielu zasięgach nazw). W innych językach programowania nazywa się to aliasami. Zazwyczaj nie jest to docenianie przy pierwszym spotkaniu z Pythonem i w bezpieczny sposób jest ignorowane przy pracy z niemutowalnymi typami danych (liczbami, napisami, niemutowalnymi listami). Jednakże, aliasy wpływają (celowo) na semantykę kodu Pythona, gdy w grę wchodzą mutowalne typy danych, takie jak listy, słowniki i większość typów reprezentujących byty jednostkowe na zewnątrz programu (pliki, okna itp.) Zwykle aliasy przynoszą korzyści, ponieważ zachowują się pod pewnymi względami jak wskaźniki. Na przykład: przekazanie obiektu jako argumentu wywołania nie kosztuje dużo tylko w przypadku przekazania przez implementację wskaźnika do niego. Jeżeli funkcja modyfikuje przekazany w ten sposób obiekt, wywołujący funkcje widzi tę zmianę -- jest to powodem występowania dwóch różnych mechanizmów przekazywania parametrów np. w Pascalu.

 
9.2 Przestrzenie i zasięgi nazw w Pythonie

Zanim przejdziemy do omawiania klas, muszę wspomnieć co nieco o regułach zasięgu nazw w Pythonie. Definicja klasy wprowadza do koncepcji przestrzeni nazw trochę zamieszania i powinno się wiedzieć jak działają zasięgi przestrzeni nazw, aby w pełni zrozumieć, co się w klasach dzieje. Przez przypadek, wiedza o tym mechanizmie jest bardzo użyteczna dla każdego zaawansowanego programisty Pythona.

Zacznijmy od definicji.

Przestrzeń nazw jest wskazaniem obiektu poprzez nazwę. Większość przestrzeni nazw w Pythonie zaimplementowanych jest obecnie w postaci pythonowych słowników, ale zwykle nie jest to zauważalne (z wyjątkiem wydajności) i może się w przyszłości zmienić. Przykładami przestrzeni nazw są: zbiór nazw wbudowanych (funkcje takie jak abs() i nazwy wyjątków wbudowanych), nazwy globalne modułu i nazwy lokalne podczas wywołania funkcji. W pewnym sensie, zbiór atrybutów obiektów także jest przestrzenią nazw. Ważne jest, aby pamiętać, że nie istnieje absolutnie żaden związek pomiędzy nazwami z różnych przestrzeni nazw. Dwa różne moduły mogą definiować funkcję ,,maksymalizuj'' -- użytkownik tych modułów musi poprzedzić jej nazwę nazwą modułu.

Przy okazji: używam słowa atrybut dla każdej nazwy poprzedzonej kropką -- np. w wyrażeniu z.real, real jest atrybutem obiektu z. Mówiąc ściśle, odniesienia do nazw w modułach są odniesieniami do atrybutów: w wyrażeniu nazwa_modulu.nazwa_funkcji, nazwa_modulu jest obiektem modułu, a nazwa_funkcji jest jego atrybutem. W tym przypadku istnieje bezpośrednia relacja pomiędzy atrybutami modułu i nazwami globalnymi w nim zdefiniowanymi: oba rodzaje nazw zdefiniowane są w tej samej przestrzeni nazw! 9.1

Atrybuty mogą być tylko do odczytu lub zapisywalne. W tym ostatnim przypadku można dokonać operacji przypisania wartości do atrybutu. Atrybuty modułu są zapisywalne: można np. napisać "modul.wynik = 42". Tego rodzaju atrybuty mogą być usuwane za pomocą operatora del, np. "del modul.wynik".

Przestrzenie nazw tworzone są w różnych chwilach i są aktywne przez różny czas. Przestrzeń nazw zawierająca nazwy wbudowane tworzona jest podczas rozpoczęcia pracy Pythona i nigdy nie jest usuwana. Przestrzeń nazw globalnych modułu tworzona jest podczas wczytywania jego definicji i jest aktywna również do chwili zakończenia pracy interpretera. Instrukcje wykonywane przez szczytowe wywołania interpretera, zarówno czytane z pliku jak i wprowadzane interaktywnie, są częścią modułu o nazwie __main__ -- tak więc posiadają swoją własną przestrzeń nazw globalnych. (Nazwy wbudowane również przechowywane są w module o nazwie __builtin__.)

Przestrzeń nazw lokalnych funkcji tworzona jest w momencie jej wywołania i niszczona, gdy następuje powrót z funkcji lub zgłoszono został w niej wyjątek, który nie został tam obsłużony (,,zapomniany'' byłoby właściwym słowem, które dokładnie oddałoby to, co naprawdę się wtedy dzieje). Oczywiście, wywołanie rekurencyjne powoduje tworzenie za każdym razem nowej przestrzeni nazw lokalnych.

Zasięg jest tekstowym obszarem programu Pythona, w którym przestrzeń nazw jest wprost osiągalna. ,,Wprost osiągalna'' oznacza tutaj, że niekwalifikowane9.2odniesienia do nazwy znajdują tę nazwę w obowiązującej przestrzeni nazw.

Pomimo iż zasięgi określane są w sposób statyczny, używane są w sposób dynamiczny. W każdym momencie wykonania programu, używa się dokładnie trzech zagnieżdżonych zasięgów nazw (tzn. wprost osiągalne są trzy przestrzenie nazw): (i) najbardziej zagnieżdżony, w którym najpierw poszukuje się nazwy, zawiera on nazwy lokalne; (ii) środkowy, przeszukiwany w następnej kolejności, który zawiera aktualne nazwy globalne modułu oraz (iii) zewnętrzny (przeszukiwany na końcu) jest zasięgiem nazw wbudowanych.

Lokalny zasięg nazw umożliwia zwykle odniesienia do nazw występujących (tekstowo) w bieżącej funkcji. Poza obrębem funkcji, lokalna nazwa jest nazwą z przestrzeni nazw globalnych modułu. Definicja klasy stanowi jeszcze jedną przestrzeń nazw w rodzinie przestrzeni nazw lokalnych.

Ważne jest zdawać sobie sprawę z tego, że zasięgi nazw określane są statycznie: zasięg globalny funkcji zdefiniowanej w module jest jego przestrzenią nazw globalnych, bez względu na to skąd i za pomocą jakiego aliasu funkcja została wywołana. Z drugiej strony, właściwe poszukiwanie nazw zachodzi w sposób dynamiczny, w czasie wykonywania programu -- jakkolwiek, definicja języka ewoluuje w stronę statycznego sposobu rozstrzygania nazw, w czasie kompilacji. Nie można więc polegać w programach na dynamicznym rozstrzyganiu nazw (w rzeczywistości, zmienne lokalne są już w obecnej wersji określane statycznie).

Jedną z cech szczególnych Pythona jest to, że przypisanie zawsze zachodzi w najbardziej zagnieżdżonym zasięgu. Przypisania nie powodują kopiowania danych -- przywiązują jedynie nazwy do obiektów. To samo zachodzi w przypadku usuwania: instrukcja "del x" usuwa związek obiektu identyfikowanego przez nazwę x z ta nazwą w przestrzeni nazw lokalnych. W rzeczywistości, wszystkie operacje, które wprowadzają nowe nazwy do przestrzeni nazw lokalnych, to robią. Dotyczy to zawłaszcza instrukcji importu i definicji funkcji (instrukcja global może zostać użyta do oznaczenia, że wyszczególniona z nazwa należy do przestrzeni nazw globalnych).

 
9.3 Pierwszy wgląd w klasy

Klasy wymagają użycia pewnej nowej składni, trzech nowych typów obiektowych i trochę nowej semantyki.

 
9.3.1 Składnia definicji klasy

Najprostszą formą definicji klasy jest:

class NazwaKlasy:
    <instrukcja-1>
    .
    .
    .
    <instrukcja-N>

Definicje klas, podobnie jak definicje funkcji (instrukcja def) muszą zostać wykonane, zanim zostaną użyte (można umieścić definicję klasy w rozgałęzieniu instrukcji if lub wewnątrz funkcji).

W praktyce, instrukcje wewnątrz definicji klasy będą definicjami funkcji, ale również dozwolone są inne, czasem bardzo użyteczne -- powrócimy do tego później. Definicja funkcji w ciele klasy posiada zwykle szczególną formę listy argumentów formalnych, dedykowanych szczególnej konwencji wywoływania metod 9.3-- to również zostanie wyjaśnione później.

Kiedy wprowadza się definicję klasy do programu, tworzona jest nowa przestrzeń nazw używana jako zasięg lokalny nazw -- w ten sposób, wszystkie przypisania do zmiennych lokalnych dotyczą nazw z tej właśnie przestrzeni. W szczególności, definicja funkcji powoduje powiązanie jej nazwy z obiektem tej funkcji w lokalnej przestrzeni nazw klasy.

Kiedy definicja klasy się kończy, tworzony jest obiekt klasy. Mówiąc wprost, jest rodzaj opakowania dla przestrzeni nazw stworzonej przez definicję klasy -- o obiektach klasy dowiemy się nieco więcej w dalszej części rozdziału. Zasięg pierwotny (ten, który obowiązywał przed rozpoczęciem definicji klasy) jest przywracany, a nowo utworzony obiekt klasy powiązany zostaje z jej nazwą, która została podana w nagłówku definicji klasy (w tym przypadku NazwaKlasy).

 
9.3.2 Obiekty klas

Na obiektach klasy można przeprowadzić dwa rodzaje operacji: odniesienia do atrybutów i konkretyzację.

Odniesienie do atrybutu da się wyrazić za pomocą standardowej składni używanej w przypadku odniesień dla wszystkich atrybutów w Pythonie: obiekt.nazwa. Prawidłowymi nazwami atrybutów są nazwy, które istniały w przestrzeni nazw klasy w czasie tworzenia jej obiektu. Tak więc, jeśli definicja klasy wygląda następująco:

class MojaKlasa:
    "Prosta, przykładowa klasa"
    i = 12345
    def f(x):
        return 'witaj świecie'

to MojaKlasa.i i MojaKlasa.f są prawidłowymi odniesieniami do jej atrybutów, których wartością jest odpowiednio liczba całkowita i obiekt metody. Atrybutom klasy można przypisywać wartości, w ten sposób można zmienić wartość MojaKlasa.i poprzez przypisanie. __doc__ jest także prawidłową nazwą atrybutu klasy, którego wartością jest napis dokumentacyjny należący do klasy: "Prosta, przykładowa klasa".

Konkretyzację klasy przeprowadza się używając notacji wywołania funkcji. Należy tylko udać, że obiekt klasy jest bezparametrową funkcją, która zwraca instancję (konkret) klasy. Oto przykład (używa definicji klasy z poprzedniego ćwiczenia):

x = MojaKlasa()

w którym tworzy się nowy konkret klasy i wiąże się ten obiekt z nazwą zmiennej lokalnej x poprzez przypisanie do niej.

Operacja konkretyzacji (,,wywołanie'' obiektu klasy) tworzy pusty obiekt. Dla wielu klas występuje konieczność stworzenia swojego konkretu w pewnym znanym, początkowym stanie. Dlatego też, można zdefiniować dla klas specjalną metodę o nazwie __init__(), tak jak poniżej:

    def __init__(self):
        self.dane = []

W momencie konkretyzacji klasy, automatycznie wywołana zostanie metoda __init__() dla nowopowstałego konkretu klasy.

Tak więc, w tym przykładzie, nowy, zainicjalizowany konkret klasy można uzyskać poprzez:

x = MojaKlasa()

Oczywiście, dla zwiększenia wygody w użyciu, metoda __init__() może posiadać argumenty. W tym przypadku, argumenty dane na wejściu operatora konkretyzacji klasy, przekazywane są do __init__(). Na przykład:

>>> class Zespolona:
...     def __init__(self, rzeczywista, urojona):
...         self.r = rzeczywista
...         self.i = urojona
...
>>> x = Zespolona(3.0,-4.5)
>>> x.r, x.i
(3.0, -4.5)

 
9.3.3 Obiekty konkretu klasy

No dobrze, więc co można zrobić z obiektem konkretu klasy? Jedyna operacją zrozumiałą dla obiektu konkretu klasy jest odniesienie do jego atrybutów. Istnieją dwa rodzaje prawidłowych nazw atrybutów takiego obiektu.

Pierwszy z nich nazywany jest przeze mnie atrybutami danych. Są to odpowiedniki «zmiennych konkretu klasy» w Smalltalku i «danymi składowymi» w C++. Atrybuty danych nie muszą być deklarowane. Podobnie jak zmienne lokalne, są one tworzone w momencie pierwszego przypisania wartości do nich. Jeżeli x jest na przykład konkretem klasy MojaKlasa, to poniższy fragment kodu spowoduje wydrukowanie wartości 16 bez powstania komunikatu o błędzie:

x.licznik = 1
while x.licznik < 10:
    x.licznik = x.licznik * 2
print x.licznik
del x.licznik

Drugim rodzajem odniesienia do atrybutu są metody. Metoda jest funkcją, która ,,należy'' do obiektu konkretu klasy. (W Pythonie, określenie «metoda» nie przynależy tylko i wyłącznie do konkretów klasy: inne typy obiektowe też mogą mieć metody, np. listy mają metody o nazwie append, insert, remove, sort, itd. Jednakże, określenie «metoda» użyte poniżej, odnosi się do funkcji należącej do instancji klasy, chyba, że zaznaczono inaczej.)

Poprawna nazwa metody obiektu konkretu zależy od jego klasy. Z definicji, wszystkie atrybuty klasy, które są funkcjami (zdefiniowanymi przez użytkownika), są poprawnymi nazwami metod konkretu tej klasy. Tak więc, w naszym przykładzie x.f jest funkcją, ale x.i już nie, ponieważ MojaKlasa.i nie jest funkcją. x.f nie jest tym samym co MyClass.f -- jest to  obiekt metody, a nie obiekt funkcji.

 
9.3.4 Obiekty metod

Metodę wywołuje się w następujący sposób:

x.f()

w naszym przykładzie zwróci ona napis "witaj świecie". Nie jest jednakże konieczne wywoływać metodę w ten bezpośredni sposób: x.f jest obiektem i można go zachować i wywołać później, np.:

xf = x.f
while 1:
    print xf()

będzie po wsze czasy drukować "witaj świecie".

Co właściwie dzieje się, gdy wywoływana jest metoda? Można było zauważyć, że x.f() została wywołana bez argumentu, pomimo że definicja funkcji f zawiera jeden argument formalny self. Co się z nim stało? Pewne jest, że Python zgłosi wyjątek, gdy funkcja, która wymaga podania argumentów, wywoływana jest bez nich -- nawet, jeżeli żaden z nich nie jest wewnątrz jej używany...

Odpowiedź jest dość oczywista: jedną ze szczególnych cech metody jest to, że obiekt, na rzecz którego jest wywoływana, przekazywany jest jako pierwszy argument funkcji. W naszym przykładzie, wywołanie x.f() odpowiada dokładnie wywołaniu MyClass.f(x). Ogólnie rzecz biorąc, wywołanie metody z listą n argumentów równoznaczne jest wywołaniem odpowiedniej funkcji klasy z lista argumentów stworzoną poprzez włożenie obiektu konkretu klasy przed pierwszy element (argument wywołania) listy argumentów.

Jeśli w dalszym ciągu nie jest zrozumiałe jak działają metody, spróbujmy spojrzeć na ich implementację, co powinno rozjaśnić nieco to zagadnienie. Gdy używa się odniesienia do atrybutu konkretu klasy, który nie jest atrybutem danych, następuje przeszukanie klasy konkretu. Jeżeli znaleziono nazwę w klasie, która odnosi się do funkcji w niej zdefiniowanej, tworzony jest obiekt metody jako paczka zawierająca odniesienia do obiektu konkretu klasy i obiektu funkcji klasy. Kiedy obiekt metody jest wywoływany z listą argumentów, następuje jego rozpakowanie, tworzona jest nowa lista argumentów z obiektu konkretu klasy i oryginalnej listy argumentów, a następnie obiekt funkcji wywoływany jest z nowo utworzoną listą.

 
9.4 Luźne uwagi

[Ten podrozdział być może powinien być umieszczony gdzieś indziej...]

Nazwy atrybuty danych przesłaniają te same nazwy metod. Przyjęło się używać pewnej konwencji w nazewnictwie, aby uniknąć przypadkowego konfliktu nazw, co może powodować trudne do znalezienia błędy w dużych programach. Na przykład, rozpoczynanie nazw metod od dużej litery, dodawanie przedrostka do nazwy atrybutu danych (zazwyczaj jest to znak podkreślenia, "_") lub używanie czasowników dla metod i rzeczowników dla danych.

Atrybuty danych mogą być użyte w metodach w ten sam sposób, w jaki używane są przez zwykłych użytkowników (,,klientów'') obiektu. Innymi słowy, klasy w Pythonie nie są użyteczne jako środek do implementacji ,,czystych'' abstrakcyjnych typów danych. W rzeczywistości, nic w Pythonie nie umożliwia ukrywania danych -- wszystko to oparte jest na konwencji. (Z drugiej strony, implementacja Pythona, która jest całkowicie napisana w C, może całkowicie ukryć szczegóły implementacyjne i kontrolować zakres dostępu do obiektu, jeśli jest to konieczne. Jest do pewna droga postępowania dla wszelkich rozszerzeń Pythona pisanych w C).

Klienci powinni używać atrybutów danych z pewną dozą ostrożności -- mogą narobić dużo bałaganu w niezmiennikach metod poprzez naruszanie ich atrybutów danych. Proszę zauważyć, że można dodawać nowe atrybuty danych do obiektu konkretu bez naruszania poprawności działania metod pod warunkiem, że uniknięto konfliktu nazw -- trzeba to znów zaznaczyć: trzymanie się konwencji w nazewnictwie może zaoszczędzić bólu głowy w tym względzie.

Nie istnieje skrócony sposób dostępu z wnętrza metody do atrybutów danych (lub innych metod)! Moim zdaniem, zwiększa to czytelność ich kodu: nie ma szansy pomieszania nazw zmiennych lokalnych i zmiennych konkretu podczas przeglądania programu źródłowego.

Przyjęta ogólnie konwencją jest nazywanie pierwszego argumentu metody self.9.4To nic więcej niż konwencja: nazwa self nie ma zupełnie żadnego specjalnego znaczenia w Pythonie (proszę jednak zauważyć, że nie przestrzeganie tej konwencji może spowodować powstanie mniej czytelnego kodu oraz to, że niektóre narzędzia służące do przeglądania definicji klas mogą polegać właśnie na tej konwencji)9.5

Każdy obiekt funkcji, który jest atrybutem klasy definiuje metodę dla konkretu tej klasy. Nie jest konieczne aby definicja takiej funkcji była tekstowo umieszczona w definicji jej klasy: przypisanie obiektu funkcji do lokalnej zmiennej w klasie powoduje to samo jakby była ona tam umieszczona. Na przykład:

# Funkcja zdefiniowana poza obrębem klasy
def f1(self, x, y):
    return min(x, x+y)

class C:
    f = f1
    def g(self):
        return 'witaj świecie'
    h = g

W tym wypadku f, g i h są wszystkie atrybutami klasy C, których wartością są obiekty funkcji i w konsekwencji stają się metodami konkretów klasy C -- h jest dokładnie odpowiednikiem g. Proszę zauważyć, że w praktyce taka kombinacja służy jedynie do wprowadzenia czytelnika takiego programu w stan głębokiego zmieszania i frustracji.

Metody mogą wywoływać inne metody poprzez użycie atrybutów metod obiektu self, np.:

class Worek:
    def __init__(self):
        self.dane = []
    def dodaj(self, x):
        self.dane.append(x)
    def dodaj_dwa_razy(self, x):
        self.dodaj(x)
        self.dodaj(x)

Metody mogą używac odniesień do nazw globalnych w ten sam sposób jak zwykłe funkcje. Zasięg globalny związany z metodą jest po prostu tym modułem, który zawiera definicje klasy (sam klasa nigdy nie jest używana jako zasięg globalny). Zazwyczaj rzadko można spotkać odniesienia do zmiennych globalnych w metodach. Istnieje parę dobrych powodów do używania zmiennych globalnych: funkcje i moduły importowane do przestrzeni nazw globalnych mogą zostać użyte przez metody, tak samo jak funkcje i klasy należace do tej samej przestrzeni. Zwykle klasa zawierająca tę metodę sama jest zdefiniowana w globalnym zasięgu. W następnym podrozdziale będzie się można dowiedzieć, dlaczego w metodzie trzeba czasami użyć odniesienia do jej własnej klasy!

 
9.5 Dziedziczenie

Bez istnienia mechanizmu dziedziczenia, ta cecha języka, która określana jest mianem ,,klasy'' nie byłaby jego warta. Poniżej podano składnię definicji klasy dziedziczącej:

class NazwaKlasyPotomnej(NazwaKlasyBazowej):
    <instrukcja-1>
    .
    .
    .
    <instrukcja-N>

Nazwa NazwaKlasyBazowej musi być zdefiniowana w zasięgu zawierającym definicję klasy pochodnej. Zamiast nazwy klasy bazowej dopuszcza się również wyrażenie. Jest to szczególnie przydatne, jeśli nazwa klasy bazowej zdefiniowana jest w innym module, np.:

class NazwaKlasyPochodnej(modul.NazwaKlasyBazowej):

Wykonanie definicji klasy pochodnej następuje w taki sam sposób jak dla klasy bazowej. Klasa jest zapamiętywana w momencie stworzenia obiektu klasy. Ten mechanizm używany jest w procesie rozstrzygania odniesień do atrybutów konkretu takiej klasy: jeśli poszukiwany atrybut nie jest znajdowany w klasie, poszukiwany jest w klasie bazowej. Ta zasada stosowana jest rekurencyjnie w przypadku, gdy klasa bazowa jest pochodną innej.

W konkretyzacji takiej klasy nie ma nic szczególnego: NazwaKlasyPochodnej() tworzy nowy konkret klasy. Odniesienia do metod rozstrzygane są w następujący sposób: poszukuje się odpowiedniego atrybutu klasy, jeśli jest to konieczne, schodzi się z poszukiwaniem w głąb drzewa dziedziczenia. Gdy odniesienie wskazuje na obiekt funkcji, metoda uznawana jest za poprawną.

Klasy pochodne mogą przesłaniać metody ich klas bazowych. Metody nie są obsługiwane w żaden uprzywilejowany sposób, gdy wywołują inne metody tego samego konkretu klasy, a więc metoda klasy bazowej, która wywołuje metodę zdefiniowaną w tej samej klasie może uruchomić metodę swojej klasy pochodnej, która tamtą przesłania (uwaga dla programistów C++: wszystkie metody w Pythonie zachowują sie jak wirtualne).

Przesłonięcie metody w klasie pochodnej jest raczej rozszerzeniem zbioru obsługiwanych funkcji niż zastąpieniem elementu noszącego taką samą nazwę jak ta metoda. Istnieje prosty sposób wywołania metody klasy bazowej: po prostu "NazwaKlasyPodstawowej.nazwa_metody(self, argumenty)". Ten mechanizm można stosować, jeżeli klasa podstawowa zdefiniowana jest w globalnym zasięgu nazw.

 
9.5.1 Dziedziczenie wielorakie

W Pythonie występuje ograniczona forma dziedziczenia wielorakiego. Poniżej podano przykład definicji klasy dziedziczącej z wielu klas podstawowych:

class NazwaKlasyPochodnej(Bazowa1, Bazowa2, Bazowa3):
    <instrukcja-1>
    .
    .
    .
    <instrukcja-N>

Aby wyjaśnić semantykę dziedziczenia wielorakiego w Pythonie, konieczne jest poznanie zasady znajdowania odniesień atrybutów klasy. Jest to zasada ,,najpierw w głąb, potem na prawo''. Jeśli atrybut nie zostanie znaleziony w klasie NazwaKlasyPochodnej, zostanie poszukany w Bazowa1, a potem (rekurencyjnie) w klasach bazowych klasy Bazowa1 i jeśli tam nie zostanie znaleziony, poszukiwanie zostanie przeniesione do klasy Bazowa2.

(Niektórym bardziej naturalne wydaje się być poszukiwanie nazw ,,w szerz'' -- przeszukiwanie Bazowa2 i Bazowa3 przed przeszukaniem klas bazowych klasy Bazowa1. Wymaga to jednak znajomości miejsca zdefiniowania konkretnego atrybutu (czy jest on zdefiniowany w Bazowa1 lub w jednej z jej klas bazowych), zanim można by wywnioskować konsekwencje konfilktu nazw atrybutu z klasy Bazowa2. Poszukiwanie ,,najpierw w głąb'' nie rozróżnia atrybutów zdefiniowanych bezpośrednio od dziedziczonych).

Nieograniczone użycie dziedziczenia wielorakiego może w oczywisty sposób stać się koszmarem w procesie pielęgnacji oprogramowania, zwłaszcza że w Pythonie unikanie konfliktów nazw opiera się na umowie. Jednym ze bardziej znanych przykładów problemu z dziedziczeniem wielorakim jest sytuacja gdy dwie klasy bazowe dziedziczą z tej samej klasy-przodka.9.6. Nie wiadomo jak bardzo taka semantyka jest użyteczna w połączeniu z faktem, że mamy pojedynczą kopię ,,zmiennych konkretu'' lub atrybutów danych wspólnej klasy bazowej.

 
9.6 Zmienne prywatne

Python posiada ograniczony mechanizm implementacji zmiennych prywatnych klasy. Każdy identyfikator o nazwie __pomyje (przynajmniej dwa poprzedzające znaki podkreślenia i co najwyżej jeden znak podkreślenia zakończający nazwę) jest zastępowany przez _nazwaklasy__pomyje, gdzie nazwaklasy jest nazwą bieżącej klasy z usuniętymi znakami podkreślenia9.7. Kodowanie to występuje zawsze, bez względu na pozycję składniową identyfikatora. Może to zostać użyte do zdefiniowania prywatnych konkretów klasy i zmiennych klasowych, metod, jak również obiektów globalnych, a nawet do przechowywania zmiennych konkretów tej klasy w konkretach innych klas. Ucięcie nazwy może wystąpić po przekroczeniu przez identyfikator długości 255 znaków. Poza granicami klasy, lub gdy nazwa klasy składa się tylko ze znaków podkreślenia, proces takiego kodowania nigdy nie zachodzi.

Kodowanie nazw ma na celu uzyskanie łatwego sposobu definiowania ,,prywatnych'' zmiennych konkretu oraz metod bez martwienia się o zmienne konkretu zdefiniowane przez inne klasy pochodne, lub mucking with instance variables by code outside the class. Trzeba zauważyć, że zasady kodowania nazw przeznaczone są głównie w celu uniknięcia nieprzyjemnych wypadków -- dla zdeterminowanego programisty wciąż możliwe jest uzyskanie bezpośredniego dostępu do do zmiennej uważaną za prywatną. Jest to nawet szczególnie użyteczne np. w przypadku debuggerów. Jest to właściwie jedyny powód, dla którego ta dziura w bezpieczeństwie nie została załatana (Pluskwa: dziedziczenie klasy o nazwie takiej samej jak jedna z klas bazowych powoduje bezpośredni dostęp do zmiennych prywatnych klasy bazowej).

Kod przekazany instrukcji exec i funkcji eval() lub evalfile() nie zawiera nazwy bieżącej klasy; this is similar to the effect of the global statement, the effect of which is likewise restricted to code that is byte-compiled together. To samo ograniczenie dotyczy getattr(), setattr() i delattr() oraz odwołań do kluczy słownika __dict__.

Poniżej przedstawiono przykład klasy, która implementuje swoje wersje metod __getattr__ i __setattr__. Za ich pomocą wszystkie atrybuty konkretu przechowywane są w zmiennej prywatnej (przypomina to sposób działania Pythona 1.4 i poprzednich wersji):

class VirtualAttributes:
    __vdict = None
    __vdict_name = locals().keys()[0]
     
    def __init__(self):
        self.__dict__[self.__vdict_name] = {}
    
    def __getattr__(self, name):
        return self.__vdict[name]
    
    def __setattr__(self, name, value):
        self.__vdict[name] = value

 
9.7 Sztuczki i chwyty

Czasami potrzebe jest użycie typu danych spotykanego w Pascalu pod nazwąi rekordu lub struktury w C. Trzeba po prostu zgrupować pewną liczbę nazwanych elemtów w jednym ,,zasobniku''. W prosty sposób da to się wyrazić za pomocą pustej definicji klasy, tzn.:

class Pracownik:
    pass

janek = Pracownik() # Tworzy pusty rekord pracownika

# Wypełnienie pól w rekordzie
janek.nazwa = 'Janek Kos'
janek.miejsce = 'laboratorium komputerowe'
janek.pensja = 1000

We fragmencie pythonowatego kodu, który wymaga użycia szczególnego abstrakcyjnego typu danych, można skorzystać z jakiejś klasy, która emuluje metody wymaganego typu. Na przykład, jeśli istnieje funkcja, która formatuje dane na wyjściu jakiegoś pliku, to można zdefiniować klasę z metodami read() i readline), które pobierają dane z łańcucha znaków zamiast z pliku i przekazać ją do danej funkcji.

Metody konkretu klasy również posiadają swoje atrybuty: m.im_self jest obiektem (wskazanie na), dla którego dana metoda ma zostać wywołana, a m.im_func jest obiektem funkcji, która implementuje daną metodę.

 
9.7.1 Wyjątki mogą być klasami

Wyjątki definiowane przez użytkownika nie są już ograniczone tylko do obiektów napisów -- można również użyć klas. Poprzez użycie tego mechanizmu można stworzyć rozszerzalna hierarchię wyjątków.

Istnieją dwie poprawne formy instrukcji zgłoszenia wyjątku:

raise Klasa, konkret

raise konkret

W pierwszej formie, konkret musi być konkretem klasy Klasa lub klasy pochodnej od niej. Druga forma jest skróconym zapisem dla:

raise konkret.__class__, konkret

Klauzula except instrukcji try może zawierać również listę klas. Klasa w tej klauzuli odpowiada zgłoszonemu wyjątkowi, jeśli jest tej samej klasy co wyjątek lub jego klasą bazową (i nie inaczej -- lista klas pochodnych w klauzuli except nie odpowiada wyjątkom, które są ich klasami bazowymi). Na przykład, wynikiem działania poniższego kodu będzie wyświetlenie B,C,D w takim właśnie porządku:

class B:
    pass
class C(B):
    pass
class D(C):
    pass

for c in [B, C, D]:
    try:
        raise c()
    except D:
        print "D"
    except C:
        print "C"
    except B:
        print "B"

Trzeba zauważyć, że jeżeli klauzula except byłaby odwrócona (tzn. "except B" na pierwszym miejscu), program wyświetliłby B, B, B -- uruchamiany jest kod pierwzej pasującej klauzuli.

Gdy wyświetlany jest komunikat o błędzie w przypadku niewyłapanego wyjątku klasowego, wyświetlana jest nazwa klasy, potem dwukropek i spacja, a następnie konkret klasy wyjątku przekształcony do napisu za pomocą funkcji wbudowanej str().



... nazw!9.1
Z wyjątkiem jednej rzeczy. Obiekty modułów posiadają tajemniczy tylko atrybut do odczytu: __dict__, któremu przypisany jest słownik użyty do zaimplementowania przestrzeni nazw. Nazwa __dict__ jest atrybutem, ale nie nazwą globalną. To oczywiste, że używanie tej nazwy jest pogwałceniem zasad implementacji przestrzeni nazw i powinno być ograniczone tylko do przypadków śledzenia programu typu ,,post-mortem''.
... niekwalifikowane9.2
tzn. nie trzeba podawać nazwy modułu, klasy, obiektu wraz z kropką .
... metod9.3
czyli funkcji zdefiniowanych dla obiektów klas .
...self.9.4
self można przetłumaczyć w tym kontekście jako «ja sam» lub «moja,mój». Dwa ostatnie określenia być może są bardziej adekwatne jeżeli weźmiemy pod uwagę, że zaraz pod self następuje odniesienie do atrybutu konkretu, np. "self.atrybut" .
... konwencji)9.5
Dlatego też zrezygnowałem z tłumaczenia tej nazw w przykładach na słowo takie jak «ja», albo «się» .
... klasy-przodka.9.6
Zwłaszcza w C++...
... podkreślenia9.7
Jeśli takie występują
Zajrzyj do Informacji na temat tej publikacji... aby pomóc w jej rozwoju.