8. Práce se souborovým systémem
Modul pathlib
Modul pro manipulaci s cestami a soubory. Poskytuje objektový přístup. Dostupný od verze 3.4, dříve se používala kombinace modulů os
, glob
a shutil
, která s cestami pracovala jako s běžnými řetězci. Modul pathlib
nabízí objektový přístup.
Programátoři běžně pracovali s cestami jako s řetězci a aplikovali na ně běžné metody pro řetězce. To v praxi přináší mnohé problémy (například rozdíly mezi platformami).
Jednonuchý příklad získání aktuálního pracovního adresáře:
1
2
3
4
5
import pathlib
# získáme instanci třídy Path reprezentující aktuální pracovní adresář
pathlib.Path.cwd()
Instanci třídy Path
je rovněž možné vytvořit z klasického řetězce reprezentující cestu v souborovém systému:
1
2
3
4
5
6
7
8
import pathlib
# Windows - využití r pro ignoraci \ jako escape znaku
pathlib.Path(r"C:\Users\user\python\file.txt")
# Unix
pathlib.Path("/home/user/python/file.txt")
Zde je nutné si uvědomit, že se jedná o reprezentaci libovolné (i fiktivní) cesty v souborovém systému. Soubory ani složky nemusí existovat. Důležité je, že se se s cestami pracuje jako s datovou strukturou umožňující abstrakce. Formát reprezentace je automaticky vybrát na základě operačního systému.
Jedna z abstrakcí je jednoduché získání domovského adresáře uživatele:
1
2
3
4
5
6
7
8
9
10
11
import pathlib
# domovský adresář
home = pathlib.Path.home()
# můžeme spojit několik složek
python_scripts = home / "python" / "scripts"
# druhý způsob spojení pomoci .joinpath
python_scripts = home.joinpath("python", "scripts")
V případě pathlib.Path.home()
získáme cestu absolutní, obsahuje tedy kompletní cestu ke složce uživatele. Je však možné pracovat i s cestou relativní.
1
2
3
4
5
6
7
8
9
10
import pathlib
# relativní cesta
python_scripts = pathlib.Path("scripts", "python")
# absolutní cesta
home = pathlib.Path.cwd()
# jejich spojení
pathlib.Path.joinpath(home, python_scripts)
V případě relativní cesty můžeme použít speciální metodu Path.resolve()
pro získání cesty absolutní. Dosáhneme pak stejného výsledku jako výše.
1
2
3
4
5
6
7
import pathlib
# relativní cesta
python_scripts = pathlib.Path("scripts", "python")
python_scripts.resolve()
Přístup k jednotlivým částem cesty
Třída Path
obsahuje vlastnosti k přístupu k užitečným částem cesty. Pro jednoduchý přehled je možné použít následující “tahák”.
Rodičovská složka/složka ve které je soubor uložen
Pomoci vlastnosti .parent
můžeme jednoduše získat rodičovský adresář.
1
2
3
4
5
6
7
8
9
10
11
import pathlib
# rodičovská složka
pathlib.Path.cwd().parent
# řetězení je možné
pathlib.Path.cwd().parent.parent.parent
# složka obsahující soubor file.py
pathlib.Path.cwd().joinpath("file.py").parent
Název souboru/složky
Pomoci vlastnosti .name
můžeme získat název souboru nebo složky.
1
2
3
4
5
6
7
8
import pathlib
# název složky
pathlib.Path.cwd().name
# název souboru
pathlib.Path.cwd().joinpath("file.py").name
Název souboru bez přípony
Pomoci vlastnosti .stem
můžeme získat název souboru bez jeho přípony (suffixu).
1
2
3
4
import pathlib
# název souboru bez suffixu
pathlib.Path.cwd().joinpath("file.py").stem
Přípona souboru
Pomoci vlastnosti .suffix
můžeme získat příponu souboru (suffix).
1
2
3
4
5
6
7
8
import pathlib
# název souboru bez suffixu
pathlib.Path.cwd().joinpath("file.py").suffix
# v případě vícero přípon lze použít
pathlib.Path.cwd().joinpath("file.tar.gz").suffixes
Rozdělení cesty na jednotlivé části
Objekt třídy Path
lze jednoduše rozdělit na jednotlivé části.
1
2
3
4
import pathlib
pathlib.Path.cwd().parts
Metody na testovaní existence
Test zda cesta existuje
Pomoci metody exists()
můžeme jednoduše ověřit zda zadaná cesta existuje.
1
2
3
4
5
6
7
8
import pathlib
# složka
pathlib.Path.cwd().exists()
# soubor
pathlib.Path.cwd().joinpath("file.py").exists()
Test zda se jedná soubor/složku
U objektu třídy Path
můžeme testovat zda se jedná o složku nebo soubor. Pozor pokud soubor/složka neexistuje je vráceno False
.
1
2
3
4
5
6
7
8
import pathlib
# složka
pathlib.Path.cwd().is_dir()
# soubor
pathlib.Path.cwd().joinpath("file.py").is_file()
Selekce souborů a složek na základě vzoru
Modul pathlib
integruje funkcionalitu modulu glob
, který umožňuje jednoduchým způsobem selektovat soubory a složky.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pathlib
# všechny soubory v aktuálním adresáři
pathlib.Path.cwd().glob("*")
# všechny soubory s příponou .py v aktuálním adresáři
pathlib.Path.cwd().glob("*.py")
# všechny soubory s příponou .py v aktuálním adresáři začínající písmenem a
pathlib.Path.cwd().glob("a*.py")
# všechny soubory s příponou .py v aktuálním adresáři
# začínající písmenem a, končící písmenem b
pathlib.Path.cwd().glob("a*b.py")
# všechny soubory s příponou .py v libovolném podadresáři v aktuálním adresáři
pathlib.Path.cwd().glob("*/*.py")
Práce se soubory
Na chvíli odbočíme od modulu pathlib
a podíváme se na práci se soubory (čtení a zápis).
Hlavní problematikou práce se soubory (ale i paralelní programování případně síťová komunikace) je správa prostředků. Situace kdy jeden program použije sdílený prostředek a již jej neodevzdá zpět nazýváme memory leak.
V kontextu souborů nám jde především o důsledné dodržování uzavírání otevřených souborů (ať už po čtení nebo zápisu). Zápis do souboru je totiž provádět přes takzvaný buffer. Data jsou napřed zapsána do bufferu a až po volání .close()
jsou z bufferu zapsána na disk. V opačném případě mohou být tato data ztracena.
Dalším případem může být otevření souboru a následné vyvolání vyjimky (ukončení programu, nikoli však uzavření souboru).
Následující kód negarantuje, že soubor bude uzavřen v případě vyjimky.
1
2
3
file = open("hello.txt", "w")
file.write("Hello, World!")
file.close()
U funkce open()
pouze zdůrazníme, že prvním argumentem je cesta k souboru, druhým pak mód otevření souboru:
w
- write - zápisr
- read - čtenía
- append - rozšiřovánír+
- read plus - čtení a zápis
Běžně jsou soubory otevřeny v textovém módu – k souborům je přistupováno tak, že obsahují text (v určitém kódování). Všechny módy však existují ve variantě b
(binární, např rb
).
Situaci můžeme ošetři již známým příkazem try ... finally
.
1
2
3
4
5
6
7
8
# bezpečné otevření souboru - pokud zde nastane chyba soubor se ani neotevře
file = open("hello.txt", "w")
try:
file.write("Hello, World!")
finally:
# bezpečné zavření souboru v případě chyby u zápisu
file.close()
Nejjednodším způsobem je však použítí příkazu with
, který zabezpečuje bezpečné odbavení kontextu (například otevření a zavření souboru, připojení na server a další).
1
2
with open("hello.txt", "w") as file:
file.write("Hello, World!")
Příkaz with
se postará o volání speciálních dunder metod __enter__()
a __exit__()
. V případě souborů se tedy jedná o otevření a zavření souboru.
Příkaz with
lze použít i v komplikovanější podobě.
1
2
with open("input.txt") as in_file, open("output.txt", "w") as out_file:
pass
Objekt souboru
Po otevření souboru funkci open()
získáme objekt reprezentující otevřený soubor. K dispozici máme následující metody.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
with open("hello.txt", "r") as file:
# přečtení jednoho řádku
file.readline()
with open("hello.txt", "r") as file:
# získání seznamu všech řádků
file.readlines()
with open("hello.txt", "w") as file:
# zápis řetězce
file.write("test")
with open("hello.txt", "w") as file:
# zápis seznamu řetězců jako jednotlivých řádků
file.writelines(["test\n", "test2\n"])
Objekt souboru poskytuje iterátor proto je možné následující.
1
2
3
4
5
6
7
with open("hello.txt", "r") as file:
for line in file:
print(line)
with open("hello.txt", "r") as file:
while file:
print(file.readline())
Použití pathlib
V rámci modulu pathlib
můžeme se soubory jednoduše pracovat. K dispozici jsou čtyři vysokoůrovňové metody.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import pathlib
path = pathlib.Path.cwd() / "test.md"
# načtení textu
path.read_text()
# načtení bajtů
path.read_bytes()
# zápis textu
path.write_bytes("Test")
# zápis bajtů
path.write_bytes(b"Test")
Pro větši kontrolu je však možné použít příkaz with
a metodu .open()
.
1
2
3
4
5
6
7
8
import pathlib
path = pathlib.Path.cwd() / "test.md"
with path.open(mode="r") as file:
# přečteme jeden řádek, můžeme použít vše jako u objektu souboru
file.readline()
Formát souboru csv
S formátem csv
(comma separated value) jsme se setkali již několikrát. V jazyce Python je k dispozici modul csv
umožňující jednoduchou práci s tímto formátem (čtení/zápis).
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
import pathlib
import csv
# vytvoření a zápis
path = pathlib.Path("data.csv")
data = [[str(value) for value in range(5)] for _ in range(10)]
with path.open(mode="w") as file:
# volitelným parametrem separator nastavujeme oddělovač
# čárka je výchozí hodnota
csv_writer = csv.writer(file, delimiter=",")
for row in data:
csv_writer.writerow(row)
# čtení
path = pathlib.Path("data.csv")
with path.open(mode="r") as file:
csv_reader = csv.reader(file)
input_data = [row for row in csv_reader]
assert data == input_data
Formát souboru json
Formát JSON (JavaScript Object Notation) je inspirován JavaScript syntaxí pro zápis objektů. V jazyce Python je nejblíže slovníku (případně zanořenému slovníku).
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
{
"glossary":{
"title":"example glossary",
"GlossDiv":{
"title":"S",
"GlossList":{
"GlossEntry":{
"ID":"SGML",
"SortAs":"SGML",
"GlossTerm":"Standard Generalized Markup Language",
"Acronym":"SGML",
"Abbrev":"ISO 8879:1986",
"GlossDef":{
"para":"A meta-markup language, used to create markup languages such as DocBook.",
"GlossSeeAlso":[
"GML",
"XML"
]
},
"GlossSee":"markup"
}
}
}
}
}
Modul json
jazyka Python můžeme použít pro jednoduché načítání a ukládání dat ve formatu JSON.
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
27
import pathlib
import json
# vytvoření a zápis
path = pathlib.Path("data.json")
data = {
"username": "pepa",
"name": {
"firstname": "Josef",
"lastname": "Novak"
},
"titles": ["Mgr.", "Bc."],
"salary": "30000"
}
with path.open(mode="w") as file:
file.write(json.dumps(data))
# čtení
path = pathlib.Path("data.json")
with path.open(mode="r") as file:
input_data = json.loads(file.read())
assert data == input_data
Úkoly
Nevíte si rady? Přečtěte si “Jak pracovat s Github Classroom?”.
Při řešení úloh nepoužívejte pokročilejší funkcionalitu jazyka která nebyla ještě představena! Takové úkoly budou vráceny na přepracování bez ohledu na jejich funkčnost.
Skupina Mikula
- L08E01: CSV sum script [Náhled], [Příjmout úkol]
- L08E02: Data 4 [Náhled], [Příjmout úkol]
- L08E03: Exercise points analysis [Náhled], [Příjmout úkol]
Skupina Petržela
- L08E01: CSV sum script [Náhled], [Příjmout úkol]
- L08E02: Data 4 [Náhled], [Příjmout úkol]
- L08E03: Exercise points analysis [Náhled], [Příjmout úkol]