Zagrożenia przy operacji na plikach

Podczas operacji na plikach powinniśmy być świadomi zagrożeń, jakie cały czas na nas czyhają.

Dwa najczęstsze niebezpieczeństwa to:

  • wyjątki podczas operacji na plikach,
  • brak poprawnego zamknięcia pliku (co jest często rezultatem poprzedniego problemu).

W celu lepszego zobrazowania problemu i rozwiązania posłużymy się prostym przykładem 😉 Spójrz na poniższy kod:

file = open("./testFiles/fileToWrite.txt", mode='w')
file.write('Hello jaktestowac.pl!')
file.close()

Powyższy kod zapisuje do pliku wskazany przez nas ciąg znaków. Nic skomplikowanego 😉

Skopiujmy go do nowego pliku write_file_safe.py, w którym przeprowadzimy kilka praktycznych eksperymentów. Będą one polegały na wprowadzeniu intencjonalnego błędu oraz pokazaniu jak wyglądają zabezpieczenia 😉

Wprowadzenie błędu

Skorzystamy tutaj z domyślnego tryb odczytu, który kryje się pod wartością r. W tym trybie (jak już się przekonaliśmy 😉 ), zawartość pliku może zostać jedynie odczytana, a wszystkie próby modyfikacji kończą się wyjątkiem.

Wprowadźmy do naszego kodu z write_file_safe.py niewielką zmianę, która spowoduje wystąpienie wyjątku podczas zapisu do pliku.

Zmiana ta będzie polegała na ustawieniu trybu dostępu do pliku jako tylko do odczytu. Można to osiągnąć poprzez usunięcie frazy mode=’w’ ze skryptu. Ustawimy jednak tryb tylko do odczytu w widoczny sposób mode='r'.

Zmiana spowoduje błąd podczas wywołania metody write(). Cały kod w pliku write_file_safe.py będzie wyglądał następująco:

file = open("./testFiles/fileToWrite.txt", mode='r')
file.write('Hello jaktestowac.pl!')
file.close()

Po uruchomieniu powyższego kodu powinniśmy otrzymać następujący wynik:

Traceback (most recent call last):
  File "C:/Projects/pythonFiles/write_file.py", line 2, in 
    file.write('Hello jaktestowac.pl!')
io.UnsupportedOperation: not writable

Jak widzisz wystąpił wyjątek io.UnsupportedOperation: not writable, czyli coś czego się spodziewaliśmy 😉

Mamy tu jednak problem. Program zatrzymał swoje wykonanie na linii nr 2 (file.write('Hello jaktestowac.pl!')) co oznacza, że nie został wykonany kod file.close(). A wiemy, że zamykanie plików jest extremalnie ważne!

Teraz zobaczmy jak możemy się przed takim problemem uchronić…

Próba zażegnania zagrożeń

Pierwszym krokiem do zażegnania zagrożenia jest użycie konstrukcji do łapania wyjątków, czyli try… except.

TIP: O konstrukcji try… except wspomnielismy wcześniej w lekcji Konstrukcja try… except w praktyce 😉

Przypomnijmy szybko jak działa łapanie wyjątków:

try:
   # tutaj wklejamy kod który może rzucić wyjątkiem
   # umieszczony tutaj kod wykona się zawsze aż do linii z wyjątkiem
except Exception as e:
   # tutaj łapiemy wyjątek - program nie jest przerywany a wykonywany dalej
   # umieszczony tutaj kod wykona się tylko gdy nastąpi wyjątek
finally:
   # tutaj dodajemy kod który ma się wykonać po niebezpiecznych operacjach
   # umieszczony tutaj kod wykona się zawsze po powyższych blokach

W naszym przypadku wykorzystamy trzy sekcje konstrukcji try… excepttry, except oraz finally w następujący sposób:

try:
   # otwarcie pliku w trybie tylko do odczytu
   # próba zapisu do pliku
except Exception as e:
   # wypisanie wyjątku
   print(e)
finally:
   # zamknięcie pliku

Teraz pozostaje nam w obecny kod w pliku write_file_safe.py odpowiednio wpasować w konstrukcję try… except:

file = open("./testFiles/fileToWrite.txt", mode='r')
file.write('Hello jaktestowac.pl!')
file.close()

Po uzupełnieniu wszystkich sekcji konstrukcji try… except całość przyjmie postać:

try:
   file = open("./testFiles/fileToWrite.txt", mode='r')
   file.write('Hello jaktestowac.pl!')
except Exception as e:
   print(e)
finally:
   file.close()

Wynik działania powyższego kodu jest mało imponujący:

not writable

Nasz błąd jednak nie wystąpił.

Co jednak z zamknięciem pliku? Czy się udało?

Użyjmy print(), aby zobaczyć, czy plik jest zamknięty. Aby to zrobić wystarczy, że odczytamy wartość pola file.closed. Pole closed zawiera informacje o tym, czy plik jest otwarty czy nie. Jeśli użycie file.closed zwróci wartość True oznacza, że plik jest zamknięty, natomiast wartość False oznacza ciągle otwarty plik.

TIP: Pole closed jest typu Bool czyli zwraca wyłącznie wartość True lub False.

Teraz umieścimy print() w bloku except oraz w bloku finally. W każdym wywołaniu print() wpiszemy dwie informacje:
1. W którym bloku wywołujemy funkcję print().
2. Wartość pola file.closed.
W rezultacie całość przyjmie postać:

try:
   file = open("./testFiles/fileToWrite.txt", mode='r')
   file.write('Hello jaktestowac.pl!')
except Exception as e:
   print(e)
   print(f'In block "except". Is file closed? {file.closed}')
finally:
   print(f'In block "finally" before close(). Is file closed? {file.closed}')
   file.close()
   print(f'In block "finally" after close(). Is file closed? {file.closed}')

Po wykonaniu powyższego kodu powinniśmy otrzymać na konsoli:

not writable
In block "except". Is file closed? False
In block "finally" before close(). Is file closed? False
In block "finally" after close(). Is file closed? True

W tym momencie upewniliśmy się, że:
Po pierwsze, plik nie jest zamykany po samym wystąpieniu wyjątku.
Po drugie, zastosowana konstrukcja faktycznie zamyka nasz plik w przypadku wystąpienia wyjątku. Aczkolwiek, jak zapewne sam poczułeś, musieliśmy wyprodukować strasznie dużo kodu, aby poprawnie zakończyć operacje na pliku.

Czy można zrobić to prościej?

Można! 😀

W kolejnej lekcji pokażemy Ci instrukcję with, która znacznie upraszcza operacje na plikach 😉

To po co był ten przykład? Skoro i tak będziemy robić inaczej?

Dzięki wyżej wymienionej konstrukcji jesteśmy w stanie dość dokładnie stwierdzić kiedy i czy następuje dana operacja. Bardzo chcieliśmy pokazać jak sprawdzić kiedy plik jest zamykany. Możesz ten sposób wykorzystać przy podobnych badaniach.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *