PPy3/WejścieWyjście
Spis treści
Obsługa wejścia i wyjścia
Większość sensownych programów służy do przetworzenia jakichś informacji uzyskanych z zewnątrz programu: w najprostszym przypadku, z pliku na dysku, lub z danych wprowadzonych przez użytkownika za pomocą klawiatury. W przypadku programów wywoływanych z linii poleceń, istnieje też poręczny i łatwy do wykorzystania mechanizm by wskazać programowi, co ma robić - za pomocą tzw. opcji i/lub wartości parametrów wpisanych na linii poleceń, po nazwie wywoływanego programu. Często też chcemy, by wyniki działania programu, np. przetworzone dane lub wyniki obliczeń, znalazły się w pliku na dysku - choć czasami możemy woleć, aby również (lub zamiast tego) zostały one wyświetlone na ekranie. Omówimy teraz krótko, jak to osiągnąć.
Pliki tekstowe vs. binarne
Zawartość każdego pliku na dysku to tak naprawdę strumień bajtów - bajt, czyli grupa ośmiu bitów, może być traktowany jako reprezentacja (w zapisie dwójkowym), jakiejś liczby całkowitej dodatniej z zakresu 0-255. Często jednak zawartość ta ma być traktowana jako reprezentacja tekstu, czyli ciągu znaków stosowanych w zapisie jakiegoś języka - może to być język naturalny (polski, angielski, chiński, ...) lub np. język programowania (Python, Java, C++, ...), język tworzenia stron WWW (HTML), itd. W tym celu stworzono tzw. kodowania, czyli standardy reprezentowania znaków stosowanych w systemach zapisu języków naturalnych za pomocą bajtów lub grup bajtów (języki programowania itp. zasadniczo posługują się tymi samymi zestawami znaków, co języki naturalne - a zwłaszcza angielski).
Nie wchodząc za bardzo w szczegóły, obecnie stosowane reprezentacje cyfrowe tekstu oparte są na standardzie Unicode, który m. in. definiuje tzw. uniwersalny zestaw znaków (UCS) - tablicę, zawierającą wszystkie znaki (alfanumeryczne, przestankowe, ideogramy - właściwe dla języków dalekowschodnich, i szeregu innych kategorii) stosowane w systemach pisma wszystkich żywych języków świata (i wielu języków martwych). Wewnętrzna reprezentacja danych napisowych w Pythonie oparta jest na standardzie Unicode - zatem pythonowy napis może zawierać znaki z wszelkiego rodzaju systemów pisma, w dowolnej kombinacji. Zapis tych danych w pliku dyskowym - i odwrotne, zinterpretowanie strumienia bajtów odczytanego z pliku dyskowego jako reprezentacji pewnego napisu - wymaga przyjęcia jakiegoś konkretnego kodowania. Unicode nie stanowi sam w sobie kodowania - określa on repertuar znaków oraz pozycję każdego z nich w tablicy UCS, nie zaś bajt lub grupę bajtów go reprezentującą.
W Pythonie problem ten rozwiązany jest w sposób następujący: domyślnie, zawartość wczytywana z plików interpretowana jest jako dane tekstowe (chyba, że zażyczymy sobie inaczej), zgodnie z pewnym domyślnym kodowaniem, właściwym dla systemu operacyjnego (chyba, że wskażemy że ma być stosowane inne) - w przypadku Linuxa będzie to prawie zawsze kodowanie o nazwie UTF-8, które się dobrze nadaje do zastosowania dla języków zachodnich, posługujących się systemem pisma opartym na alfabecie łacińskim. Inaczej może być w środowiskach języków posługujących się np. cyrylicą, czy języków azjatyckich (pismo arabskie lub ideograficzne - chińskie, japońskie, koreańskie, itd.). W środowiskach Windows może być inaczej...
Z powyższego widać, że rozróżnienie - plik tekstowy czy binarny - jest dość umowne, i dotyczy raczej interpretacji zawartości pliku (plik zapisany zgodnie z nieznanym nam i/lub nieoczekiwanym kodowaniem jest nie do odróżnienia od pliku binarnego). Co więcej, pliki zapisywane przez programy takie, jak MS Word, lub pliki PDF, nawet zawierające wyłącznie tekst, nie są w tym rozumieniu plikami tekstowymi, tylko binarnymi. W przykładach będziemy mieli do czynienia prawie wyłącznie z plikami tekstowymi - warto jednak wiedzieć, że w Pythonie jest osobny typ danych - ciągi bajtów (bytes), o własnościach nieco podobnych do napisów, ale o elementach będących bajtami, a nie znakami pisma.
Czytanie tekstu z pliku
f = open('nazwa_pliku.txt')
for linia in f:
przetworz(linia)
f.close()
# albo może lepiej:
with open('nazwa_pliku.txt') as f:
for linia in f:
przetworz(linia)
- Otwarty do odczytu plik tekstowy dopuszcza iterację, w której kolejnymi elementami są linie tekstu - wraz z kończącym je kodem przejścia do nowej linii; w ostatniej linijce pliku kodu tego może brakować (lub nie);
- Wartość zmiennej linia to w każdym obiegu pętli, treść kolejnej linii tekstu jako napis; zawartość (bajtowa) pliku jest interpretowana jako napis zgodnie z domyślnym kodowaniem systemowym (można to zmienić poprzez dodatkowy parametr wywołania open);
- Jeżeli zawartość pliku nie jest zgodna z założeniem, że można go interpretować jako tekst w przyjętym kodowaniu, to w trakcie przetwarzania może wystąpić błąd (wyjątek);
- Po zakończeniu przetwarzania plik należy zamknąć, wywołując metodę close; ewentualnie można to pominąć, jeśli wiemy na pewno że wraz z końcem przetwarzania pliku kończy się cały program - wraz z zakończeniem działania programu otwarte pliki zostaną zamknięte;
- Druga postać wprowadza nową instrukcję złożoną - blok with; to, jak on dokładnie działa, zależy od typu obiektu do jakiego odwołujemy się po słowie with - jeżeli jest nim otwarty plik, to zostanie on automatycznie zamknięty wraz z końcem bloku;
- Odwoływanie się do pliku, który już został zamknięty, będzie nieskuteczne; żadne operacje się nie powiodą.
Czytanie dowolnych danych z pliku
f = open('nazwa_pliku', mode='b')
dane = f.read(1024*1024) # wczytujemy 1 MB danych (lub mniej, jeśli tylu już nie ma)
(...)
f.seek(0) # "przewinąć" do początku pliku (lub innej pozycji, względem początku)
(...)
pos = f.tell() # uzyskać aktualną pozycję
(...)
f.close()
- To, co odczytamy - czyli dane, to będzie łańcuch bajtów;
- Można odpowiednio wykorzystać blok with, aby uniknąć ręcznego zamykania pliku;
- Wywołanie read() bez argumentu oznacza: wczytaj wszystko.