Operacje na plikach

Dotychczas wszystkie programy operowały na danych już wprowadzonych (zmienne, np napisy) lub wylosowanych. Najczęściej jednak dane do programów wczytujemy z plików. Praca z plikiem ma 3 etapy: plik trzeba otworzyć, przeczytac (ew. zapisać) a na końcu zamknąć:

1# Otwarcie pliku
2plik = open("plik.txt")
3# wczytanie zawartości
4zawartosc = plik.read()
5# zamknięcie pliku
6plik.close()

Uwaga

Omówiony podczas tego wykładu materiał umożliwia jedynie wczytywanie bądź zapisywanie plików tekstowych. Jeżeli chcesz samemu przygotować taki plik, zrób to w Notatniku (na Windows). Niestety nie można w ten sposób wczytywać plików .docx czy .pdf. Będzie to możliwe po zainstalowaniu dodatkowych bibliotek (modułów) języka Python.

Do otwierania pliku w Pythonie służy instrukcja open(), która przyjmuje co najmniej jeden argument: nazwę pliku z ewentualną ścieżką. Drugim (opcjonalnym) argumentem jest litera oznaczająca przeznaczenie otwartego pliku:

  • 'r' (read) - plik otwarto do odczytu - jest to wartość domyślna; innymi słowy: open("plik") oznacza dokładnie to samo, co open("plik",'r'); w tak otwartym pliku nie uda się nic zapisać

  • 'w' (write) - plik otwarto do zapisu; jeżeli plik już istnieje, jego zawartośc zostanie skasowana

  • 'a' (append) - plik otwarto do zapisu w trybie dopisywania

Uwaga

Python działający w przeglądarce nie może wczytywać plików. W tym wykładzie skorzystamy pewnego tricku, w którym zmienną napisową (tekst) zamienimy na plik. Umożliwi nam to obiekt StringIO z modułu io. W przykładach, które uruchamiacie na swoich komputerach poza przeglądarką (np w środowisku PyCharm lub w terminalu), linię plik = io.StringIO(napis) powinniście zamienić na plik = open("nazwa_pliku"). Plik ten oczywiście musi być nagrany na dysku Waszego komputera a jego zawartość - taka jak w przykładach.

Czytanie z pliku

Zacznijmy od wczytywania zawartości pliku, co można zrobić na kilka sposobów. Skrypt powyższy przetwarza plik linijka po linijce (pętla w liniach 8 i 9). Ponieważ nie wczytujemy całego pliku na raz do pamięci, w ten sposób możemy przetwarzać nawet bardzo duże pliki. Jeżeli jednak zależy nam na załadowaniu całego pliku, możemy użyć instrukcji read(), jak pokazano poniżej w linii 10:

Jak widać, instrukcją readlines() (linia 16) można wczytać wszystkie linie pliku jako listę. Na końcu programu raz jeszcze wczytujemy ten plik linija po linii, korzystając z pętli (linie 20,21).

Uwaga

W podanych tu przykładach, uruchamianych w przeglądarce, niestety nie widać, że Python wczytuje linie wraz z kończącym je znakiem końca linii, czyli '\n'. Wykonanie powyższego programu w terminalu (lub w środowisku PyCharm) da wynik jak poniżej:

Ala ma kota.
Kot ma Alę.
Zuza ma psa.

['Ala ma kota.\n', 'Kot ma Alę.\n', 'Zuza ma psa.\n']
Ala ma kota.

Kot ma Alę.

Zuza ma psa.

Jak widać, ponowne wydrukowanie linii powoduje, że pojawiają się też linie puste. Zapobiegamy temu stosując poznaną niedawno metodę linia.strip().

Zapis do pliku

Zapisywanie jest równie proste, jak czytanie; służy do tego instrukcja write(). Poniższy przykład zapisuje dwie kolumny liczb losowych do pliku:

1from random import random
2
3plik = open("losowe.txt", 'w')
4for i in range(100):
5#  plik.write(random()," ",random(),"\n")
6  plik.write("%f %f \n" % (random(),random()))
7plik.close()

Uwaga

Otwarte pliki koniecznie należy zamknąć!

A co jeśli nie zamknę pliku?

Otwarty w programie plik może powodować problemy. Pliki otwarte do zapisu mogą “zniknąć” albo stracić część danych. Może się też okazać, że inne aplikacja nie może odczytać pliku, który pozostaje otwarty. Jeżeli korzystasz z najpopularniejszej (i domyślnej) dystrybucji CPython, to otwarte pliki zostaną zamknięte automatycznie przy końcu programu. Nie każda dystrybucja jednak robi to za nas. Dobrą praktyką jest robienie tego samodzielnie.

Instrukcja with

Choć instrukcja with wygląda zupełnie inaczej, niż przykłady powyżej, w praktyce robi prawie to samo: otwiera plik, który można np przeczytać:

with open('plik.txt') as plik:
    for linia in plik:
        print linia.strip().split()

Dodatkowo instrukcja with pilnuje, aby zamknąć plik. Robi to nawet wtedy gdy coś pójdzie nie tak, np. nastąpi jakiś wyjątek.

Powyższy program to typowy przykład przetwarzania plików za pomocą Pythona. Program otwiera plik (linia 7), następnie w pętli biegnie po wszystkich jego liniach (wiersz 8). W wierszu 9 każda z linii jest przetwarzana: obcinane są białe znaki z obydwu końców linii a nastepnie linia dzielona jest na wyrazy. Instrukcja with gwarantuje nam, że plik zostanie poprawnie zamknięty po zakończeniu przetwarzania. Często jednak potrzeba jednocześnie przetwarzać dwa pliki; dobrym przykładem jest sytuacja, w której czytamy dane z jednego pliku, analizujemy je, a wyniki na bieżąco nagrywamy do drugiego pliku. Poniższy przykład pokazuje, jak zrobić to stosując instrukcję with:

 1import re
 2from sys import argv
 3
 4
 5info = """\ngrep.py: szuka wzorca w pliku
 6
 7Użycie:
 8	python3 grep.py plik_we "wzorzec" plik_wy\n
 9"""
10
11def grep(plik_we, wyrazenie, plik_wy):
12	regex = re.compile(wyrazenie)
13	with open(plik_we) as plik_we, open(plik_wy,'w') as plik_wy:
14		for linia in plik_we:
15			if regex.search(linia) : 
16				plik_wy.write(linia)
17
18if __name__ == "__main__":
19	if len(argv) != 4 : print(info)
20	else:
21		grep(argv[1], argv[2], argv[3])

Powyższy program naśladuje Linuxowy program grep. Pobiera z linii poleceń nazwę pliku wejściowego, wyrażenie regularne oraz nazwę pliku wyjściowego. W pliku wyjściowym nagrane zostaną tylko te linie, w których znaleziony zostanie podany wzorzec. Przy okazji program podsumowuje materiał, z którym zapoznaliście się już do tej pory:

  • linia 1 importuje cały moduł re, podczas gdy w linii 2 importowana jest jedynie lista argv (importowanie opisano tutaj)

  • w liniach 5-9 stworzona jest zmienna - napis o wielu liniach (fragment tekstu)

  • w linii 11 (deklarujemy funkcję) o 3 argumentach

  • w linii 12 kompilujemy wyrażenie regularne (tutaj), które zostanie wykorzystane w tej funkcji

  • w linii 13 stosujemy instrukcję with do jednoczesnego otwarcia dwóch plików

  • linia 14 to pętla po wierszach z pliku

  • a linia 15 zawiera warunek sprawdzający, czy dana linia pasuje do wyrażenia regularnego

  • pasujące linie nagrywane są w linii 16 (sekcję main opisano tutaj)

  • w linii 18 sprawdzamy, czy powyższy program został uruchomiony, czy jest importowany

  • w linii 19 zaś liczymy, ile argumentów mu przekazano (listę argumentów argv opisano tutaj)

Podstawowe operacje na plikach i katalogach

Dotychczas zakładaliśmy, że pliki zawsze są tam, gdzie ich szukamy. Co jednak zrobić, kiedy nie wiemy, jaki plik chcemy wczytać? A jak sprawdzić, czy dany plik istnieje? Odpowiedzi na te pytania należy szukać w modułach standardowej biblioteki Pythona:

  • os.path.isfile(ścieżka) - sprawdza, czy podany plik wraz z podaną ścieżką istnieje i czy naprawdę jest plikiem; zwraca prawdę lub fałsz

  • os.path.exists(ścieżka) - sprawdza, czy podana ścieżka istnieje, nie koniecznie musi ona być plikiem

  • os.path.isdir(ścieżka) - sprawdza, czy podana ścieżka jest istniejącym katalogiem

  • glob.glob(ścieżka_maska) - wyszukuje wszystkie pliki wg podanej maski, np: glob.glob("*.gif") zwróci listę wszystkich plików z bieżącego katalogu, które kończą się na ".gif"

  • os.getcwd() zwraca ścieżkę do bieżącego katalogu, w którym aktualnie wykonywany jest program. Oznacza to na przykład to, że pliki otwarte instrukcją open("plik",'w') pojawią się w tym właśnie katalogu

  • os.mkdir('folder') - zakłada nowy katalog (folder) w katalogu bieżącym

Praca domowa

Poniższy program drukuje na ekranie zawartość pliku linijka po linijce. Zmodyfikuj go tak, aby obliczał wartość średnią z liczb w drugiej kolumnie. Pamiętaj przy tym o:

  • sprawdzeniu, czy linia aby nie jest pusta

  • obcięciu białych znaków z końców linii przed podzieleniem jej na wyrazy

  • tym, że każdy wyraz jest zmienną napisową, którą trzeba zamienić na liczbę