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)
# jeszcze inaczej
tresc = open('nazwa_pliku.txt').read() # wczytujemy od razu całą treść pliku
- 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ą.
- Ostatnia wersja jest odrobinę ryzykowna - jeśli plik jest bardzo duży, to na jego treść może nie wystarczyć pamięci RAM, i program się wywróci. W dzisiejszych czasach oznacza to jednak, że rozmiar pliku sięga wielu gigabajtów.
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;
- Jeśli wywołanie read z dodatnim argumentem zwróci łańcuch o długości zero - to dotarliśmy do końca pliku.
Zapis danych do pliku
Aby był możliwy zapis danych do pliku dyskowego, należy go otworzyć w trybie do zapisu:
f = open('nazwa_pliku.txt', 'w')
(...)
f.write(dane)
(...)
f.close()
UWAGA: otwarcie pliku w trybie do zapisu ('w') spowoduje usunięcie poprzedniej zawartości pliku - o ile dotyczy pliku już istniejącego. Jeżeli nazwa odnosi się do pliku jeszcze nieistniejącego, to zostanie on utworzony. Jeżeli chcemy zachować poprzednie dane, dopisując nowe do końca pliku, należy plik otworzyć w trybie 'a' (append).
Domyślnie plik jest otwierany w trybie zapisu tekstu, zgodnie z domyślnym kodowaniem. A więc dane powinny być napisem. Jeżeli chcemy zapisywać surowe bajty, należy użyć trybu 'wb'. Jeżeli chcemy użyć kodowania innego niż domyślne, możemy w funkcji open użyć parametru encoding, w postaci:
f = open('nazwa_pliku.txt', 'w', encoding='cp1250')w tym przykładzie użyto nazwy kodowania stosowanego do języka polskiego w Windows.
Standardowe strumienie i przekierowania
Odczyt linii poleceń
Zawartość linii poleceń dostępna jest w obiekcie sys.argv - czyli moduł (standardowy) sys, element argv. Jest to lista argumentów - ,,słów" jakie wywołujący program umieścił w linii poleceń. Inaczej mówiąc, treść linii poleceń ulega wstępnemu rozbiorowi - na słowa, według reguł zależnych od systemu operacyjnego. Najczęściej poszczególne słowa oddzielają spacje (jedna lub więcej), jeśli chcemy, by ciąg zawierający spacje był potraktowany jako pojedyncze słowo (np. nazwa pliku zawierająca spacje), należy ciąg ten np. umieścić w cudzysłowach (które zostaną usunięte z treści argumentu). Treść linii poleceń na ogół ulega jeszcze innym formom obróbki przez system operacyjny (np. rozwijanie rozmaitych skrótów), ale dzieje się to poza kontrolą Pythona.
from sys import argv
for arg in argv:
print(arg)
Korzystając z powyższego kodu możemy dowiedzieć się, jak wygląda lista argumentów już po jej obróbce przez system operacyjny.
Na początku listy argv czyli w pozycji arv[0] znajduje się nazwa uruchomionego programu (tj. nazwa pliku z kodem uruchomionego jako program główny).
Przykład
Jeśli przewidujemy, że jedynymi argumentami wywołania naszego programu będą nazwy plików, na każdym z których należy wykonać jakąś czynność, to można to zrealizować tak:
#! /usr/bin/python3
from sys import argv
def przetwarzaj(plik):
# tu określamy na czym polega "przetworzenie" pliku
for plik in argv[1:]:
przetwarzaj(plik)
- Wzięcie wycinku z argv służy pominięciu pliku zawierającego kod programu;
- Elementami argv są nazwy plików - napisy.