03 - Przetwarzanie danych tekstowych

Podstawy przetwarzania danych

Politechnika Poznańska, Instytut Robotyki i Inteligencji Maszynowej

Ćwiczenie laboratoryjne 3: przetwarzanie danych tekstowych

Powrót do spisu treści ćwiczeń laboratoryjnych


Przetwarzanie danych tekstowych

Przetwarzanie danych tekstowych jest jednym z fundamentalnych zadań programistycznych. Jest ono niezbędne w większości sytuacji wymagających długotrwałego przechowywania informacji, także po zakończeniu działania skryptu.

Obsługa plików

Aby rozpocząć pracę z plikiem tekstowym w pierwszej kolejności należy go otworzyć. W pythonie ogranicza się to do skorzystania z wbudowanej funkcji open(). Zwraca ona uchwyt do pliku. Pierwszym argumentem przyjmowanym przez tę funkcję jest ścieżka do pliku. Domyślnie ścieżka zaczyna się od katalogu roboczego, czyli katalogu w którym uruchomiony został skrypt. Jeżeli interesujący nas plik znajduje się w tym samym katalogu jako argument podajemy jedynie jego nazwę.

open("text_file.txt")

Jeżeli plik znajduje się w innej lokalizacji, jako argument możemy podać ścieżkę bezwzględną. W systemie operacyjnym Windows zaczyna się ona od litery dysku, np. C:\ a w rodzinie systemów operacyjnych Linux będzie to katalog główny /. Należy przy tym pamiętać, że użycie ścieżki bezwzględnej znacząco ogranicza przenośność naszego skryptu. Następnym argumentem funkcji open() jest tryb otwarcia pliku. Domyślnym trybem jest tryb odczytu pliku tekstowego r lub rt.

Znak Znaczenie
"r" Read - Domyślna wartość. Otwiera plik do odczytu, zwraca błąd jeżeli plik nie istnieje.
"w" Write - Otwiera plik do zapisu, ucina zawartość, utworzy plik jeżeli nie istnieje.
"x" Exclusive creation - Otwiera plik do utworzenia, zwraca błąd jeżeli plik istnieje.
"a" Append - Otwiera plik w trybie do dopisywania, utworzy plik jeżeli nie istnieje.
"b" Binary - Otwiera plik w trybie binarnym.
"t" Text - Domyślna wartość. Otwiera plik w trybie tekstowym.

Więcej informacji o funkcji można znaleźć tutaj.

Odczyt danych

Jak zostało wspomniane w podrozdziale Obsługa plików, funkcja open() nie zwraca zawartości pliku a jedynie uchwyt do niego. Do odczytania zawartości można użyć metod read() i readline(). Pierwsza z nich odczyta całość pliku, a druga będzie odczytywać kolejne linie tekstu. Ostatnia metoda to close(). Dobrą praktyką jest użycie jej kiedy praca z plikiem zostanie zakończona. Jest to szczególnie istotne przy zapisywaniu danych do pliku, ponieważ jego zamknięcie gwarantuje, że skrypt nie zakończy działania w trakcie tego procesu. Aby wypróbować poniższy przykład pobierz file.txt.

file = open("file.txt")
contents = file.read()
print(contents)
file.close()

print("---")

file = open("file.txt")
line1 = file.readline()
line2 = file.readline()
print(line1)
print(line2)
file.close()

Jako opcjonalny argument w obu metodach sprecyzować można ilość bajtów do odczytania. Aby upewnić się, że plik zawsze zostanie poprawnie zamknięty, a przy okazji poprawić czytelność kodu, użyć można wyrażenia with. Przy jego użyciu plik zostanie zamknięty automatycznie po zakończeniu wykonywania bloku kodu.

with open("file.txt") as file:
    contents = file.read()
    print(contents)

W przypadku nieistniejącego pliku python zatrzyma wykonywanie skryptu i wyświetli w konsoli błąd. Aby temu zapobiec użyć można bloków try-except. Except wywoła się tylko wtedy, kiedy zwróconym przez skrypt błędem będzie FileNotFoundError. Więcej wbudowanych typów błędów.

try:
    with open("file.txt") as file:
        file.read()
except FileNotFoundError:
    print("file.txt nie istnieje")

💥 Zadanie 1 💥

Wykorzystaj powyższy przykład i pętlę aby odczytać całość pliku broken_sentence.txt i scalić ze sobą kolejne linie. Kiedy readline() osiągnie koniec pliku zwróci pusty string, wykorzystaj to w swojej pętli.

Podpowiedź

Jak można zaobserwować metoda readline() zwraca nam także znak nowej linii \n. Usunąć go można na przykład przy pomocy metody replace() która jako pierwszy z argumentów przyjmuje string do wymiany, a drugim jest string na który zostanie zamieniony, w tym przypadku powinien on pozostać pusty.

Zapis danych

Do zapisania danych do pliku użyć można metody write(). Należy przy tym pamiętać o wybraniu odpowiedniego trybu otwarcia pliku.

with open("file.txt", "w") as file:
    file.write("I'm the file contents now")

Użycie powyższego zapisu spowoduje ucięcie oryginalnej zawartości pliku już w momencie jego otwarcia. Aby ją zachować używa się między innymi trybu append.

with open("file.txt", "a") as file:
    file.write("I'm at the end of the file")

💥 Zadanie 2 💥

Napisz skrypt który sprawdzi czy plik user_data.txt istnieje, jeżeli tak odczyta jego zawartość i wyświetli w konsoli. Jeżeli nie, zostanie on utworzony, a następnie przez konsolę użytkownik skryptu podawać będzie kolejne linie zawartości tak długo aż nie poda “stop”.

Parsowanie danych tekstowych

Może się zdarzyć, że odczytanie danych z pliku będzie wymagało przetworzenia ich w określony sposób. Jeżeli plik zawierać będzie nagłówek, to powinien on zostać zignorowany przy wczytywaniu danych. Poszczególne wartości zapisane w pliku mogą być oddzielone od siebie różnymi separatorami, spacją, przecinkiem, średnikiem. W takiej sytuacji należy napisać parser, który rozbije surowy tekst na przydatne fragmenty, gotowe do zapisania w strukturach pythonowych. Poniżej przedstawiono prosty przykład parsera wczytującego dane z pliku custom_data.txt.

file = open("custom_data.txt")

data_dict = {}

skip_lines = 1
for line in file:
    if skip_lines <= 0:
        usr_id, name, surname, grade = line.split(sep=';')
        data_dict[usr_id] = {"Name": name, "Surname": surname, "Grade": float(grade)}
    else:
        skip_lines -= 1
        
file.close()

Jeżeli pracujemy zarówno na stringach jak i uchwytach do plików i zależy nam na przetwarzaniu ich w podobny sposób, string można zamienić na obiekt IO przy pomocy funkcji io.StringIO(). Należy pamiętać przy tym o imporcie wbudowanej biblioteki import io.

💥 Zadanie 3 💥

Pobierz plik weather_data.yaml, następnie wczytaj z niego dane. Kluczem rekordów jest data. Oblicz i wyświetl średnią temperaturę dla tego okresu, oraz znajdź i wyświetl dzień z najniższą wilgotnością.

Podpowiedź

Metoda split() z argumentem domyślnym None spowoduje potraktowanie wszystkich białych znaków jak separatorów. Następujące po sobie separatory zostaną zgrupowane. Zdefiniowanie separatora innego niż None spowoduje potraktowanie pustych stringów pomiędzy separatorami jako osobnych wartości.

Format CSV

Format csv (ang. comma-separated values) jest formatem przechowywania danych w plikach tekstowych. Jak sugeruje jego nazwa poszczególne wartości oddzielone są od siebie przecinkiem, choć zawarty w bibliotece standardowej moduł csv pozwala na wybranie innego separatora. Rekordy oddzielone są od siebie znakiem nowej linii. Aby zacząć pracę z modułem csv należy go najpierw zaimportować.

import csv

Aby odczytać zawartość pliku w formacie csv używa się metody csv reader(). Przy użyciu pętli for obiekt reader zwróci rzędy w formie listy elementów. Przykładowy plik file.csv

with open("file.csv") as file:
    data = csv.reader(file)
    for row in data:
        print(row)

Jeżeli separator nie jest domyślnym przecinkiem, niezbędne jest podanie go jako atrybut metody reader()

with open("file.csv") as file:
    data = csv.reader(file, delimiter='.')
    for row in data:
        for value in row:
            print(value)

Do zapisywania danych w formacie csv służy metoda writer(), po którego utworzeniu korzystamy z metody writerow(), do której w argumencie podajemy listę wartości. Metoda ta dodaje znak nowej linii po każdym wywołaniu, co powoduje problem w przypadku domyślnego otwarcia pliku do zapisu, które również dodaje kolejny znak nowej linii przy każdym zapisie do pliku. Z tego powodu należy przy otwarciu pliku zastąpić znak nowej linii pustą zmienną string, nadpisując argument newline.

row_to_save = [12, 23, 34]

with open("file.csv", 'w', newline='') as file:
    csv_writer = csv.writer(file, delimiter=';')
    csv_writer.writerow(['A', 'B', 'C'])
    csv_writer.writerow(row_to_save)

Więcej informacji na temat korzystania z modułu csv znaleźć można tutaj.

💥 Zadanie 4 💥

Zapisz poniższe dane do formatu csv, uwzględniając nagłówek z nazwami kolumn. Dobierz separator tak, aby współgrał z danymi w tabeli.

header = ["Date", "Product", "Units Sold", "Price per Unit"]
sales_data = [
    ("2023-10-01", "Laptop", 5, 1200.0),
    ("2023-10-01", "Phone", 10, 500.0),
    ("2023-10-02", "Laptop", 3, 1200.0),
    ("2023-10-02", "Phone", 7, 500.0),
    ("2023-10-03", "Laptop", 4, 1200.0),
    ("2023-10-03", "Phone", 12, 500.0),
]
Następnie odczytaj te dane z pliku csv i wypisz w konsoli łączną wartość sprzedanych laptopów.

Podpowiedź

Aby przy odczytywaniu csv pominąć pierwszą linię z nagłówkiem możemy, tak jak dla innych iterowalnych obiektów w pythonie, wywołać funkcję next(). Reader przejdzie wtedy do kolejnej linii pliku.

Format JSON

Kolejnym popularnym formatem przechowywania danych w plikach tekstowych jest JSON (ang. JavaScript Object Notation). Choć nazwa sugeruje powiązanie z językiem JavaScript, w praktyce obsługiwany jest on przez wiele języków programowania, w tym python. Strukturą przypomina zawarty w pythonie słownik. Do jego obsługi zaimportować należy wbudowany w bibliotekę standardową moduł json.

import json

Aby wczytać plik używa się metody load(). W tym przypadku zwróci ona słownik, dlatego do poszczególnych wartości należy odwołać się według klucza. Zwrócony przez load() obiekt może być jednym z tych wypisanych na poniższej liście. - dict - list - str - int - float - True - False - None

Przykładowy plik json do odczytania.

with open("employees.json") as file:
    data = json.load(file)
    print(data)
    print(data[0]["name"])

Oprócz load() istnieje również metoda loads(), która odczytuje json z obiektu string, zamiast z pliku bądź obiektu json. Dane w formacie json zapisujemy jak inne pliki tekstowe, metodą write(), ale przed zapisem słownik musi być odpowiednio sformatowany przy pomocy metody dumps(). Nie mylić z metodą dump(), która konwertuje obiekty pythonowe na obiekt json. Do stringu w formacie JSON przekonwertować możemy następujące obiekty: - dict - list - tuple - str - int - float - True - False - None

student = {
  "student_name": "John",
  "student_surname": "Paul",
  "age": 24,
  "subject_list": [
    {"subject": "Flexible manufacturing systems", "points": 77.5, "grade": 4.0},
    {"subject": "Basics of data processing", "points": 21.00, "grade": 2.0}
  ]
}

with open("file.json", 'w') as file:
    data = json.dumps(student)
    file.write(data)

Więcej informacji na temat korzystania z modułu json znaleźć można tutaj.

💥 Zadanie 5 💥

Otwórz plik employees.json, do którego użytkownik skryptu doda przez konsolę kolejnego pracownika. Następnie nadpisz plik json z nowym pracownikiem.

Zadanie dla chętnych

Napisz funkcję odczytującą dane z pliku tekstowego. Powinna ona sama znajdować ile linii nagłówka zawiera plik i jakiego separatora używa, jako argument przyjmując jedynie ścieżkę do pliku. Przykładowe pliki do załadowania: sample1.txt, sample2.txt, sample3.txt.

Powrót do spisu treści ćwiczeń laboratoryjnych