Zanurkuj w Pythonie/Obiekty i klasy: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
Rofrol (dyskusja | edycje)
mNie podano opisu zmian
Piotr (dyskusja | edycje)
mNie podano opisu zmian
Linia 135:
year=2000
comment=http://mp3.com/artists/95/vxp
 
== Importowanie modułów za pomocą ''from module import'' ==
 
W Pythonie mamy dwa sposoby importowania modułów. Obydwa są przydatne, dlatego też powinniśmy umieć je wykorzystać. Jednym ze sposobów jest użycie polecenia <tt>import nazwa_modulu</tt>, który mogliśmy zobaczyć w rozdziale [[Programowanie:Python:Twój pierwszy program#Wszystko jest obiektem|Twój pierwszy program - „Wszystko jest obiektem”]]. Inny sposób realizuje tę samą czynność, ale posiada pewne różnice. Niżej jest prosty przykład wykorzystujący instrukcję <tt>from module import</tt>:
 
from UserDict import UserDict
 
Jak widzimy jest to w składni bardzo podobne do <tt>import nazwa_modulu</tt>, ale z jedną ważną różnicą: atrybuty i metody importowanego modułu są importowane bezpośrednio do lokalnej przestrzeni nazw, tak więc będą dostępne bezpośrednio, bez dodawania kwalifikatora z jakiego modułu korzystamy. Możemy importować określone pozycje lub użyć <tt>from module import *</tt>, aby zaimportować wszystko.
 
{{Infobox|
tekst=<tt>from module import *</tt> w Pythonie jest podobne do <tt>use module</tt> w Perlu; <tt>import module</tt> w Pythonie przypomina <tt>require module</tt> w Perlu.}}
{{Infobox|
tekst=<tt>from module import *</tt> w Pythonie jest analogią do <tt>import module.*</tt> w Javie; <tt>import module </tt> w Pythonie przypomina <tt>import module</tt> w Javie.}}
 
'''Example 5.2. ''import module'' a ''from module import'' '''
 
>>> import types
>>> types.FunctionType #(1)
<type 'function'>
>>> FunctionType #(2)
Traceback (innermost last):
File "<interactive input>", line 1, in ?
NameError: There is no variable named 'FunctionType'
>>> from types import FunctionType #(3)
>>> FunctionType #(4)
<type 'function'>
 
# Moduł <tt>types</tt> nie posiada żadnych metod; on właśnie posiada atrybuty dla każdego typu obiektu w Pythonie. Zauważmy, że atrybut (w tym przypadku FunctionType) musi być poprzedzony nazwą modułu - <tt>types</tt>.
# FunctionType nie został sam w sobie określony w przestrzeni nazw; istnieje on tylko w kontekści modułu <tt>types</tt>.
# Używając tej składni atrybut FunctionType z modułu <tt>types</tt> został zaimportowany bezpośrednio do lokalnej przestrzeni nazw.
# Teraz możemy odwoływać się bezpośrednio do FunctionType, bez odwoływania się do <tt>types</tt>.
 
Kiedy powinniśmy używać <tt>from module import</tt>?
 
* Kiedy często odwołujemy się do atrybutów i metod, a także nie potrzebujemy wpisywać wiele razy nazwy modułu. Wtedy najlepiej wykorzystać <tt>from module import</tt>.
* Jeśli potrzebujemy selektywnie zaimportować tylko kilka atrybutów, metod, powinniśmy wykorzystać <tt>from module import</tt>.
* Jeśli moduł zawiera atrybuty lub metody, które posiadają taką samą nazwę jaka jest w naszym module, powinniśmy wykorzystać <tt>import nazwa_modulu</tt> aby uniknąć konfliktu nazw.
W innych przypadkach jest to kwestia stylu, możemy spojrzeć na kody napisane obydwoma sposobami.
 
{{Infobox|
tekst=Należy używać <tt>from module import *</tt> oszczędnie, ponieważ utrudnia on określenie, skąd pochodzi dana funkcja lub atrybut, dlatego też utrudnia debugowanie (wyszukiwanie błędów w programie).}}
 
== Definiowanie klas ==
 
Python jest całkowicie zorientowany obiektowo: możesz definiować własne klasy, dziedziczyć ze swojej lub jakiejś wbudowanej klasy, a także tworzyć instancję zdefiniowanej przez siebie klasy.
 
Definiowanie klasy w Pythonie jest proste. Podobnie jak z funkcjami, nie używamy odzielnego interfejsu definicji. Ściśle definiujemy klasę i rozpoczynamy kodowanie. Klasa w Pythonie rozpoczyna się zastrzeżonym słowem <tt>class</tt>, po którym następuje nazwa klasy. Technicznie, jest to wszystko, co jest wymagane, ponieważ klasa nie musi być dziedziczona z innej klasy.
 
'''Przykład 5.3. Prosta klasa'''
 
class Nicosc: #(1)
pass #(2) (3)
 
# Nazwa tej klasy to <tt>Nicosc</tt>, która nic nie dziedziczy z innej klasy. Nazwy klas są zazwyczaj pisane przy użyciu wielkich liter np. <tt>KazdeSlowoOdzieloneWielkaLitera</tt>, ale jest to tylko konwencja, nie jest to wymagane.
# Klasa ta nie definiuje żadnej metody, czy atrybutów, lecz żeby kod był zgodny ze składnią Pythona, musimy coś umieści w definicji, tak więc użyliśmy <tt>pass</tt>. Jest to zastrzeżone przez Pythona słowo, które mówi interpreterowi „przejdź dalej, nic tu nie ma”. Instrukacja ta nic nie wykonuje i powinnośmy wstawiać ją, jeśli chcemy aby nasza funkcja lub klasa nic nie posiadała, wykonywała.
# Prawdopodobnie zauważyliście już to, że elementy w klasie są wyszczególnione za pomocą wcięć, podobnie jak kod funkcji, instrukcje warunkowe, pętle itp. Pierwsza nie wcięta instrukcja nie będzie należała do klasy.
 
{{Infobox|
Instrukcja <tt>pass</tt> w Pythonie jest analogiczna do pustego zbioru nawiasów klamrowych ({}) w Javie lub języku C++.
}}
 
Większość klas, do których definiujemy własne metody i atrybuty, dziedziczy z innych klas. Jak przed chwilą mogliśmy zobaczyć, klasa nie musi nic w sobie zawierać, poza nazwą. W szczególności programiści C++ mogą zauważyć, że w Pythonie klasy nie mają wyraźnie sprecyzowanych konstruktorów i destruktorów. Pythonowe klasy mają coś, co nieco przypomina konstruktor - metodę <tt>__init__</tt>.
 
 
'''Przykład 5.4. Definiowanie klasy FileInfo'''
from UserDict import UserDict
class FileInfo(UserDict): #(1)
 
# W Pythonie przodek klasy jest po prostu wyszczególnioniony w nawiasach okrągłych, umieszczonych bezpośrednio po nazwie naszej klasy. Tak więc klasa <tt>FileInfo</tt> dziedziczy klasę <tt>UserDict</tt> (klasa ta jest została zaimportowana z modułu <tt>UserDict</tt>). Klasa UserDict działa podobnie jak słownik, pozwala ona dzidziczyć typ danych jakim jest słownik, a także zmieniać jego zachowanie. (Mamy także podobne klasy - UserList i UserString, które umożliwiają dzidziczenie z list i stringów.) Może być to trochę nie zrozumiałe, ale jeszcze w tym rozdziale lepiej omówimy klasę <tt>UserDict</tt>.
 
{{Infobox|
W Pythonie przodek danej klasy jest wyszczególnioniony w nawiasach okrągłych, umieszczonych bezpośrednio po nazwie naszej klasy. Python nie posiada specjalnego słowa kluczowego jak np. <tt>extends</tt> w Javie.}}
 
Python obsługuje dziedziczenie wielokrotne. Wystarczy w nawiasach okrągłych, umiejscowionych zaraz po nazwie klasy wstawić nazwy klas z których chcemy dziedziczyć i oddzielić je przecinkami np. <tt>class klasa(klasa1,klasa2)</tt>.
 
=== Initializowanie i kodowanie klas ===
 
Ten przykład pokazuje initializację klasy <tt>FileInfo</tt> za pomocą metody <tt>__init__</tt>.
 
 
<big> '''Przykład 5.5. Initializator klasy FileInfo''' </big>
class FileInfo(UserDict):
"store file metadata" #(1)
def __init__(self, filename=None): #(2) (3) (4)
 
# Klasy mogą (a nawet powinny) posiadać także pewien tekst dokumentacyjny (ang. doc string), podobnie jak moduły i funkcje.
# Metoda <tt>__init__</tt> jest wywoływana bezpośredniu po utworzeniu instancji klasy. Może kusić aby nazwać ją konstruktorem klasy, co jednak nie jest prawdą. Metoda <tt>__init__</tt> wygląda podobnie do konstruktora (z reguły <tt>__init__</tt> jest pierwszą metodą definiowaną dla klasy), działa podobnie (jest pierwszym fragmentem kodu wykonywanego w nowo utworzonej instancji klasy) a nawet podobnie przmi (słowo „init” sugeruje, że jest to konstruktor). Niepoprawne, ponieważ obiekt jest już utworzony przed wywołaniem metody <tt>__init__</tt>, a my już posiadamy poprawną referencję do świeżo utworzonego obiektu. Jednak <tt>__init__</tt> jest najbliższą rzaczą, która przypomina konstruktor w Pythonie, ponadto pełni taką samą rolę.
# Pierwszy argument każdej metody znajdującej się w klasie, włączając w to <tt>__init__</tt>, jest zawsze referencja do bieżącej instancji naszej klasy. Według powszechnej konwencji, argument ten jest zawsze nazywany <tt>self</tt>. W metodzie <tt>__init__</tt> <tt>self</tt> odwołuje się do właśnie utworzonego obiektu; w innych metdoach klasy, odwołuje się do instancji, z której wywoływaliśmy metodę. Mimo, że musisz wyraźnie określić <tt>self</tt> podczas definiowania metody, nie określasz go w czasie wywoływania metody; Python dodaje go automatycznie.
# Motoda <tt>__init__</tt> może posiadać dowolną liczbę argumentów i podobnie jak w funkcjach, argumenty mogą być zdefiniowane z domyślnymi wartościami (i w ten sposób stają się argumentami opcjonalnymi). W tym przypadku argument <tt>filename</tt> ma domyślną wartość okreśłoną jako <tt>None</tt>, który jest Pythonową pustą wartością (czyli null).
 
{{Infobox|
Według konwencji, pierwszy argument metody należącej do pewnej klasy (referencja do bieżącej instancji klasy) jest nazywana <tt>self</tt>. Argument ten pełni ten samą rolę, co zastrzeżone słowo <tt>this</tt> w C++ czy Javie, ale <tt>self</tt> nie jest zastrzeżonym słowem w Pythonie (zależy to od konwencji nazw). Niemniej lepiej zostać przy tej konwencji, ponieważ jest to wręcz bardzo silna umowa.}}
 
<big>'''Przykład 5.6. Kodowanie klasy FileInfo'''</big>
 
class FileInfo(UserDict):
"store file metadata"
def __init__(self, filename=None):
UserDict.__init__(self) #(1)
self["name"] = filename #(2)
#(3)
 
# Niektóre języki pseudo-zorientowane obiektowo jak ''Powerbuilder'' posiadają koncepcję „rozszeżania” konstruktorów i innych zdarzeń, w których metoda należąca do przodka jest wykonywana automatycznie przed metodą potomka. Python takiego czegoś nie wykonuje; zawsze należy wyraźnie wywołać odpowiednią metodę należącą do przodka klasy.
# Klasa ta działa podobnie jak słownik, co mogliśmy zauważyć po spojrzeniu na tę linię. Przypisaliśmy argument ''filename'' jako wartość klucza ''name'' w naszym obiekcie.
# Zauważmy, że metoda <tt>__init__</tt> nigdy nie zwraca żadnej wartości.
 
=== Kiedy używać self i __init__ ===
 
Podczas definiowania metody pewnej klasy, musimy wyraźnie wstawić <tt>self</tt> jako pierwszy argument każdej metody, włączając w to <tt>__init__</tt>. Kiedy wywołujesz metodę z dziedziczonej klasy, musisz dołączyć argument <tt>self</tt>. Lecz jeśli wywołujesz metodę z zewnątrz, nie określamy argumentu <tt>self</tt>, po prostu go pomijamy. Python automatycznie wstawi odpowiednią referencję za nas. Na początku może się to wydawać trochę namieszane, jednak wynika to z pewnych różnic, o których jeszcze nie wiemy (between bound and unbound methods).
 
{{Infobox|
Metoda <tt>__init__</tt> jest opcjonalna, lecz jeśli ją definiujesz, musisz pamiętać o wywołaniu metody <tt>__init__</tt>, która należy do przodka klasy. W szczególności, jeśli potomek chce poszerzyć pewne zachowanie przodka, odpowiednia metoda potomka musi w odpowiednim miejscu bezpośrednio wywoływać metodę należącą do przodka, oczywiście z odpowiednimi argumentami.}}
 
== Inicjalizowanie klasy ==
Inicjalizowanie klasy jest dosyć proste. W tym celu wywołujemy klasę tak jak by była funkcją, dodając odpowiednie argumenty, które są określone w metodzie <tt>__init__</tt>. Zwracaną wartością będzie zawsze nowo utworzony obiekt.
 
'''Przykład 5.7. Tworzenie instacji klasy FileInfo'''
>>> import fileinfo
>>> f = fileinfo.FileInfo("/music/_singles/kairo.mp3") #(1)
>>> f.__class__ #(2)
<class fileinfo.FileInfo at 010EC204>
>>> f.__doc__ #(3)
'store file metadata'
>>> f #(4)
{'name': '/music/_singles/kairo.mp3'}
 
# Utworzyliśmy instancję klasy FileInfo (zdefiniowaną w module <tt>fileinfo</tt>) i przypisaliśmy właśnie utworzony obiekt do zmiennej <tt>f</tt>. Użyliśmy jednego parametru <tt>/music/_singles/kairo.mp3</tt>, który będzie odpowiadał za argument <tt>filename</tt> w metodzie <tt>__init__</tt> klasy FileInfo.
# Każda instancja pewnej klasy ma wbudowany atrybut <tt>__class__</tt>, który jest klasą danego obiektu. (Dodajmy, że ta reprezentacja dołancza fizyczny adres instancji na moim komputerze, na twoim reprezentacja będzie się różniła) Programiści Javy mogą być zaznajnajomieni z klasą <tt>Class</tt>, która posiada metody takie jak <tt>getName</tt> czy <tt>getSuperclass</tt>, aby pobrać metadane o pewnym obiekcie. W Pythonie, dziecko metadadanych jest dostępne bezpośrednio z obiektu wykorzystując atrybuty takie jak <tt>__class__</tt>, <tt>__name__</tt>, czy <tt>__bases__</tt>.
# Możesz pobrać notkę dokumentacyjną w podobny sposób jak funkcję czy moduł. Wszystkie instancje klasy mają taką samą informację.
# Pamiętamy, że metoda <tt>__init__</tt> przypisuje argument <tt>filename</tt> do self["name"]? W tym miejscu mamy wynik. Argumenty podawane podczas tworzenia instancji pewnej klasy zostają wysyłane do metody <tt>__init__</tt> (poza referencją do obiektu (argument, który nazwaliśmy <tt>self</tt>), Python zrobił to za nas).
 
{{Infobox|
W Pythonie, aby utworzyć instancję pewnej klasy, wywołujemy klasę, jakby to była zwykła funkcja zwracająca pewną wartość. Python nie posiada jakiegoś kluczowego słowa jakim jest np. operator <tt>new</tt> w Javie czy C++.
}}
 
=== Odśmiecanie pamięci ===
 
Jeśli tworzenie nowej instancji jest proste, to usuwanie jej jest jeszcze prostsze. W ogólności nie musimy wyraźnie zwalniać instancji klasy, ponieważ Python robi to automatycznie, gdy wychodzą one poza swój zasięg. W Pythonie rzadko występują wycieki pamięci.
==== Przykład 5.8. Próba zaimpelemtowania wycieku pamięci ====
 
>>> def leakmem():
... f = fileinfo.FileInfo('/music/_singles/kairo.mp3') #(1)
...
>>> for i in range(100):
... leakmem() #(2)
 
# Za każdym razem, gdy funkcja <tt>leakmem</tt> jest wywoływana, zostaje utworzona instancja klasy <tt>FileInfo</tt> przypisana do zmiennej <tt>f</tt>, która jest lokalną zmienną wewnątrz funkcji. Wtedy funkcja kończy się bez jakiegokolwiek zwolnienia zmiennej <tt>f</tt>, a więc spodziewalibyśmy się wycieku pamięci, lecz tak nie będzie. Kiedy funkcja się kończy, lokalna zmienna <tt>f</tt> wychodzi poza swój zakres. W tym miejscu nie ma więcej żadnych referencji do nowej instancji <tt>FileInfo</tt>, ponieważ nigdzie nie przypisywaliśmy jej do czegoś innego niż <tt>f</tt>), tak więc Python zniszczy instancję za nas.
# Nie zależnie od tego, jak wiele razy wywołamy funkcję <tt>leakmem</tt>, nigdy nie otrzymamy wycieku pamięci, ponieważ za każdym razem kiedy to zrobimy Python będzie niszczył nowo utworzony obiekt przed wyjściem z funkcji <tt>leakmem</tt>.
 
Technicznym terminem tego sposobu odśmiecania pamięci jest „zliczanie odwołań”. Python przechowuje listę referencji do każdej utworzonej instancji. W powyższym przykładzie, mamy tylko jedną referencję do instancji <tt>FileInfo</tt> -- zmienną <tt>f</tt>. Kiedy funkcja się kończy, zmienna <tt>f</tt> wychodzi poza zakres, więc licznik odwołań zmiejsza się do <tt>0</tt> i Python niszczy tę instancję automatycznie.
 
W poprzednich wersjach Pythona występowały sytuacje, gdy zliczanie odwołań zawodziało i Python nie mógł wyczyścić po nas pamięci. Jeśli tworzyłeś dwie instancję, które odowoływały się do siebie nawzajem (np. instancja listy dwukierunkowej (ang. double linked list), w których każdy węzeł wskazuje na poprzedni i następny znajdujący się w liście), żadna instancja nie była niszczona automatycznie, ponieważ Python uważał (poprawnie), że ciągle mamy referencję do każdej instancji. Od Pythonie 2.0 mamy dodatkowy sposób odśmiecania pamięci nazywany po ang. “mark-and-sweep” (znacz i zamiataj), dzięki któremu w sprytny sposób Python odnajduje różne wirtualne blokady i poprawnie czyści referencje tworzące cykl.
 
Podsumowywując w języku tym można po prostu zapomnieć o zarządzaniu pamięcią i pozostawić tę sprawę Pythonowi.
 
== Exploring UserDict: A Wrapper Class ==
== Specialne metody ==
== Zaawansowane specjalne metody ==
== Wprowadzenie do atrybutów w klasach ==
== Funkcje prywatne ==
== Podsumowanie ==
 
[[Kategoria:Python]]