W niniejszym rozdziale rozszerzymy znajomość typów danych w stosunku do zawartości rozdziału 6., i zintegrujemy ją z umiejętnością tworzenia kodu źródłowego, opisaną w rozdziale 7. Daje to możliwość korzystania ze złożonych struktur danych. Wykorzystanie gotowych obiektów ilustrujemy przykładami. W ramach uzupełnienia opisujemy koncepcję klas, pozwalającą tworzyć własne typy obiektów, i podajemy elementarne przykłady jej wykorzystania.
Obiekty integrują dane z przypisanym ich obsłudze kodem.
W Pythonie wszystko jest obiektem — zobaczymy… (chociaż nie o wszystkim warto myśleć jak o obiekcie).
Python pozwala deklarować własne klasy obiektów (osobny podrozdział 10.5. dla chętnych).
Niektóre obiekty reprezentują dane złożone. W takim przypadku ich części składowe są reprezentowane przez pola.
Pola
Przykładem pól są zmienne z.real
i z.imag
,
wchodzące w skład obiektu z
typu complex
(czyli reprezentacji liczby zespolonej).
Oprócz pól, stanowiących o wartości złożonej, obiekty są wyposażone w zestaw typowych operacji, zwanych metodami. Są to procedury lub funkcje przypisane do obiektu i operujące na nim. Oto krótka charakterystyka metod:
lista.append(element)
lista.sort()
wynik = napis.encode(kodowanie)
plik.close()
Stwierdzenie, że zmienna
,
mimo że w zasadzie poprawne, nie oddaje całej prawdy.
Wartość jest w Pythonie obiektem przechowywanym
w pamięci, a zmienna jedynie umożliwia dostęp do niego.
Ideę tę dobrze ilustruje schemat:
a
przechowuje wartość
zmienna ------> wartość
Dwie lub więcej zmiennych może reprezentować ten sam obiekt. Mówimy o nich, że są tożsame.
Tożsamość jest warunkiem znacznie silniejszym niż równość.
O obiektach równych możemy powiedzieć, że są takie same
,
czyli że przyjmują takie same wartości
.
Obiekty tożsame są istocie tym samym obiektem.
Dwie zmienne reprezentują dwa różne obiekty a ------> wartość b ------> wartość Dwie zmienne reprezentują ten sam obiekt a ------> wartość <------ b
Do sprawdzania tożsamości obiektów służy operator is
.
Istnieje też operator is not
będący jego zaprzeczeniem.
a == b # sprawdza równość obiektów (czy zmienne mają te same wartości) a is b # sprawdza identyczność obiektów (czy zmienne są tym samym obiektem w pamięci) a is not b # sprawdzenie, czy dwie wartości reprezentują dwa różne obiekty # uwaga: a is not b nie jest tym samym, co a is (not b)
Poniższe przykłady ilustrują subtelną, lecz istotną różnicę między równością wartości a tożsamością obiektów:
1 == 1.0 True 1 is 1.0 False a = 1.0 b = a # po operacji przypisania zmienne są tożsame (bo odnoszą się do tego samego obiektu) a == b True a is b True a = 1.0 b = 1.0 a == b True a is b False a = 1.0 b = 0.1*10 a == b True a is b False a = 'Ala' b = 'As' c = a + ' i ' + b c == 'Ala i As' True c is 'Ala i As' False
W przypadku obiektów złożonych sprawa tożsamości nabiera pierwszorzędnej wagi:
a = [1,2,3] b = a a == b True a is b True c = [1,2,3] a == c True a is c False a[1] = 99 a is b True b [1, 99, 3] b = c a is c False b is c True
Operacja przypisania nadaje tożsamość zmiennej
(choć może jej nie zmienić, np. a = a
).
Operacje
a = wartość b = wartość
sprawią, że zmienne a
i b
będą miały wartości wyliczone identycznym sposobem,
ale niekoniecznie będą wskazywać ten sam obiekt.
Zależy to od sposobu określenia wartości
:
czy będzie się on wiązał z utworzeniem nowego obiektu.
a = 2 b = 2 c = 1+1 a is b # w pamięci Pythona istnieje tylko jeden „egzemplarz” danej wartości typu int True a is c True a = 1.0 b = 1.0 a is b # natomiast poszczególne wartości typu float mogą mieć więcej „wcieleń” False
Zmienne a
i b
po wykonaniu operacji
a = wartość b = a
na pewno będą odwoływały się do tego samego obiektu. Ten sam skutek uzyskamy stosując skrótową notację podstawienia
b = a = wartość
Taki „łańcuch przypisań” może mieć dowolną długość.
W liście utworzonej techniką zwielokrotnienia, czyli przez „pomnożenie” listy przez liczbę całkowitą, wszystkie elementy są ze sobą tożsame. W przypadku, kiedy otrzymano tą techniką listę list, należy być bardzo ostrożnym podczas jej modyfikacji:
dane = [[0,0]]*5 dane [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]] dane[0] is dane[1] True dane[0][0] = 1.0 # uwaga! [[1.0, 0], [1.0, 0], [1.0, 0], [1.0, 0], [1.0, 0]]
Tak, napis jest obiektem
dir(str)
daje spis metod dostępnych dla napisów.
Dla nas liczą się tylko te, których nazwa nie rozpoczyna się
od znaku podkreślenia:
dir(str) [… 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
Niektóre są całkiem użyteczne, np.
encode()
,
split()
albo replace()
.
napis.encode(kodowanie)
napis.decode(kodowanie)
a = 'żółw' a '\xc5\xbc\xc3\xb3\xc5\x82w' b = a.decode('utf-8') # konwersja z UTF-8 do Unikodu b u'\u017c\xf3\u0142w' c = b.encode('cp1250') # konwersja z Unikodu do Windows-1250 c '\xbf\xf3\xb3w' print(a) # interpretacja tekstu w kodowaniu zgodnym z terminalem (w tym przypadku terminal rozumie UTF-8) żółw print(b) # interpretacja tekstu unikodowego żółw print(c) # interpretacja tekstu w kodowaniu niezgodnym z terminalem (w tym przypadku terminal nie rozumie kodowania Windows-1250) ¿ó³w
encode()
i decode()
działają na dowolnych obiektach tekstowych, w tym także na stałych i na wynikach metod:
c = 'żółw'.decode('utf-8').encode('cp1250') c '\xbf\xf3\xb3w' print(c) # interpretacja tekstu w kodowaniu niezgodnym z terminalem (w tym przypadku terminal nie rozumie kodowania Windows-1250) ¿ó³w
Konstrukcja tablica = napis.split(separator)
rozbija napis na tablicę napisów. Jest ona bardzo wygodna np. podczas czytania danych liczbowych.
wierszdanych = '1\t12.22\t-10.55\n' wektor = wierszdanych.split() wektor ['1', '12.22', '-10.55'] wektor = [int(wektor[0]), float(wektor[1]), float(wektor[2])] wektor [1, 12.22, -10.55]
Jak widać, elementy tablicy otrzymanej za pomocą split()
nadal pozostają tekstami.
Domyślnie zestaw separatorów obejmuje ' \t\n'
, czyli tzw. białe znaki.
W domyślnym trybie pomijane są puste ciągi danych (tj. sąsiadujące separatory
są traktowane jak pojedynczy separator).
'1\t\t\t12.22\t-10.55'.split() ['1', '12.22', '-10.55']
Można też wymusić separowanie za pomocą dowolnego innego znaku.
wierszdanych = '1,12.22,-10.55' wektor = wierszdanych.split(',') wektor ['1', '12.22', '-10.55'] wektor = [int(wektor[0]), float(wektor[1]), float(wektor[2])] wektor [1, 12.22, -10.55]
W tym przypadku każde wystąpienie separatora jest traktowane osobno, zatem pustych ciągów nie pomija się.
'1,,,12.22,-10.55'.split(',') ['1', '', '', '12.22', '-10.55']
Separatorem może także być ciąg znaków.
dane = '1trututu2trututu3\n\n4trututu5trututu6\n' dane '1trututu2trututu3\n\n4trututu5trututu6\n' dane.split() ['1trututu2trututu3', '4trututu5trututu6'] dane.split('\n') ['1trututu2trututu3, '', ,4trututu5trututu6', ''] dane.split('trututu') ['1', '2', '3\n\n', '5', '6\n']
Operacja odwrotna do split()
polegałaby na połączeniu tekstów z danej listy
w jeden tekst, przy czym w miejscach „sklejenia” znajdowałaby się jakaś fraza separująca.
Odpowiednia metoda jest przypisana, co jest nieco nieoczekiwane, do typu napisowego, a nie listowego,
i nosi nazwę join
. Napis będący jej „właścicielem” będzie pełnił rolę separatora
w połączonym tekście.
By nie rozwodzić się nad tym zbyt długo, zakończymy typowym przykładem konwersji tekstu na listę
tekst = 'Adam\tKowalski\t2008-12-22\tWrocław' tekst.split('\t') ['Adam', 'Kowalski', '2008-12-22', 'Wrocław']
oraz odpowiadającej jej konwersji w stronę przeciwną
rekord = ['Adam', 'Kowalski', '2008-12-22', 'Wrocław'] separator = '\t' separator.join(rekord) 'Adam\tKowalski\t2008-12-22\tWrocław'
Metoda napis.replace(fraza1, fraza2)
tworzy nową daną typu napisowego, jaką otrzymamy z wartości napis
przez zastąpienie w niej wszystkich wystąpień tekstu fraza1
tekstem fraza2
.
zdanie = 'Ala ma Asa.' zmienione = zdanie.replace('A', 'O') zmienione 'Ola ma Osa.' zdanie.replace('A', 'Be') 'Bela ma Besa.'
Wynik nie modyfikuje oryginalnego napisu — wiemy już, że dane tekstowe w Pythonie są niemodyfikowalne. Jednak przypisanie
zdanie = zdanie.replace('A', 'O')
jest jak najbardziej legalne; spowoduje ono „zbudowanie” nowej wartości
typu str
, zapamiętanie jej w zmiennej zdanie
, a w konsekwencji
— „zapomnienie” przez tę zmienną jej pierwotnej zawartości (co oznacza też
zerwanie tożsamości z innymi zmiennymi reprezentującymi tę zawartość, i ile takie istniały).
Zamiana fraz dopasowanych do wyrażeń regularnych jest także możliwa.
Dane typu napisowego nie mają odpowiedniej metody; celowi temu służy funkcja
re.sub(wzorzec, zast, napis)
z modułu re
.
Tak, lista jest obiektem.
append
, insert
i sort
są metodami.
dir(list)
daje spis metod dostępnych dla list.
Nie ma ich wiele, ale są użyteczne (dla nas „liczą się”
tylko metody o nazwach nie rozpoczynających się znakiem podkreślenia):
dir(list) [… 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
Dodajmy, że metody zmieniające stan listy zachowują jej tożsamość jako obiektu. Tak więc operacje wykonywane za ich pomocą
a = [1, 2, 3] b = a # a i b wskazują na ten sam obiekt a.append(4) # a zachowuje dotychczasową tożsamość; operacje wykonane na a „widać” także na b a [1, 2, 3, 4] b [1, 2, 3, 4]
nie są równoważne podobnym operacjom używającym działań
a = [1, 2, 3] b = a # a i b wskazują na ten sam obiekt a = a + [4] # a dostaje nową tożsamość; od tej chwili a jest czym innym niż b a [1, 2, 3, 4] b [1, 2, 3]
mimo że końcowa zawartość listy a
jest w obu przykładach identyczna.
Wypada dodać, że w przypadku użycia operatora +=
w następujący sposób:
a += [4]
tożsamość listy a
zostanie zachowana.
Pliki służą przede wszystkim do trwałego przechowywania informacji w pamięci masowej. Zarządzanie plikami leży w gestii systemu plików właściwego dla danego urządzenia i odbywa się za pośrednictwem systemu operacyjnego. Plik jest identyfikowany na podstawie nazwy, ścieżki dostępu lub adresu sieciowego (URI), będących danymi typu tekstowego.
Z istniejącego pliku możliwe jest pobieranie informacji w drodze operacji zwanej odczytywaniem. W istniejącym lub nowo tworzonym pliku można umieszczać informację w drodze operacji zwanej zapisywaniem.
Do operowania na zawartości plików w Pythonie służą obiekty
specjalnego typu plikowego (file
). Obiekt plikowy jest wiązany
z konkretnym plikiem systemowym lub sieciowym, wskazanym przez nazwę
lub ścieżkę dostępu, podczas operacji otwarcia (open
).
Odczyt lub zapis zawartości pliku mogą być wykonywane w czasie,
kiedy obiekt plikowy jest otwarty. Z chwilą zamknięcia
(close
) obiektu plikowego tracimy tę możliwość.
Pamiętajmy, że znak '\'
ma specjalne znaczenie w napisach.
W nazwach plików bezpieczniej jest używać ukośników '/'
niż '\'
(np. '/home/Ja/dane/proba1.txt'
lub
'c:/Users/Ja/dane/proba1.txt'
,
'ftp://mat.up.wroc.pl/pub/dane/proba1.txt'
).
Będą one poprawnie rozumiane we wszystkich systemach operacyjnych.
Można też pisać '\\'
zamiast '\'
(np. 'c:\\Users\\Ja\\dane\\proba1.txt'
),
ale tylko w systemach używających znaku '\'
jako separatora w ścieżkach dostępu
— czyli w systemach Windows.
Użycie /
jest zalecane, jeżeli program ma być przenośny między
systemami operacyjnymi (MacOS X, *nix,
Windows, inne). Użycie \\
jest zalecane tylko
w sytuacji, kiedy pracujemy w Windows, ale pliki otwieramy
za pośrednictwem zewnętrznej aplikacji nie akceptującej /
(np. przez
Component Object Model, patrz podrozdział 11.4.).
plik = open(nazwa, tryb)
plik
nazwa
tryb
'r'
(‘read’, ‘czytaj’)'w'
(‘write’, ‘nadpisz’)'a'
(‘append’, ‘dopisuj’)'+'
) pozwalają na jednoczesny odczyt i zapis; nie omawiamy'b'
) pozwala na odczytywanie/zapisywanie bloków bajtów; nie omawiamy
Python 3 przewiduje dodatkowe argumenty funkcji open()
,
pozwalające m.in. zadeklarować system kodowania znaków obowiązujący w otwieranym pliku.
Do zamknięcia otwartego pliku służy operacja plik.close()
.
Po jej wykonaniu program traci dostęp do zawartości pliku.
Zamknięcie pliku jest niezbędne dla pomyślnego ukończenia operacji systemowych, zwłaszcza związanych z zapisywaniem danych.
Odczyt jest możliwy tylko z pliku uprzednio otwartego
w trybie do odczytu (czyli 'r'
).
Plik przypomina taśmę z zapisanym ciągiem znaków. Zawartość pliku jest odczytywana znakami lub wierszami po kolei. Wygodnie jest wyobrażać sobie głowicę czytającą ten ciąg. Bezpośrednio po otwarciu głowica stoi na początku taśmy, i w miarę kolejnych instrukcji dotyczących odczytu przesuwa się wzdłuż zawartości pliku. Głowica nie może się cofnąć, nie może też przeskoczyć w przód inaczej, niż czytając zawartość.
tekst = plik.read()
tekst = plik.readline()
tablica = plik.readlines()
Przy próbie czytania z pliku, którego głowica umieszczona jest już przy jego końcu,
otrzymamy pusty napis, czyli ''
. Zauważmy, że w każdym innym przypadku
odczytany tekst nie będzie pusty. Np. czytanie za pomocą readline()
daje zawsze zawartość kolejnego wiersza pliku wraz z kończącym go znakiem
końca wiersza. Zatem odczytując pusty wiersz dostaniemy '\n'
.
W wyniku czytania z pliku otrzymuje się wyłącznie dane napisowe (str
).
Konwersję do innych typów danych trzeba wymusić, np. w przypadku kiedy pojedynczy
wiersz pliku zawiera jedną liczbę całkowitą:
wartość = int(plik.readline())
.
Jeżeli wiersz pliku zawiera kilka danych, to trzeba je wyłuskać z odczytanego tekstu, np.:
wiersz = plik.readline() wiersz = wiersz.split() wektor = [float(wiersz[0]), float(wiersz[1]), float(wiersz[2])]
przy czym
readline()
zwraca tekst pojedynczego wiersza pliku (wraz z kończącym go znakiem końca wiersza);tekst.split(separator)
rozbija go wg wskazanego znaku separatora na tablicę napisów;split()
możemy użyć notacji tekst[od:do]
, by pobrać kawałki odczytanego tekstu;float()
.
Zapisywać da się tylko do pliku otwartego do zapisu
(w trybie 'w'
lub 'a'
).
Tryb 'w'
usuwa poprzednią zawartość pliku.
Tryb 'a'
wymusza dopisywanie do końca istniejącego pliku.
plik.write(tekst)
str()
.
write()
nie dopisuje nic „od siebie”, nawet separatora.
plik.writelines(tablica)
\n
plik.writeline(tekst)
…\n
Zapisywanie do pliku dotyczy tylko wartości napisowych.
Zapis wartości innych typów trzeba poprzedzić konwersją,
np. plik.write(str(wartość_liczbowa))
.
W Pythonie 2 w celu zapisania wartości wyrażenia do pliku
można było wykorzystać instrukcję print
w postaci
print >>plik wyrażenie
(Notacja ta przypomina nieco przekierowanie strumienia danych w powłoce systemowej).
Także i w tym przypadku plik
musi być obiektem plikowym
otwartym do zapisu.
Jak odczytać zawartość pliku?
Odpowiedź zależy od tego, jakiego rodzaju dane plik zawiera, i co zamierzamy z nimi zrobić. Ograniczymy się tylko do plików znakowych, zwracając większą uwagę na zbiory danych o charakterze tabelarycznym, niż na dokumenty.
Najprostsza sytuacja dotyczy programów typu filtr, które odczytują plik danych, i w miarę jego odczytywania budują i wyprowadzają informację wynikową. Nie ma wtedy żadnej potrzeby, by w całości przechowywać zawartość pliku w pamięci; wystarczy, że będziemy ją analizować wiersz po wierszu. Dlatego wszystko odbywa się w cyklu: przeczytaj, przetwórz, wyrzuć, powtarzanym tak długo, aż osiągnięty zostanie koniec pliku. Pomocne będą przy tym następujące schematy postępowania:
wykorzystanie pętli for
przebiegającej składniki obiektu readlines()
.
Wiersze są wtedy odczytywane w kolejnych iteracjach.
plik = open(nazwa, 'r') for wiersz in plik.readlines(): przetwórz wiersz plik.close()
użycie pętli while
. Wykorzystamy przy tym fakt, że każdy
pomyślnie odczytany wiersz jest niepusty (bo musi zawierać co najmniej
znak \n
). Zatem spełnienie warunku readline() == ''
oznacza, że dotarliśmy już do końca pliku.
plik = open(nazwa, 'r') # wczytaj pierwszy wiersz wiersz = plik.readline() # sprawdź, czy doczytaliśmy plik do końca while wiersz != '': przetwórz wiersz # wczytaj następny wiersz wiersz = plik.readline() plik.close()
Przeciwna sytuacja ma miejsce, kiedy musimy mieć jednoczesny dostęp do wszystkich danych, zaś podział pliku na wiersze nie jest istotny. Jest to np. przypadek, kiedy plik zawiera niesformatowany tekst o ustalonej szerokości wierszy.
Wtedy najprostszym właściwym sposobem jest przeczytanie pliku w całości. W porównaniu z poprzednim sposobem metoda ta wypada słabiej, jeżeli nie jest potrzebny jednoczesny dostęp do danych z różnych wierszy pliku.
plik = open(nazwa, 'r') dane = plik.read() plik.close() przetwórz odczytany tekst
W naszym przypadku najbardziej typowa jest sytuacja, kiedy plik zawiera tabelę danych, przy czym znaki końca wiersza rozdzielają poszczególne rekordy. Taki plik, jeżeli zadanie na to pozwala, warto czytać „na raty”, zgodnie z pierwszą wskazaną metodą. Jeżeli jednak musimy wczytać go w całości, zapewne zależy nam na zbudowaniu tablicy obiektów odpowiadających zawartości wierszy pliku. Możemy to zrobić kilkoma sposobami:
Pierwszy z nich zapamięta wynik czytania wiersz po wierszu:
plik = open(nazwa, 'r') dane = plik.readlines() plik.close() przetwórz listę odczytanych tekst
Pamiętajmy, że metody odczytujące nie pomijają ani jednego znaku z pliku,
więc każdy element listy dane, być może z wyjątkiem ostatniego,
będzie się kończył znakiem końca wiersza \n
.
Druga metoda polega na wczytaniu tekstu i podzieleniu go na wiersze.
Posłużymy się w tym celu operacją str.split()
, omawianą
w podrozdziale 10.2.2.
plik = open(nazwa, 'r') dane = plik.read() plik.close() dane = dane.split('\n') przetwórz listę z wierszami odczytanych danych
W tym przypadku znaki końca wiersza pełnią rolę separatorów, więc
po zastosowaniu funkcji split('\n')
znikną.
Za to jeżeli ostatni wiersz pliku kończył się Enterem, to na końcu
zbudowanej tą metodą listy powstanie ostatni, pusty element.
Jest to związane ze specyfiką działania funkcji split()
.
Nie ma sensu utrzymywać w pamięci dwóch kopii zawartości pliku
odpowiadających różnym stadiom przetworzenia.
Dlatego zawartość dane.split()
zapamiętujemy
zamiast, a nie oprócz, odczytanej treści tekstowej.
Następnym etapem jest odzwierciedlenie zawartości poszczególnych wierszy pliku w strukturze danych. Omawiamy przypadek, w którym plik zawiera typową tabelę składającą się z jednakowo zbudowanych wierszy, poprzedzonych ewentualnie jedno- lub kilkuwierszowym nagłówkiem. W tym celu musimy przetworzyć każdy wiersz po kolei, zamieniając go z typu tekstowego na rekord (obrazowany np. przez listę).
Przy podziale wiersza na pola pomoże nam funkcja split()
.
Postać separatora, będąca argumentem funkcji split
, winna
być dopasowana do formatu odczytywanych danych. W otrzymanej liście
warto od razu wymusić typ pól zgodny z sensem danych.
Wyglądać to może mniej więcej tak:
plik = open(nazwa, 'r') # znamy format pliku i wiemy, że nagłówek zajmuje dwa pierwsze wiersze... naglowek = plik.readline() + plik.readline() # ... a reszta aż do końca to dane tabelaryczne dane = [] for wiersz in plik.readlines(): # zamieniamy tekst na listę; jeżeli trzeba, przekazujemy do split() postać separatora wiersz = wiersz.split() # modyfikujemy dane z bieżącego wiersza stosownie do naszych potrzeb wiersz = ... # po czym dołączamy rekord do tabeli dane.append(wiersz) # kiedy skończymy, mamy gotową tabelę plik.close()
różne techniki czytania i pisania |
|
czytanie i przetwarzanie wiersz po wierszu, bez zapamiętywania całości |
|
wczytanie listy wierszy |
|
wczytanie całego pliku, potem podział na wiersze |
Jak wiadomo, plik jest lokalizowany w systemie plików na podstawie ścieżki dostępu, a w ramach pojedynczej kartoteki — za pomocą nazwy. Odwołanie się do pliku na podstawie samej tylko nazwy nie gwarantuje powodzenia, gdyż domyślna ścieżka dostępu (kartoteka bieżąca, current working directory) przydzielona procesowi przez system zależy od okoliczności uruchomienia programu.
Opanowanie reguł odwoływania się do plików za pomocą nazw, ścieżek względnych i ścieżek bezwzględnych wymaga zrozumienia podstawowych reguł uruchamiania procesów pod kontrolą systemu operacyjnego, nie ma natomiast głębszego związku z językiem programowania.
Podstawowe informacje o ścieżkach dostępu do plików i o kartotekach bieżących znajdziesz np. w podrozdziale 1.3. pierwszej części opracowania.
W przypadku używania samych nazw bez ścieżek, pliki będą poszukiwane, odczytywane i zapisywane w kartotece bieżącej, a w przypadku ścieżki względnej: w kartotece o położeniu ustalonym względem kartoteki bieżącej.
Początkowa wartość kartoteki bieżącej zależy od okoliczności uruchomienia programu, i w trakcie jego wykonywania może być zmieniana.
W tym przypadku jednoznacznie identyfikujemy plik w całym systemie.
Przenośność danych.
Nośniki wymienne.
Funkcja getcwd
z modułu os
zwraca pełną ścieżkę dostępu do kartoteki bieżącej.
import os os.getcwd() '/home/jan/cwiczenia/2011/testy'
Funkcja chdir
z modułu os
powoduje zmianę kartoteki bieżącej działającego procesu.
import os os.getcwd() '/home/jan/cwiczenia/2011/testy' os.chdir('/home/jan/dane') os.getcwd() '/home/jan/dane'
Funkcja expanduser
z modułu os.path
zastępuje znajdujące się w tekście frazy ~
oraz ~user
ścieżkami dostępu do kartoteki osobistej, odpowiednio bieżącego i wskazanego użytkownika.
import os os.getcwd() '/home/jan/cwiczenia/2011/testy' os.path.expanduser('~') '/home/jan' os.path.expanduser('~adam') # ten użytkownik ma kartotekę osobistą w innym miejscu '/student/adam' os.path.expanduser('~ktosiu') # użytkownika o tej nazwie nie ma w systemie '~ktosiu' os.chdir(os.path.expanduser('~/dane')) os.getcwd() '/home/jan/dane'
Funkcja listdir
z modułu os
generuje spis zawartości wybranej kartoteki. Ma on postać listy nazw plików.
W celu uzyskania ścieżek wystarczy poprzedzić je ścieżką do kartoteki,
nie zapominając o separatorze.
import os os.listdir('/home/jan/cwiczenia/2011/dane') ['temp_sty2010.txt', 'wilg_sty2010.txt', 'temp_lut2010.txt', 'wilg_lut2010.txt', 'stacje.zip']
Funkcja glob
z modułu glob
generuje listę ścieżek dostępu do plików o nazwach zgodnych z podanym wzorcem.
import glob glob.glob('/home/jan/cwiczenia/2011/dane') ['/home/jan/cwiczenia/2011/dane'] glob.glob('/home/jan/cwiczenia/2011/dane/temp*') ['/home/jan/cwiczenia/2011/dane/temp_sty2010.txt', '/home/jan/cwiczenia/2011/dane/temp_lut2010.txt']
Funkcja open()
umożliwia dostęp do plików znajdujących się
w lokalnym systemie plików. Pliki umieszczone na zdalnych systemach,
dostępne za pomocą protokołów sieciowych, takich jak ftp
czy http, otwiera się nieco inaczej. Zamiast funkcji
open(nazwa, tryb)
, użyjemy w tym celu
funkcji urlopen(adres)
z biblioteki urllib
.
Jest ona częścią standardowej instalacji Pythona. Zamiast nazwy pliku
należy podać wtedy jego adres URI. Na przykład instrukcja
plik = urllib.urlopen('ftp://mat.up.wroc.pl/pub/dane/dane.txt')
pozwala odczytać dane bezpośrednio z pliku udostępnionego na serwerze, bez potrzeby uprzedniego kopiowania go do lokalnego systemu plików.
Użytkownik pragnący korzystać z programu posługującego się tą metodą powinien mieć uprawnienia pozwalające na dostęp do takiego pliku. Pozostałe szczegóły obsługi tak otwartego pliku pozostają bez zmian.
Za pomocą biblioteki urllib
możemy również otwierać,
choć jedynie do odczytu, zwykłe pliki z systemu plików naszego komputera.
W tym celu należy użyć bezwzględnej ścieżki dostępu w postaci adresu
URL z przedrostkiem file://
, np.
# w systemach uniksowych plik = urllib.urlopen('file:///home/adam/dane/dane.txt') # w systemach Windows plik = urllib.urlopen('file:///c:/Users/adam/dane/dane.txt')
…
W skład obiektu wchodzą pola będące zmiennymi przechowującymi istotne parametry obiektu, oraz metody będące funkcjami odwzorowującymi działania przypisane do obiektu. Zarówno pola, jak metody noszą nazwy.
Klasy służą do tworzenia nowych typów obiektów. Można powiedzieć, że klasa jest szablonem pozwalającym tworzyć obiekty.
Deklaracja klasy odbywa się za pomocą dyrektywy class
.
We współczesnym Pythonie podstawowa forma definicja klasy ma postać
class nazwaklasy(object): definicja_klasy
Gdzieniegdzie spotyka się przestarzałe deklaracje postaci
class nazwaklasy: definicja_klasy
których nie należy stosować w nowo tworzonym kodzie.
Użytkownik deklaruje własne klasy, by na ich podstawie tworzyć konkretne obiekty dopasowane do własnych potrzeb. Obiekt taki tworzy się używając klasy w następujący sposób:
obiekt = nazwaklasy(argumenty)
przy czym argumenty
zależą od szczegółów
zadeklarowania danej klasy, które zostaną skrótowo wyjaśnione poniżej.
Wszystkie obiekty zbudowane za pomocą jednej klasy są w pewnym
sensie „podobne” do siebie.
Wszystkie zmienne zadeklarowane wewnątrz klasy stają są właściwościami
(polami) wspólnymi dla wszystkich obiektów powoływanych za pomocą tej klasy.
Przy dostępie do właściwości obiektów posługujemy się nazwami postaci
obiekt.właściwość
.
Funkcje zadeklarowane wewnątrz klasy stają się metodami obiektu.
Przy wywoływaniu metody stosowana jest podobna konwencja
obiekt.metoda(argumenty_wywołania)
.
Istnieje też mechanizm pozwalający na zróżnicowanie poszczególnych obiektów generowanych na podstawie samej klasy.
Na przykład wszystkie obiekty reprezentujące prostokąty, generowane
na podstawie klasy prostokat
, miałyby swoje indywidualne
właściwości szer
i wys
, reprezentujące ich wymiary.
Jest to konieczne, gdyż od różnych prostokątów oczekujemy, by mogły różnić się wymiarami.
Winny one natomiast posiadać wspólną metodę pole()
, która mnożyłaby te wymiary
w celu obliczenia powierzchni figury. Jest jasne, że metoda musi pobierać wymiary
od tego prostokąta, do którego sama należy.
W związku z tą potrzebą, przyjęto w Pythonie następującą konwencję
dotyczącą argumentów metod. Pierwszy argument w każdej deklaracji metody odpowiada
obiektowi będącemu jej właścicielem. Tradycyjnie dla tego argumentu używana jest
nazwa self
.
Jeżeli więc wewnątrz klasy prostokat
zdefiniowano metodę
o nagłówku pole(self)
, to argument self
będzie reprezentował
prostokąt, którego pole zamierzmy obliczyć. W tym momencie nie ma różnicy między metodą
a zwykłą funkcją. Różnica pojawi się w chwili wywołania; w przypadku metody będzie ono
miało postać
a = prostokat() a.szer = 2.0 a.wys = 3.0 ... a.pole()
gdzie a
jest konkretnym obiektem typu prostokat
(obiekt-właściciel
„wchodzi” jaki pierwszy argument wywołania należącej do niego metody).
Sama metoda pole()
w przypadku prostokąta może być zorganizowana następująco:
class prostokat(object): szer = 0.0 wys = 0.0 def pole(self): return self.szer * self.wys
Wyjaśnijmy: w celu znalezienia pola danego prostokąta (czyli tego, z którego została wywołana), metoda oblicza iloczyn wymiarów tego właśnie prostokąta.
Jeżeli dany obiekt typu prostokat
miał nadane właściwości
szer
oraz szer
, to właśnie one będą użyte przy obliczeniu pola.
Jeżeli takich wartości nie ustalono, to będą użyte wartości domyślne, opisane
wewnątrz deklaracji klasy (czyli: nowo utworzony za pomocą naszej klasy prostokąt ma pole 0).
Niektóre nazwy funkcji deklarowanych jako metody wewnątrz klasy
mają szczególne znaczenie. Najważniejszą wśród nich jest funkcja
o nazwie __init__()
. Jest ona wywoływana w chwili
tworzenia nowego obiektu za pomocą przypisania postaci
obiekt = klasa(argumenty)
Argumenty wywołania klasy są przekazywane funkcji __init__()
.
Lista argumentów zaprojektowana dla tej funkcji może być dowolna,
jednak — tak samo jak w przypadku innych metod — pierwszy z nich
odpowiada tworzonemu obiektowi (tradycyjnie nosi on nazwę self
).
Wynika stąd, że liczba argumentów wywołania klasy winna być o jeden mniejsza
od liczby argumentów jej funkcji __init__()
.
Na przykład w rozpatrywanym wyżej przypadku klasy prostokat
,
tworzone za jej pomocą obiekty powinny otrzymywać wymiary szerokości i wysokości.
Szkic takiej klasy mógłby wyglądać następująco:
class prostokat(object): def pole(self): return self.szer*self.wys def __init__(self, a, b): self.szer = a self.wys = b
Pierwszy argument zadeklarowany w nagłówku funkcji __init__(self, a, b)
odnosi się do generowanego obiektu. Wartości pozostałych dwóch argumentów zostaną
przejęte z wywołania klasy, i przypisane do obiektu jako jego właściwości
szer
oraz wys
. Oto, jak można użyć powyższej klasy
do utworzenia trzech różnych obiektów typu prostokat
i sprawdzenia
ich pola powierzchni:
a = prostokat(1.0, 2.5) b = prostokat(2.0, 4.0) b = prostokat(1.5, 3.0) print(a.pole(), b.pole(), c.pole())
Funkcja __init__()
jest właściwym narzędziem w każdym przypadku,
w którym tworzone obiekty winny różnić się wartościami atrybutów.
Nie będziemy wgłębiać się w dalsze szczegóły deklaracji klas i obiektów. Poniżej zamieszczamy kilka elementarnych przykładów, które poglądowo zilustrują ideę klas i obiektów.
Deklaracja klasy reprezentującej punkty płaszczyzny |
|
Deklaracja klasy reprezentującej prostokąty o kanonicznej orientacji |
|
Demonstracja użycia zdefiniowanych wyżej klas |
Klasy zadeklarowane tym sposobem są obiektami typu type
(czyli de facto typami danych),
zaś typami tworzonych za ich pomocą obiektów są te właśnie klasy.
list