Przetwarzanie plików w językach C++ i Python

przez | 7 stycznia 2021

Wszystkie treści na stronie ir.migra.pl chronione są prawami autorskimi. Więcej informacji znajdziesz tutaj.

Uwaga: Zapoznaj się wcześniej z tematem C3 z podręcznika „Teraz bajty. Informatyka dla szkół ponadpodstawowych. Zakres podstawowy. Klasa II” lub z tematem 63  lub 75 z podręcznika „Informatyka 1-3. Podręcznik dla szkoły ponadpodstawowej. Zakres podstawowy”. Wykonaj zawarte tam ćwiczenia i zadania.

Zapisy podstawy programowej 2024 realizowane w temacie:

I. Rozumienie, analizowanie i rozwiązywanie problemów
Zakres rozszerzony. Uczeń spełnia wymagania określone dla zakresu podstawowego, a ponadto:
  1. do realizacji rozwiązania problemu dobiera odpowiednią metodę lub technikę algorytmiczną i struktury danych;
II. Programowanie i rozwiązywanie problemów z wykorzystaniem komputera i innych urządzeń cyfrowych.
Zakres rozszerzony. Uczeń spełnia wymagania określone dla zakresu podstawowego, a ponadto:
  1. projektuje i tworzy programy w procesie rozwiązywania problemów, wykorzystuje w programach dobrane do algorytmów struktury danych, […]
  2. sprawnie posługuje się zintegrowanym środowiskiem programistycznym przy pisaniu, uruchamianiu i testowaniu programów;

Spis treści

  1. Jak przebiega dostęp do plików w językach C++ i Python?
  2. Korzystanie z plików w języku C++
    1. Odczyt danych z pliku w języku C++
    2. Zapis danch do pliku w języku C++
  3. Korzystanie z plików w języku Python
    1. Odczyt danych z pliku w języku Python
    2. Zapis danych z pliku w języku Python
  4. Przetwarzanie plików tekstowych
    1. Przetwarzanie plików tekstowych w języku C++
    2. Przetwarzanie plików tekstowych w języku Python
  5. Obsługa błędów
    1. Obsługa błędów w języku C++
    2. Obsługa błędów w języku Python

1. Jak przebiega dostęp do plików w językach C++ i Python?

Plik możemy porównać do taśmy magnetofonowej. Na takiej taśmie można wykonać trzy operacje: zapisać taśmę pod głowicą magnetofonu (i przesunąć ją dalej), odczytać taśmę pod głowicą magnetofonu (i przesunąć ją dalej) oraz przewinąć taśmę do żądanego miejsca. Podobne operacje możemy wykonywać na plikach: odczytywanie danych, zapisywanie danych i zmianę pozycji w pliku. Funkcję głowicy magnetofonu pełni natomiast wskaźnik pozycji pliku, określający miejsce następnej operacji na pliku.

W języku C++ mówimy ogólnie o strumieniach (danych). Strumień może być powiązany z urządzeniem wejścia (np. klawiaturą), wyjścia (np. oknem konsoli) lub plikiem na dysku. Dostęp do danych przebiega w podobny sposób.

Elementy pliku są uporządkowane, czyli występują w określonej kolejności.
W odróżnieniu od tablic (C++) i list (Python), w których przypadku dostęp do elementów jest swobodny (bezpośredni), dane w plikach muszą być zapisywane po kolei, czyli sekwencyjnie. W tablicy (C++) lub liście (Python) można na przykład zapisać wartość tylko setnego elementu, pisząc instrukcję:

C++
tablica[100] = 123

lista[100] = 123

W przypadku pliku wymagane jest, aby najpierw przejść do pozycji wybranego elementu (przesunąć taśmę). Aby przejść do określonego elementu pliku, należy skorzystać:

  • w języku C++ z metod seekp(), seekg().
  • w języku Python z metody seek().

Przykład sekwencyjnego odczytu danych z pliku pokazano na rysunku 1.

Po przeczytaniu znaku „p” wskaźnik pliku przesuwa się na następną pozycję.

Rys. 1. Plik i wskaźnik pozycji pliku

W tablicy (C++) liczba elementów jest stała, a w pliku może się zmieniać w trakcie wykonywania programu.
W języku C++ do obsługi plików wykorzystuje się standardowo klasy ifstream (do odczytu danych), ofstream (do zapisu danych) oraz fstream (do odczytu i zapisu danych).

W języku Python do obsługi plików wykorzystuje się funkcję open() oraz obiekty plików (ang. file object), służące zarówno do odczytu, jak i do zapisu.

2. Korzystanie z plików w języku C++

W języku C++ plik otwiera się za pomocą metody open. Parametrem tej metody jest nazwa pliku i ewentualnie tryb wykonywania operacji na pliku. Możliwe tryby to:
ios::in – otwiera plik do odczytu,
ios::out – otwiera plik do zapisu,
ios::binary – traktowanie pliku jako pliku binarnego (domyślnie – tekstowego),
ios::ate – ustawia początkową pozycję na końcu pliku (domyślnie – na początku pliku),
ios::app – otwiera plik w trybie dopisywania,
ios::trunc – istniejąca zawartość pliku jest usuwana.
Tryby można łączyć za pomocą bitowego operatora lub ( | ).

Dane do pliku zapisuje się tak samo, jak wyprowadza się je na standardowe wyjście (ekran), czyli za pomocą operatora <<. Dane te zapisywane są w postaci tekstowej (sformatowanej); aby zapisać dane w postaci binarnej, należy użyć metody write().

Dane z pliku wprowadza się tak samo, jak wprowadza się je ze standardowego wejścia (klawiatury), czyli za pomocą operatora >>. Operator zakłada, że dane w pliku zapisane są w postaci tekstowej.
Aby odczytać dane binarne, należy użyć metody read().
Funkcja logiczna eof() pozwala na sprawdzenie, czy osiągnięto już koniec pliku.
Po zakończeniu przetwarzania pliku zamykamy go metodą close().

Ćwiczenie 1. Poznajemy funkcje obsługi plików

Otwórz plik TC3_pliki_C.pdf. Zapisano w nim wszystkie funkcje obsługi plików w języku C++. Przejrzyj ten wykaz.

2.1. Odczyt danych z pliku w języku C++

Przykład 1. Wyświetlanie na ekranie zawartości pliku w języku C++

C++
Program prosi użytkownika o wprowadzenie ścieżki dostępu do pliku, a następnie wypisuje zawartość tego pliku na konsoli, bajt po bajcie.

Uwagi: Odczytując dane z pliku, w większości przypadków używamy pętli while, w której do zapisania warunku użyto wyniku działania operatora >>. Wynik ten będzie wartością NULL, gdy nie ma już danych do odczytania, co spowoduje zakończenie instrukcji while. W ten sposób zabezpieczamy się przed sytuacją próby odczytania danej z pliku, którego koniec już osiągnęliśmy.

Ćwiczenie 2. Analizujemy gotowy program odczytujący dane z pliku i wyświetlający je na ekranie

  1. Otwórz plik TC3_p1.cpp.
  2. Przeanalizuj program z przykładu 1. Wyjaśnij działanie poszczególnych instrukcji.
  3. Uruchom program i przetestuj dla dwóch różnych plików – pobierz pliki tekst.txt i slowa.txt (możesz też utworzyć własny plik tekstowy w Notatniku i zapisać go z rozszerzeniem txt.).
    Wskazówka: Umieść plik tekstowy i plik z zapisanym programem w tym samym miejscu (dysku, folderze). Po uruchomieniu programu wpisz nazwę pliku z rozszerzeniem; nie podawaj ścieżki dostępu do pliku.

Ćwiczenie 3. Piszemy program odczytujący dane z pliku

Napisz program sprawdzający, czy w pliku o nazwie podanej przez użytkownika występuje znak o kodzie ASCII równym 62, i wyprowadzający na ekran stosowny komunikat.

2.1. Zapis danych do pliku w języku C++

Przykład 2. Zapisywanie do pliku znaków wprowadzanych z klawiatury w języku C++

Program pokazany w tym przykładzie zapisuje wszystkie wprowadzone z klawiatury znaki do pliku keylog.txt.

Wprowadzane znaki są dodatkowo wyświetlane na ekranie. Program działa aż do naciśnięcia klawisza Escape, który to znak w kodzie ASCII ma wartość 27.
Funkcja getch() służy do pobrania pojedynczego znaku z klawiatury.

Ćwiczenie 4. Analizujemy gotowy program zapisujący dane do pliku

  1. Otwórz plik TC3_p2.cpp.
  2. Przeanalizuj program z przykładu 2. Wyjaśnij działanie poszczególnych instrukcji.
  3. Uruchom program. Sprawdź zawartość pliku keylog.txt.
    Wskazówka: W wierszu 11 możesz zmienić odpowiednio ścieżkę dostępu do utworzonego pliku keylog.txt.

Ćwiczenie 5. Piszemy program kopiujący zawartość pliku znak po znaku

Napisz program kopiujący zawartość wskazanego pliku do innego pliku. Nazwy pliku źródłowego i docelowego powinien podawać użytkownik.

3. Korzystanie z plików w języku Python

W języku Python plik otwiera się za pomocą wbudowanej funkcji open. Najważniejszym parametrem tej funkcji jest ścieżka do pliku. Jeżeli do funkcji zostanie przekazana wyłącznie ścieżka, to plik zostanie otwarty jako plik tekstowy w trybie tylko do odczytu. Inne tryby możemy ustawić za pomocą parametru mode, którego wartość można złożyć z poniższych znaków:

“r“ – otwiera plik w trybie odczytu (wartość domyślna),

“w“ – jeżeli plik istnieje, to usuwa całą zawartość; jeżeli nie istnieje, to go tworzy; następnie otwiera w trybie zapisu,

“x“ – podobnie jak “w“, ale wykonanie się nie udaje, gdy plik już istnieje,

“a“ – otwiera plik w trybie do zapisu i ustawia początkową pozycję na jego końcu,

“b“ – traktowanie pliku jako pliku binarnego,

“t“ – traktowanie pliku jako pliku tekstowego (wartość domyślna),

“+“ – otwiera plik w trybie odczytu i zapisu,

Te znaki można łączyć w dłuższe łańcuchy. Przykładowo, “w+b“ otwiera plik, czyszcząc jego zawartość, w trybie binarnym do zapisu i odczytu.

Dane do pliku zapisuje się, używając metody write.

Dane z plików wyprowadza się, korzystając z metod read, readline lub readlines.

W zależności od trybu podane metody zwracają i przyjmują jako argumenty albo ciągi znaków str, albo ciągi bajtów bytes.

Bardzo istotne jest zamykanie otwartych plików niezależnie od tego, czy operacje wykonane na pliku się powiodły czy nie. Liczba otwartych plików w tym samym czasie jest ograniczona przez system operacyjny i jej wyczerpanie może doprowadzić do nieoczekiwanych błędów w naszym programie lub w innych aktualnie działających programach. Dlatego należy pamiętać o wywołaniu metody close po zakończeniu przetwarzania pliku lub użyć menedżera kontekstu opisanego w sekcji o obsłudze błędów w języku Python.

3.1. Odczyt danych z pliku w języku Python

Przykład 3. Wyświetlanie na ekranie zawartości pliku w języku Python

Program prosi użytkownika o wprowadzenie ścieżki dostępu do pliku, a następnie wypisuje zawartość tego pliku na konsoli, bajt po bajcie.

Program otwiera plik w trybie binarnym i odczytuje bajty, ponieważ w funkcji open() umieszczono paramatr “br“(czyli “binary read“). W takim trybie nie następuje zamiana bajtów na znaki i wywołanie metody read(n) zwraca n-elementową sekwencję liczb 0-255.

W trybie tekstowym następuje dekodowanie bajtów do znaków według zadanego kodowania i argument n w metodzie read(n) oznacza liczbę znaków do odczytu.


Ćwiczenie 6. Analizujemy gotowy program odczytujący dane z pliku i wyświetlający je na ekranie

  1. Otwórz plik TC3_p3.py.
  2. Przeanalizuj program z przykładu 3. Wyjaśnij działanie poszczególnych instrukcji.
  3. Uruchom program i przetestuj dla dwóch różnych plików – pobierz pliki tekst.txt i slowa.txt (możesz też utworzyć własny plik tekstowy w Notatniku i zapisać go z rozszerzeniem txt.).
  4. Usuń z funkcji open() (umieszczonej w drugim wierszu programu) parametr „br” i przetestuj program dla tych samych plików tekstowych. Porównaj wyświetlone wyniki. Co zauważasz?
    Wskazówka: Umieść plik tekstowy i plik z zapisanym programem w tym samym miejscu (dysku, folderze). Po uruchomieniu programu wpisz nazwę pliku z rozszerzeniem; nie podawaj ścieżki dostępu do pliku.

Ćwiczenie 7. Piszemy program odczytujący dane z pliku

Napisz program sprawdzający, czy w pliku o nazwie podanej przez użytkownika występuje znak o kodzie ASCII równym 62 (czyli bajt o tej wartości), i wyprowadzający na ekran stosowny komunikat.

3.2. Zapis danych do pliku w języku Python

Przykład 4. Zapisujemy dane do pliku w języku Python

Program pokazany w tym przykładzie zapisuje wszystkie wprowadzone z klawiatury linie tekstu do pliku notatki.txt.


Ćwiczenie 8. Analizujemy gotowy program zapisujący dane do pliku

  1. Otwórz plik TC3_p4.py.
  2. Przeanalizuj program z przykładu 4. Wyjaśnij działanie poszczególnych instrukcji.
  3. Uruchom program. Sprawdź zawartość pliku notatki.txt.
    Uwaga: Plik notatki.txt zostanie utworzony w tym samym miejscu (dysku, folderze) co jest zapisany program).

Ćwiczenie 9. Piszemy program kopiujący zawartość pliku

Napisz program kopiujący zawartość wskazanego pliku do innego pliku. Nazwy pliku źródłowego i docelowego powinien podawać użytkownik.

4. Przetwarzanie plików tekstowych

Szczególnym przypadkiem pliku jest plik tekstowy. W pliku tekstowym dane również są zapisane jako bajty, jednak można je interpretować jako tekst. Potrzebna jest do tego znajomość sposobu zamiany bajtów na litery i inne znaki (i odwrotnie) czyli kodowanie. Podstawowym przykładem kodowania jest kodowanie ASCII, które obejmuje 128 znaków, włączając w to alfabet łaciński, podstawowe symbole (np. spacja, dwukropek, średnik) oraz specjalne kody kontrolne, oznaczające np. przejście do nowej linii. Wszystkie znaki ASCII można przedstawić, używając jednego bajtu. Dodatkowo, pierwszy bit każdego takiego bajtu zawsze jest zerem. Stąd właśnie ASCII obejmuje jedynie 128=27 (a nie 256=28) znaków.

Kodowanie ASCII nie obejmuje polskich znaków, takich jak “ą“ czy “ł“ oraz znaków używanych w dalekowschodnich czy indyjskich systemach pisma. Dlatego też obecnie najczęściej używane są sposoby kodowania takie jak kodowanie UTF-8, które umożliwia użycie większej liczby bajtów do zakodowania pojedynczego znaku, dzięki czemu może być używane we wszystkich krajach. Jednak warto zauważyć, że UTF-8 jest nadzbiorem ASCII. Czyli każdy kod zgodny z ASCII jest również poprawnym kodem UTF-8 i oznacza ten sam znak.

Plik tekstowy składa się z ciągu wierszy (koniec wiersza oznaczony jest symbolem końca wiersza, czyli w systemie Windows sekwencją znaków ASCII CR i LF o kodach odpowiednio 13 i 10). W innych systemach używany jest jedynie znak LF. Ponieważ używane jest standardowe kodowanie, plik taki można przygotować, korzystając z dowolnego edytora tekstu.

Uwaga: Na końcu pliku tekstowego może wystąpić znak kodu ASCII SUB (26), oznaczający w niektórych systemach operacyjnych koniec pliku EOF.

CR (ang. carriage return)– powrót karetki (przejście na początek linii).
LF (ang. line feed) – przejście do nowej linii.
SUB (ang. substitute) – zastąpienie (oryginalnie oznaczało znak błędny lub nieznany w ASCII).
EOF (ang. end of file) – koniec pliku.

4.1. Przetwarzanie plików tekstowych w języku C++

Dane z plików tekstowych można odczytywać również linia po linii, wynik odczytu zapamiętując w zmiennej typu string. Służy do tego funkcja getline().

Przykład 5. Wyświetlanie na ekranie zawartości pliku tekstowego w języku C++

Program wypisuje zawartość pliku tekstowego wskazanego przez użytkownika, poprzedzając zawartość każdej linii jej numerem porządkowym.

Ćwiczenie 10. Wyświetlamy na ekranie zawartość pliku bez pustych wierszy

  1. Otwórz plik TC3_p5.cpp.
  2. Przeanalizuj program z przykładu 5. Wyjaśnij działanie poszczególnych instrukcji.
  3. Uruchom program i przetestuj dla pliku slowa.txt (możesz też utworzyć własny plik tekstowy w Notatniku i zapisać go z rozszerzeniem txt.).
  4. Zmodyfikuj program, aby nie wypisywał pustych wierszy.

4.2. Przetwarzanie plików testowych w języku Python

W języku Python pliki tekstowe otwiera się, wykorzystując tryb „t„. Można również wybrać kodowanie, dodając kolejny argument do wywołania funkcji open(). Jeśli programista nie wybierze konkretnego kodowania, to zostanie użyte kodowanie specyficzne dla systemu operacyjnego i właściwości lokalnych. Można je sprawdzić, wywołując funkcję getencoding (lub getpreferredencoding w wersjach Pythona poniżej 3.11) z modułu locale, jak w poniższym przykładzie:


    import locale
    print(locale.getencoding())

Dane z plików tekstowych można odczytać również linia po linii, jak w przykładzie 6.

Przykład 6. Wyświetlanie na ekranie zawartości pliku tekstowego w języku Python

Program wypisuje zawartość pliku tekstowego wskazanego przez użytkownika, poprzedzając zawartość każdej linii jej numerem porządkowym. Zauważmy, że funkcja print() domyślnie wypisuje podany ciąg znaków i przechodzi do nowej linii. Dodatkowo odczytany wiersz zawiera znak końca linii, więc możemy wypisać dwa takie znaki, tworząc niepotrzebny odstęp. W programie jednak użyliśmy parametru end, ustawiając go na pusty ciąg znaków (a nie znak nowej linii, jak jest domyślnie), aby uniknąć tego podwojenia.

Ćwiczenie 11. Wyświetlamy na ekranie zawartość pliku bez pustych wierszy

  1. Otwórz plik TC3_p6.py.
  2. Przeanalizuj program z przykładu 6. Wyjaśnij działanie poszczególnych instrukcji.
  3. Uruchom program i przetestuj dla pliku slowa.txt (możesz też utworzyć własny plik tekstowy w Notatniku i zapisać go z rozszerzeniem txt.).
  4. Zmodyfikuj program, aby nie wypisywał pustych wierszy.

5. Obsługa błędów

W przypadku dostępu do plików zawsze należy liczyć się z możliwością wystąpienia błędów (brak pliku o podanej nazwie, brak miejsca na dysku, dysk zabezpieczony przed zapisem itp.). Jeżeli nie obsłużymy odpowiednio tych błędów, program będzie niespodziewanie przerywał działanie (błąd wykonania programu).

5.1. Obsługa błędów w języku C++

W języku C++ do obsługi błędów można użyć funkcji is_open(), good(), bad() i fail(), sprawdzających stan otwartego pliku.

Przykład 7. Obsługa błędów w programie w języku C++

Funkcja perror() wypisuje podany przez system operacyjny komunikat o błędzie, poprzedzając go tekstem podanym jako parametr wywołania funkcji (w tym przypadku – nazwą pliku).

C++

Ćwiczenie 12. Analizujemy gotowy program z obsługą błędów

  1. Otwórz plik TC3_p7.cpp. W plikach zapisano program wyświetlający na ekranie zawartość pliku (wersja z obsługą błędów).
  2. Zapoznaj się z kodem programu i uruchom go.

Ćwiczenie 13. Modyfikujemy program

Uzupełnij program do kopiowania zawartości pliku z ćwiczenia 9. o obsługę błędów.

5.2. Obsługa błędów w języku Python

W języku Python błędy są sygnalizowane przez wystąpienie błędu wykonania. Taki błąd przerywa zwykłe wykonywanie programu do miejsca, które obsługuje błędy, lub w ogóle kończy wykonywanie programu po wyświetleniu informacji o błędzie. Jest to o tyle istotne, że należy zawsze pamiętać o zamykaniu plików otwartych w programie niezależnie od wystąpienia błędów. Można to osiągnąć na wiele sposobów pokazanych w przykładzie 8.

Przykład 8. Obsługa wyjątków w języku Python z użyciem słów kluczowych try i with


# użycie try/except/finally
f = open("plik")
try:
    # operacje na pliku
except:
    # blok uruchamiany w przypadku wystąpienia każdego błędu
    print("Wystąpił problem z przetwarzanie pliku")
finally:
    # blok uruchamiany zawsze, niezależnie od wystąpienia błędów
    f.close()
# użycie zwięzłej składni menedżera kontekstu
# plik zawsze zostanie zamknięty niezależnie wystąpienia błędów
with open("plik") as f:
    # operacje na pliku f można wykonać wyłącznie w tym bloku
    # błędy, które wystąpią w tym bloku można obsłużyć

Błędy w operacjach na plikach w języku Python są podklasami klasy OSError i dlatego można je obsłużyć, pisząc:
except OSError as e
tak jak w przykładzie 9.

Przykład 9. Obsługa błędów w programie w języku Python

Ćwiczenie 14. Analizujemy gotowy program z obsługą błędów

  1. Otwórz plik TC3_p9.py. W plikach zapisano program wyświetlający na ekranie zawartość pliku (wersja z obsługą błędów).
  2. Zapoznaj się z kodem programu i uruchom go.
  3. Spróbuj podać do programu nazwę pliku, który nie istnieje. Jak wtedy zachowa się program?

Ćwiczenie 15. Modyfikujemy program

Uzupełnij program do kopiowania zawartości pliku z przykładu 3 lub 4 o obsługę błędów.

Wskazówka: Skorzystaj z pliku TC3_p3.py lub TC3_p4.py.

Zadania

Uwaga: Zadania 1-6 możesz rozwiązać w języku C++ lub Python, natomiast zadania 7. i 8. wykonaj w języku Python.

  1. Napisz program łączący zawartość dwóch plików i zapisujący ją w trzecim pliku. Nazwy wszystkich trzech plików powinien podać użytkownik.
  2. Napisz funkcję FileExists(), sprawdzającą, czy plik o podanej nazwie istnieje na dysku. W języku C++ wykorzystaj metody open() i is_open(), w języku Python wykorzystaj funkcję open() i obsłuż błąd FileNotFoundError.
  3. Otwórz plik tekstowy slowa.txt. W pliku zapisano tekst zawierający nie więcej niż 100 słów, z których każde zapisane jest w jednym wierszu. Napisz program odczytujący zawartość pliku slowa.txt i tworzący plik slowa2.txt, zawierający te same słowa, ale zapisane w odwrotnej kolejności.
  4. Pobierz plik liczby.txt. W pliku zapisano nie więcej niż 1000 liczb naturalnych z przedziału〈1; 100〉, w każdym wierszu po jednej liczbie. Napisz program odczytujący zawartość pliku i wypisujący liczbę, która wystąpiła najczęściej.
  5. Dla zainteresowanych

  6. Mając dany plik slowa.txt, napisz program sortujący słowa w porządku alfabetycznym (rosnąco) i zapisujący wyniki sortowania w pliku slowa3.txt.
  7. Znajdź w Internecie informacje na temat formatu CSV. Napisz program umożliwiający odczyt i zapis danych w tym formacie.
  8. Kodowanie ASCII nie obejmuje polskich znaków. Sprawdź, co się stanie, gdy mimo to spróbujesz je zapisać tak, jak w przykładzie poniżej. Wynikowy plik otwórz w Notatniku. Dodaj do wywołania funkcji open() argument errors=”replace” albo errors=”namereplace”. Jak wtedy zachowa się program?
    f = open("polskie_znaki.txt", "wt", encoding="ascii")
    f.write("Zażółć gęślą jaźń")
    f.close()
  9. Kodowanie UTF-8 obejmuje wszystkie polskie znaki. Sprawdź, co stanie się, gdy zapiszesz je używając tego kodowania i spróbujesz odczytać korzystając z kodowania ASCII. Spróbuj znów dodać argument  errors="replace". Jak wtedy zachowa się program? Wykorzystaj poniższy fragment:
    f = open("polskie_znaki.txt", "wt", encoding="utf8")
    f.write("Zażółć gęślą jaźń")
    f.close()