Zanurkuj w Pythonie/Klasa opakowująca UserDict: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
Sqrll (dyskusja | edycje)
Piotr (dyskusja | edycje)
Nie podano opisu zmian
Linia 1:
== Klasa opakowująca <ttcode>UserDict</ttcode> ==
{{Podświetl|py}}
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 <code>UserDict</code>, która nadal jest dostępna wraz z nowymi wersjami Pythona. Przyglądnięcie się implementacji tej klasy może być dla nas cenną lekcją. Zatem zaglądnijmy do kodu źródłowego klasy <code>UserDict</code>, który znajdują się w module <code>UserDict</code>. Moduł ten z kolei jest przechowywany w katalogu <tt>lib</tt> instalacji Pythona, a pełna nazwa pliku to <tt>UserDict.py</tt> (nazwa modułu z rozszerzeniem <tt>.py</tt>).
== Klasa opakowująca <tt>UserDict</tt> ==
 
Klasa <tt>FileInfo</tt> działa podobnie do słownika, o czym mogliśmy się już wcześniej przekonać. Aby ją lepiej zgłębić przyjrzyjmy się klasie <tt>UserDict</tt> w module <tt>UserDict</tt>, która jest przodkiem klasy <tt>FileInfo</tt>. Nie ma w niej nic specjalnego; jest to klasa napisana w Pythonie i przechowywana w pliku o rozszerzeniu <tt class="lang-none">.py</tt> jak każdy inny kod Pythona. Moduł ten jest przechowywany w katalogu <tt>lib</tt> twojej instalacji Pythona.
 
{{Infobox|
W <tt>IDE ActivePython na Windowsie</tt> możeszmożemy szybko otworzyć dowolny moduł, znajdującyktóry znajduje się w twojej ścieżce do bibliotek, wybierającgdy ''wybierzemy <tt>File->Locate... (Ctrl-L)''</tt>.
}}
 
{{Python/Przykład
'''Przykład |5.9. |Definicja klasy <ttcode>UserDict</ttcode>'''
|tekst=
<nowiki>class UserDict: #(1)
def __init__(self, filenamedict=None): #(2)
self.data = {} #(3)
if dict is not None: self.update(dict) #(4) (5)</nowiki>
 
# Klasa <code>UserDict</code> nie dziedziczy nic z innych klas. Jednak nie patrzmy się na to, pamiętajmy, żeby zawsze dziedziczyć z <code>object</code> (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 <code>__init__</code> jest wywoływana bezpośrednio po utworzeniu instancji klasy. Przy tworzeniu instancji klasy <code>UserDict</code> możemy zdefiniować początkowe wartości, poprzez przekazanie słownika w argumencie <code>dict</code>.
class UserDict: #(1)
# PythonW wspieraPythonie 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 <ttcode>UserDict</ttcode> będzie posiadać atrybut <ttcode>data</ttcode>. ByAby odwołać się do tego pola z kodu spoza klasy, dodajemy z przodu nazwę instancji -np. <code>instancja.data,</code>; wrobimy tento samw identyczny sposób, jak odwołujemy się do funkcji poprzez nazwę modułu, w jakimktórym ta funkcja się znajduje. ByAby odwołać się do atrybutu danych z wnętrza klasy, używamy <ttcode>self</ttcode>. Zazwyczaj wszystkie atrybuty są inicjalizowane sensownymi wartościami już w metodzie <ttcode>__init__</ttcode>. Jednak nie jest to wymagane, gdyż atrybuty, takpodobnie jak zmienne lokalne, są tworzone, gdy pierwszypo raz przypisuje[[../Deklarowanie zmiennych|pierwszy przypisze się do nich jakąś wartość]].
def __init__(self, dict=None): #(2)
# metodaMetoda <ttcode>update</ttcode> jestpowiela zachowanie duplikatoremmetody 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, ale; pozostałe klucze nie zmienią się. MyślMyślmy o <ttcode>update</ttcode> jak o funkcji łączenia, nie kopiowania.
self.data = {} #(3)
# Z tej składni mogłeś wcześniej nie widziećkorzystaliśmy (nie używałem jejjeszcze w przykładach z tej książki)książce. Jest to instrukcja <ttcode>if</ttcode>, ale zamiast wciętego bloku, zaczynającegoktóry rozpoczynałby się w następnej linii, jestkorzystamy tu pojedynczaz instrukcjainstrukcji, która znajduje się w tej samejjednej linii, zaraz za dwukropkiem. Jest to całkowicie poprawna składnia, będącaskrótowa tylko skrótemskładnia, któregoktórej możeszmożemy używać, jeśli maszmamy tylko jedną instrukcję w bloku (tak jak pojedyncza instrukcja bez klamer w C++). MożeszMożemy użyćalbo skorzystać z tej skrótowej składni, albo wciętegotworzyć koduwcięte w następnych liniachbloki, alejednak nie możeszmożemy użyćich obuze składnisobą łączyć w tymodniesieniu do tego samymsamego bloku kodu.
if dict is not None: self.update(dict) #(4) (5)
}}
 
# Klasa <tt>UserDict</tt> jest klasą bazową, nie dziedziczy nic z innych klas.
# Metodę <tt>__init__</tt> nadpisaliśmy w klasie <tt>FileInfo</tt>. Zauważmy, że lista argumentów w klasie przodka jest inna niż w klasie potomka. To jest ok. Każda podklasa może mieć własny zbiór argumentów, pod warunkiem, że metody przodka będą wywoływane z poprawnymi argumentami. Tutaj klasa przodka posiada możliwość zdefiniowania początkowych wartości (dzięki przekazaniu słownika w argumencie <tt>dict</tt>), której klasa <tt>FileInfo</tt> nie wykorzystuje.
# Python wspiera atrybuty (zwane polami w Javie i PowerBuilderze). Atrybuty to kawałki danych przechowywane w konkretnej instancji klasy. W tym przypadku, każda instancja klasy <tt>UserDict</tt> będzie posiadać atrybut <tt>data</tt>. By odwołać się do tego pola z kodu spoza klasy, dodajemy z przodu nazwę instancji - instancja.data, w ten sam sposób jak odwołujemy się do funkcji poprzez nazwę modułu w jakim się znajduje. By odwołać się do atrybutu z wnętrza klasy, używamy <tt>self</tt>. Zazwyczaj wszystkie atrybuty są inicjalizowane sensownymi wartościami w metodzie <tt>__init__</tt>. Jednak nie jest to wymagane, gdyż atrybuty tak jak zmienne lokalne są tworzone gdy pierwszy raz przypisuje się do nich wartość.
# metoda <tt>update</tt> jest duplikatorem słownika: kopiuje wszystkie klucze i wartości z jednego słownika do drugiego. Metoda ta nie czyści słownika docelowego; jeśli były tam już jakieś klucze, to zostaną one nadpisane tymi, które są w słowniku źródłowym, ale pozostałe nie zmienią się. Myśl o <tt>update</tt> jak o funkcji łączenia, nie kopiowania.
# tej składni mogłeś wcześniej nie widzieć (nie używałem jej w przykładach z tej książki). Jest to instrukcja <tt>if</tt> ale zamiast wciętego bloku zaczynającego się w następnej linii jest tu pojedyncza instrukcja w tej samej linii, za dwukropkiem. Jest to całkowicie poprawna składnia, będąca tylko skrótem, którego możesz używać, jeśli masz tylko jedną instrukcję w bloku (tak jak pojedyncza instrukcja bez klamer w C++). Możesz użyć tej składni albo wciętego kodu w następnych liniach, ale nie możesz użyć obu składni w tym samym bloku kodu.
 
{{Infobox|
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, różniącychktóre różnią się jedynie nazwą argumentu np. Na przykład 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 metodamamy potomnaw majakiejś klasęklasie potomnej metodę <ttcode>__init__</ttcode>, to zawsze zasłoni ona metodę <ttcode>__init__</ttcode> klasy-przodka nawetrodzicielskiej, nawet jeśli klasa pochodna definiuje ją z innymi argumentami. Ta uwaga stosuje się do wszystkich metod.}}
 
{{Infobox|
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 wołającwywołujemy inne metody tego samego obiektu, może okazać się, że metoda klasy bazowej wołającawywołująca inną metodę zdefiniowaną w tej samej klasie bazowej woławywoł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 (dla mnie osobiście jest to strasznie zagmatwane), możesz to zignorować. Uważałem,Po żeprostu warto należybyło o tym wspomnieć.
}}
 
{{Uwaga|
Zawsze przypisujprzypisujmy wartośćwartości początkowąpoczątkowe wszystkim zmiennym obiektu w jego metodzie <ttcode>__init__</ttcode>. Oszczędzi to godzin debugowania w poszukiwaniu wyjątków <ttcode>AtributeError</ttcode>, powodowanych przezktóre odwołaniasą spowodowane odwałaniami do niezainicjalizowanych (czyli nieistniejących) atrybutów.
}}
 
{{Python/Przykład
'''Przykład |5.10. |Standardowe metody klasy <ttcode>UserDict</ttcode>'''
 
|tekst=
<nowiki>def clear(self): self.data.clear() #(1)
def copy(self): #(2)
if self.__class__ is UserDict: #(3)
Linia 40 ⟶ 43:
def keys(self): return self.data.keys() #(5)
def items(self): return self.data.items()
def values(self): return self.data.values()</nowiki>
 
# <ttcode>clear</ttcode> jest normalną metodą klasy; jest dostępna publicznie i może być wołana przez kogokolwiek w dowolnej chwili. ZauważZauważmy, że w <ttcode>clear</ttcode>, jak wszystkiewe metodywszystkich metodach klas, mapierwszym argumentem jest <ttcode>self</ttcode> jako pierwszy argument. (PamiętajPamiętajmy, że nie dodajeszdodajemy <ttcode>self</ttcode>, gdy wywołujeszwywołujemy metodę; Python robi to za Ciebienas.) ZauważZwróćmy podstawowąuwagę na odstawową cechę tej opakowującej klasy opakowującej: przechowuje ona prawdziwy słownik (w atrybucie <ttcode>data</ttcode>) jako atrybut danych i definiuje wszystkie metody, którewbudowanego ma prawdziwy słowniksłownika, a w każdej z tych metod zwraca wynik identyczny do odpowiedniej metody słownika. (GdybyśGdybyśmy zapomniałzapomnieli, metoda słownika <ttcode>clear</ttcode> słownikaczyści cały słownik kasujekasując jego wszystkie klucze i wartości.)
# metodaMetoda słownika o nazwie <ttcode>copy</ttcode> słownika zwraca nowy słownik, będącyktóry jest dokładną kopią oryginału (mający takie same pary klucz-wartość). AleNatomiast klasa <ttcode>UserDict</ttcode> nie może po prostu wywołać <ttcode>self.data.copy</ttcode>, ponieważ ta metoda zwraca wbydowany słownik, a to comy chcemy tuzwrócić zrobić,nową toinstancję zwrócenieklasy nowejtej instancjisamej klasy takiej, jakjaką ma <ttcode>self</ttcode>.
# Używamy atrybutu <ttcode>__class__</ttcode>, żeby sprawdzić, czy <ttcode>self</ttcode> jest obiektem klasy <ttcode>UserDict</ttcode>; jeśli tak, jesteśto jesteśmy w domu, bo wieszwiemy, jak zrobić kopię <ttcode>UserDict</ttcode>: stwórztworzymy nowy obiekt <ttcode>UserDict</ttcode> i przekażprzekazujemy mu słownik wyciągnięty z <ttcode>self.data</ttcode>., a Wtedywtedy możeszmożemy od razu zwrócić nowy obiekt <ttcode>UserDict</ttcode> nie wykonując nawet instrukcji <ttcode>import copy</ttcode> z następnej linii.
# Jeśli <ttcode>self.__class__</ttcode> nie jest <ttcode>UserDict</ttcode>-em, to <ttcode>self</ttcode> musi być jakąś podklasą <ttcode>UserDict</ttcode>-a, (jaka byćw możetakim <tt>FileInfo</tt>)przypadku <!--życie inwymaga whichużycia casepewnych life gets trickier -->trików. <ttcode>UserDict</ttcode> 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 iteracjępętlę, podczas której kopiująckopiujemy każdy z tych atrybutów. Na szczęście istnieje moduł, który wykonuje dokładnie to samo, nazywa się on <ttcode>copy</ttcode>. Nie będziemy się tutaj wdawać w szczegóły (choć jest to niezły (cool)wypaśny moduł, jeśli wgłębisz się w niego samemutrochę wgłębimy). Wystarczy wiedzieć, że <ttcode>copy</ttcode> potrafi kopiować dowolne obiekty, a tu widzimy, jak możemy z niego skorzystać.
# Pozostałe metody są bezpośrednimi przekierowaniami, wywołującymiktóre wywołują wbudowane metody naz <ttcode>self.data</ttcode>.
}}
 
{{InfoboxUwaga|
Od Pythona 2.2 nie korzystamy z klasy <code>UserDict</code>, ponieważ od tej wersji możemy już dziedziczyć z wbudowanych typów danych.
1=W wersjach 2.2 i wcześniejszych nie można było bezpośrenio tworzyć podklas wbudowanych typów jak napisy, listy czy słowniki. Aby to zrekompensować, Python dostarcza klasy opakowujące, które naśladują zachowanie wbudowanych typów danych. Są to: <tt>UserString</tt>, <tt>UserList</tt> i <tt>UserDict</tt>. Wykorzystując kombinację normalnych i specjalnych metod, klasa <tt>UserDict</tt> doskonale imituje słownik. W Pythonie 2.2 i nowszym możemy dziedziczyć bezpośrednio z wbudowanych typów danych, jak np. <tt>dict</tt>, czyli słownik. Przykład tego możemy znaleźć w przykładach dostarczonych wraz z tą książką, w pliku <tt class="lang-none">fileinfo_fromdict.py</tt>.
}}
 
W Pythonie możemy dziedziczyć bezpośrednio z wbudowanych typów danych, co pokazano w poniższym przykładzie. Tutaj mamy trzy różnice w porównaniu z wersją korzystającą z klasy <tt>UserDict</tt>.
 
'''Przykład 5.11. Dziedziczenie bezpośrednio z wbudowanej klasy <tt>dict</tt>'''
 
 
class FileInfo(dict): #(1)
"store file metadata"
def __init__(self, filename=None): #(2)
self["name"] = filename
 
# Pierwsza różnica polega na tym, że nie importujemy modułu <tt>UserDict</tt>, ponieważ <tt>dict</tt> jest wbudowanym typem danych i jest ciągle dostępny. Druga polega na tym, że dziedziczymy bezpośrednio z klasy <tt>dict</tt>, zamiast z klasy <tt>UserDict.UserDict</tt>.
# Trzecia różnica jest subtelna, lecz też bardzo ważna. Z powodu w jaki <tt>UserDict</tt> jest skonstruowany wewnątrz, wymaga on, aby ręcznie wywołać metodę <tt>__init__</tt>, która w odpowiedni sposób zainicjalizuje jego wewnętrzne struktury danych. Z kolei <tt>dict</tt> nie działa w ten sposób. Nie jest to klasa opakowująca i nie wymaga jawnej inicjalizacji.
 
<noinclude>
{{Nawigacja|Python|
[[Python../Tworzenie instancji klasy|Tworzenie instancji klasy/]]|
[[Python../Metody specjalne|Metody specjalne/]]|
}}
</noinclude>