Zanurkuj w Pythonie/Klasa opakowująca UserDict
Klasa opakowująca UserDict
Edytuj
Wrócimy na chwilę do przeszłości. Za czasów, kiedy nie można było dziedziczyć wbudowanych typów danych np. słownika, powstawały tzw. klasy opakowujące, które pełniły te same funkcję, co typy wbudowane, ale można je było dziedziczyć. Klasą opakowującą dla słownika była klasa UserDict
, która nadal jest dostępna wraz z nowymi wersjami Pythona. Przyjrzenie się implementacji tej klasy może być dla nas cenną lekcją. Zatem zajrzyjmy do kodu źródłowego klasy UserDict
, który znajdują się w module UserDict
. Moduł ten z kolei jest przechowywany w katalogu lib instalacji Pythona, a pełna nazwa pliku to UserDict.py (nazwa modułu z rozszerzeniem .py).
W IDE ActivePython na Windowsie możemy szybko otworzyć dowolny moduł, który znajduje się w ścieżce do bibliotek, gdy wybierzemy File->Locate... (Ctrl-L). |
UserDict
class UserDict: #(1)
def __init__(self, dict=None): #(2)
self.data = {} #(3)
if dict is not None: self.data.update(dict) #(4) (5)
- Klasa
UserDict
nie dziedziczy nic z innych klas. Jednak nie patrzmy się na to, pamiętajmy, żeby zawsze dziedziczyć zobject
(lub innego wbudowanego typu), bo wtedy mamy dostęp do dodatkowych możliwości, które dają nam klasy w nowym stylu. - Jak pamiętamy, metoda
__init__
jest wywoływana bezpośrednio po utworzeniu instancji klasy. Przy tworzeniu instancji klasyUserDict
możemy zdefiniować początkowe wartości, poprzez przekazanie słownika w argumenciedict
. - W Pythonie możemy tworzyć atrybuty danych (zwane polami w Javie i PowerBuilderze). Atrybuty to kawałki danych przechowywane w konkretnej instancji klasy (moglibyśmy je nazwać atrybutami instancji). W tym przypadku każda instancja klasy
UserDict
będzie posiadać atrybutdata
. Aby odwołać się do tego pola z kodu spoza klasy, dodajemy z przodu nazwę instancji np.instancja.data
; robimy to w identyczny sposób, jak odwołujemy się do funkcji poprzez nazwę modułu, w którym ta funkcja się znajduje. Aby odwołać się do atrybutu danych z wnętrza klasy, używamyself
. Zazwyczaj wszystkie atrybuty są inicjalizowane sensownymi wartościami już w metodzie__init__
. Jednak nie jest to wymagane, gdyż atrybuty, podobnie jak zmienne lokalne, są tworzone, gdy po raz pierwszy przypisze się do nich jakąś wartość. - Metoda
update
powiela zachowanie metody słownika: kopiuje wszystkie klucze i wartości z jednego słownika do drugiego. Metoda ta nie czyści słownika docelowego (tego, z którego wywołaliśmy metodę), ale jeśli były tam już jakieś klucze, to zostaną one nadpisane tymi, które są w słowniku źródłowym; pozostałe klucze nie zmienią się. Myślmy oupdate
jak o funkcji łączenia, nie kopiowania. - Z tej składni nie korzystaliśmy jeszcze w tej książce. Jest to instrukcja
if
, ale zamiast wciętego bloku, który rozpoczynałby się w następnej linii, korzystamy tu z instrukcji, która znajduje się w jednej linii, zaraz za dwukropkiem. Jest to całkowicie poprawna, skrótowa składnia, której możemy używać, jeśli mamy tylko jedną instrukcję w bloku (tak jak pojedyncza instrukcja bez klamer w C++). Możemy albo skorzystać z tej skrótowej składni, albo tworzyć wcięte bloki, jednak nie możemy ich ze sobą łączyć w odniesieniu do tego samego bloku kodu.
Java i Powerbuilder mogą przeciążać funkcje mające różne listy argumentów, na przykład klasa może mieć kilka metod z taką samą nazwą, ale z różną liczbą argumentów lub z argumentami różnych typów. Inne języki (na przykład PL/SQL) obsługują nawet przeciążanie funkcji, które różnią się jedynie nazwą argumentu np. jedna klasa może mieć kilka metod z tą samą nazwą, tą samą liczbą argumentów o tych samych typów, ale inaczej nazwanych. Python nie ma żadnej z tych możliwości, nie ma tu w ogóle przeciążania funkcji. Metody są jednoznacznie definiowane przez ich nazwy i w danej klasie może być tylko jedna metoda o danej nazwie. Jeśli więc mamy w jakiejś klasie potomnej metodę __init__ , to zawsze zasłoni ona metodę __init__ klasy rodzicielskiej, nawet jeśli klasa pochodna definiuje ją z innymi argumentami. Ta uwaga stosuje się do wszystkich metod.
|
Guido, pierwszy twórca Pythona, tak wyjaśnia zasłanianie funkcji: "Klasy pochodne mogą zasłonić metody klas bazowych. Ponieważ metody nie mają żadnych specjalnych przywilejów, kiedy wywołujemy inne metody tego samego obiektu, może okazać się, że metoda klasy bazowej wywołująca inną metodę zdefiniowaną w tej samej klasie bazowej wywołuje właściwie metodę klasy pochodnej, która ją zasłania. (Dla programistów C++: wszystkie metody w Pythonie zachowują się tak, jakby były wirtualne.)" Jeśli dla Ciebie nie ma to sensu, możesz to zignorować. Po prostu warto było o tym wspomnieć. |
Uwaga!
Zawsze przypisujmy wartości początkowe wszystkim zmiennym obiektu w jego metodzie |
UserDict
def clear(self): self.data.clear() #(1)
def copy(self): #(2)
if self.__class__ is UserDict: #(3)
return UserDict(self.data)
import copy #(4)
return copy.copy(self)
def keys(self): return self.data.keys() #(5)
def items(self): return self.data.items()
def values(self): return self.data.values()
clear
jest normalną metodą klasy; jest dostępna publicznie i może być wołana przez kogokolwiek w dowolnej chwili. Zauważmy, że wclear
, jak we wszystkich metodach klas, pierwszym argumentem jestself
. (Pamiętajmy, że nie dodajemyself
, gdy wywołujemy metodę; Python robi to za nas.) Zwróćmy uwagę na podstawową cechę tej klasy opakowującej: przechowuje ona prawdziwy słownik w atrybuciedata
i definiuje wszystkie metody wbudowanego słownika, a w każdej z tych metod zwraca wynik identyczny do odpowiedniej metody słownika. (Gdybyśmy zapomnieli, metoda słownikaclear
czyści cały słownik kasując jego wszystkie klucze i wartości.)- Metoda słownika o nazwie
copy
zwraca nowy słownik, który jest dokładną kopią oryginału (mający takie same pary klucz-wartość). Natomiast klasaUserDict
nie może po prostu wywołaćself.data.copy
, ponieważ ta metoda zwraca wbudowany słownik, a my chcemy zwrócić nową instancję klasy tej samej klasy, jaką maself
. - Używamy atrybutu
__class__
, żeby sprawdzić, czyself
jest obiektem klasyUserDict
; jeśli tak, to jesteśmy w domu, bo wiemy, jak zrobić kopięUserDict
: tworzymy nowy obiektUserDict
i przekazujemy mu słownik wyciągnięty zself.data
, a wtedy możemy od razu zwrócić nowy obiektUserDict
nie wykonując nawet instrukcjiimport copy
z następnej linii. - Jeśli
self.__class__
nie jestUserDict
-em, toself
musi być jakąś podklasąUserDict
-a, a w takim przypadku życie wymaga użycia pewnych trików.UserDict
nie wie, jak utworzyć dokładną kopię jednego ze swoich potomków. W tym celu możemy np. znając atrybuty zdefiniowane w podklasie, wykonać na nich pętlę, podczas której kopiujemy każdy z tych atrybutów. Na szczęście istnieje moduł, który wykonuje dokładnie to samo, nazywa się oncopy
. Nie będziemy się tutaj wdawać w szczegóły (choć jest to wypaśny moduł, jeśli się w niego trochę wgłębimy). Wystarczy wiedzieć, żecopy
potrafi kopiować dowolne obiekty, a tu widzimy, jak możemy z niego skorzystać. - Pozostałe metody są bezpośrednimi przekierowaniami, które wywołują wbudowane metody z
self.data
.
Uwaga!
Od Pythona 2.2 nie korzystamy z klasy |
Materiały dodatkoweEdytuj
- Python Library Reference dokumentuje moduł
copy