7. Práce se souborovým systémem
Obsah
- Argumenty z příkazové řádky
- Modul pathlib
- Práce se soubory
- Formát souboru csv
- Formát souboru json
- Formát souboru XML
Argumenty z příkazové řádky
Použití modulu sys
Modul sys poskytuje atribut argv, což je seznam, který obsahuje argumenty zadané v příkazové řádce. Prvním prvkem je vždy název skriptu.
# soubor script.py
import sys
# sys.argv obsahuje všechny argumenty z příkazové řádky
print("Název skriptu:", sys.argv[0])
# pokud existují další argumenty
if len(sys.argv) > 1:
print("Argumenty:", sys.argv[1:])
else:
print("Žádné argumenty nebyly zadány.")
Po spuštení tohoto skriptu se dvěma argumenty hello a world dostaneme následující výsledek.
$ python3 script.py hello world
Název skriptu: script.py
Argumenty: ['hello', 'world']
Modul argparse
Pro složitější zpracování parametrů, jako je pojmenování argumentů, výchozí hodnoty nebo validace, je lepší použít modul argpase.
import argparse
parser = argparse.ArgumentParser(description="Ukázka hodnoty args.")
# povinný argument name
parser.add_argument("name", help="Jméno uživatele.")
# nepovinný argument s výchozí hodnotou, který se pokusí přetypovat hodnotu na int
parser.add_argument("--age", type=int, default=18, help="Věk uživatele.")
# nepovinný argument pro příznak, tj. pokud jej uvedeme bude mít hodnotu True
parser.add_argument("--verbose", action="store_true", help="Zobrazit podrobné informace.")
# nepovinný více hodnot pro jeden argument
parser.add_argument("--hobbies", nargs="+", help="Seznam koníčků.")
# v args jsou hodnoty uloženy v Namespace objektu
args = parser.parse_args()
args.name # přístup k hodnotě name
args.hobbies # přístup k hodnotě hobbies atd.
# příkaz: python script.py Bob --age 25 --verbose
# obsah args: Namespace(name='Bob', age=25, verbose=True, hobbies=None)
# příkaz: python script.py Charlie --hobbies čtení běhání programování
# obsah args: Namespace(name='Charlie', age=18, verbose=False, hobbies=['čtení', 'běhání', 'programování'])
Modul pathlib
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).
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.
Jednonuchý příklad získání aktuálního pracovního adresáře:
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:
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:
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í.
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.
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ář.
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.
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).
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).
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.
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.
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.
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.
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 aktuálního adresáře
pathlib.Path.cwd().glob("*/*.py")
Pro rekurzivní prohledávání adresářové struktury lze použít funkci rglob().
import pathlib
# všechny soubory s příponou .py v libovolně zanořeném podadresáři dostupný z aktuálního adresáře
pathlib.Path.cwd().rglob("*/*.py")
# rekuzrivně prohledá složku files v aktuálním adresáři
pathlib.Path.cwd().rglob("files/*/*.py")
Práce se soubory
Pozor, při práci se soubory může dojít k nechtěnému poškození uložených dat. Ověřujte jaké zdrojové kódy na svém PC spouštíte.
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.
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.
# 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ší).
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ě.
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.
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í.
with open("hello.txt", "r") as file:
for line in file:
print(line)
with open("hello.txt", "r") as file:
line = file.readline()
while line:
print(line)
line = file.readline()
Použití pathlib pro práci se souborem
V rámci modulu pathlib můžeme se soubory jednoduše pracovat. K dispozici jsou čtyři vysokoůrovňové metody.
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().
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). Více zde.
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).
{
"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. Více zde.
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
Formát souboru XML
Formát XML (Extensible Markup Language) je značkovací jazyk pro ukládání dat a vytváření dokumentů.
<knihovna>
<kniha id="1">
<nazev>Python Basics</nazev>
<autor>Jan Novak</autor>
<rok>2020</rok>
</kniha>
<kniha id="2">
<nazev>Advanced Python</nazev>
<autor>Petra Svobodova</autor>
<rok>2021</rok>
</kniha>
</knihovna>
Modul xml jazyka Python můžeme použít pro jednoduché načítání a ukládání dat ve formatu XML. Více zde.
import xml.etree.ElementTree as ET
# Načtení XML souboru
strom = ET.parse('knihy.xml')
koren = strom.getroot()
# Průchod elementy knihy
for kniha in koren.findall('kniha'):
id_knihy = kniha.get('id') # Získání atributu id
nazev = kniha.find('nazev').text # Získání textu z elementu nazev
autor = kniha.find('autor').text # Získání textu z elementu autor
rok = kniha.find('rok').text # Získání textu z elementu rok
# Výpis informací o knize
print(f'Kniha ID: {id_knihy}')
print(f'Název: {nazev}')
print(f'Autor: {autor}')
print(f'Rok vydání: {rok}')
print('-------------------------')
# Kniha ID: 1
# Název: Python Basics
# Autor: Jan Novak
# Rok vydání: 2020
# -------------------------
# Kniha ID: 2
# Název: Advanced Python
# Autor: Petra Svobodova
# Rok vydání: 2021
# -------------------------