Praca z plikami¶
Python dzięki swojej uniwersalności i łatwości w użyciu, wykorzystywany jest przez największych “gigantów” z branży IT m.in Google (Gmail, YouTube), Facebook (włącznie z Instagramem), Spotify, Netflix, Dropbox. Jak widać, każdy z nas codziennie korzysta “dobrodziejstw” Pythona nawet o nich nie wiedząc. Mimo swojej wszechstronności, najczęściej wykorzystuje się go jednak do tworzenia aplikacji internetowych, analizy dużej ilości danych lub tworzenia skryptów. Niezależnie od tego do czego planujemy go wykorzystać, prędzej czy później, każdy stanie przed zadaniem wymagającym pracy z plikami, dlatego w tym module dowiemy się w jaki sposób wykorzystać do tego Pythona.
Operacje na plikach i katalogach
Aby wyświetlić listę plików i katalogów, należy skorzystać z modułu os.
1 2 3 | >>> import os
>>> os.listdir('/sciezka/do/katalogu')
['file_1.txt', 'file_2.csv', 'file_3.pdf']
|
Aby wyświetlić listę plików pasujących do wzoru, należy skorzystać z modułów os oraz fnmatch.
1 2 3 4 5 6 | >>> import os
>>> for file_name in os.listdir('/sciezka/do/katalogu'):
... if file_name.endswith('pdf'):
... print(file_name)
file_1.pdf
file_2.pdf
|
Aby stworzyć katalog, użyjemy metody mkdir.
1 2 3 4 | >>> import os
>>> os.mkdir('homework')
>>> os.listdir()
['homework']
|
Aby zmienić nazwę pliku, użyjemy metody rename
.
1 2 3 | >>> os.rename('homework', 'workfromhome')
>>> os.listdir()
['workfromhome']
|
Aby usunąć plik, użyjemy metody remove
.
1 2 3 4 5 6 7 | >>> import os
>>> os.listdir('/sciezka/do/katalogu')
['file_1.pdf']
>>> os.remove('file_1.pdf')
>>> os.listdir('/sciezka/do/katalogu')
[]
|
Otwieranie pliku
Zanim będziemy mogli odczytywać i zapisywać dane z i do pliku, musimy go otworzyć. Aby to zrobic, należy po prostu wywołać wbudowaną w Pythona funkcję open
podając jako pierwszy parametr ścieżkę do pliku. Jeśli dany plik istnieje, uzyskamy w ten sposób dostęp do obiektu pliku posiadającego metody i atrybuty pozwalające wykonywać na nim różne operacje.
1 2 | >>> open('songs.txt')
<_io.TextIOWrapper name='songs.txt' mode='r' encoding='UTF-8'>
|
W przypadku gdy plik nie istnieje, Python rzuci nam wyjątek FileNotFoundError
.
1 2 3 4 | >>> open('non_existing_file.txt')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'non_existing_file.txt'
|
Funkcja open
akceptuje jeszcze parę innych parametrów, ale najważniejszym z nich, poza ścieżką do pliku, jest parametr określający tryb otwarcia pliku, a w przypadku gdy go nie podamy, stosowana jest domyślna wartość r
. Dostępne tryby otwarcia pliku:
r
- (ang. read) tryb służacy tylko do odczytu (domyślny) - tylko do odczytu zawartości z pliku bez możliwości modyfikacji. Kursor ustawiony jest na początku pliku.r+
- tryb dla odczytu i zapisu. Kursor ustawiony jest na początku pliku.w
- (ang. write) tryb służący tylko do zapisu - usuwa zawartość pliku przed jego otwarciem. Jeśli plik nie istnieje, zostanie wówczas utworzony.w+
- tryb służacy do zapisu i odczytu, usuwa zawartość pliku przed otwarciem. Jeśli plik nie istnieje, zostanie wówczas utworzony.a
- (ang. append) tryb służący do dopisywania - nie usuwa zawartości pliku przed otwarciem. Jeśli plik nie istnieje, zostanie wówczas utworzony. Kursor ustawiony jest na końcu pliku.a+
- tryb służący do odczytywania i dopisywania - nie usuwa zawartości pliku przed otwarciem. Jeśli plik nie istnieje, zostanie wówczas utworzony. Kursor ustawiony jest na końcu pliku.
Tak więc, aby otworzyć plik, np. w trybie dopisywania (nie usuniemy w ten sposób poprzednich wartości), użyjemy takiej składni:
1 2 3 | >>> my_songs = open('songs.txt', 'a+')
>>> my_songs
<_io.TextIOWrapper name='songs.txt' mode='a+' encoding='UTF-8'>
|
Odczytywanie danych z pliku
Aby odczytać dane z pliku, należy użyć metody .read()
. Zwróci ona wówczas całą dostępną zawartość pliku.
1 2 3 | >>> my_songs = open('songs.txt', 'r+')
>>> my_songs.read()
"Alice in Chains - Would?\nMetallica - Nothing Else Matters\nMichael Jackson - Beat It\nKękę - Samson\nVan Halen - Panama\nAC/DC - Highway To Hell\nPantera - Walk\nGuns N' Roses - Don't Cry\nRed Hot Chili Peppers - Scar Tissue\nHuman - Polski\nPaul Kalbrenner - Azure\nThe Weeknd - Blinding Lights\nMetallica - Master Of Puppets\nOzzy Osbourne - No More Tears"
|
Zamykanie pliku
Po każdej operacji na pliku, należy go zamknąć. Python automatycznie zamyka plik w momencie gdy referencja do pliku jest nadpisana, np. gdy otworzymy inny plik. Dobrą praktyką jest jednak “wymuszenie” zamknięcia pliku poprzez wywołanie metody .close():
1 2 3 4 | >>> my_songs = open('songs.txt', 'a')
>>> my_songs
<_io.TextIOWrapper name='songs.txt' mode='a' encoding='UTF-8'>
>>> my_songs.close()
|
Zapisywanie postów do pliku
Do tego zadania wykorzystamy klasę Post
oraz poznany wcześniej context manager open
.
Na potrzeby tego zadania przyjmijmy następujące założenia:
Każdy post będzie posiadał unikalny atrybut - identyfikator, który posłuży nam do pobierania/usuwania konkretnego postu z pliku.
Każdy post będzie zapisywany w oddzielnej linijce do pliku tekstowego - aby zasygnalizować nową linijkę należy użyć \n na końcu zapisywanego wiersza.
Kolejność atrybutów opisujących post w pliku zawsze będzie taka sama:
id
,title
,content
,author
.Każdy atrybut postu będzie oddzielony przecinkiem np. “1,Is coding fun or boring?,How to know if coding is for you?,MC Abra”.
Tworzenie klasy Post
1 2 3 4 5 6 7 8 9 | class Post:
def __init__(self, id_, title, content, author):
self.id = id_
self.title = title
self.content = content
self.author = author
def __str__(self):
return ",".join((str(self.id), self.title, self.content, self.author))
|
Tworzenie generatora
Każdy post musi posiadać unikalny identyfikator. W tym zadaniu pomocny okazać się może generator, który będzie “pamiętał” ostatni wygenerowany identyfikator.
1 2 3 4 5 | def id_generator():
id_ = 1
while True:
yield id_
id_ += 1
|
Zapisywanie postu do pliku
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | post_id_generator = id_generator()
def save_post_to_file(title, content, author):
next_post_id = next(post_id_generator)
post = Post(id_=next_post_id, title=title, content=content, author=author)
with open("posts.txt", "a+") as file_:
file_.write(f"{post}\n")
return post
post_1 = save_post_to_file(
title="STX Next is awesome",
content="This post explains why STX Next is awesome",
author="Piotr B."
)
post_2 = save_post_to_file(
title="Is coding fun or boring?",
content="How to know if coding is for you?",
author="MC Abra"
)
post_3 = save_post_to_file(
title="How to make programming more exciting?",
content="Writing code is like playing golf. When you start it's not fun.",
author="Mr. Potato"
)
|
$ cat posts.txt
1,STX Next is awesome,This post explains why STX Next is awesome,Piotr B.
2,Is coding fun or boring?,How to know if coding is for you?,MC Abra
3,How to make programming more exciting?,Writing code is like playing golf. When you start it's not fun.,Mr. Potato
Pobieranie wszystkich postów
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def get_post_from_line(line):
id_, *rest = line.strip().split(",")
return Post(int(id_), *rest)
def get_all_posts():
with open("posts.txt", "r") as file_:
return [get_post_from_line(line) for line in file_.readlines()]
posts = get_all_posts()
print(len(posts) == 3)
for post in posts:
print(post.title)
|
Pobieranie pojedyńczego postu
1 2 3 4 5 6 7 8 9 | def get_post_by_id(post_id):
with open("posts.txt", "r") as file_:
for line in file_.readlines():
post = get_post_from_line(line)
if post.id == int(post_id):
return post
post = get_post_by_id(2)
print(post.title)
|
Usuwanie postu z konkretnym ID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def delete_post_by_id(post_id):
with open("posts.txt", "r+") as file_:
lines = file_.readlines()
file_.seek(0)
for line in lines:
post = get_post_from_line(line)
if post.id != int(post_id):
file_.write(line)
file_.truncate()
delete_post_by_id(2)
assert get_post_by_id(2) is None
|
$ cat posts.txt
1,STX Next is awesome,This post explains why STX Next is awesome,Piotr B.
3,How to make programming more exciting?,Writing code is like playing golf. When you start it's not fun.,Mr. Potato