Cvičení č. 19 rozšířené o poznámky ze cvičení a řešení některých příkladů (ZS 2025/26)¶

Práce se soubory 2¶

Minule jsme viděli¶

  • Otevření souboru pomocí open().
  • Čtení dat pomocí read(), readlines().
  • Zápis do souboru pomocí write() nebo print().
  • Uzavření souboru pomocí close().

Práce s adresářovou a souborovou strukturou¶

Práce s adresáři v Pythonu může být komplikovaná a bývá nutné použít více importovaných balíčků. Mimojiné z tohoto důvodu byl vytvořen balíček pathlib, který práci dost zjednodušuje a umožňuje lehce psát multiplatformní skripty pro různé operační systémy. Systémy Windows používají pro zápis cesty k souboru odlišnou syntaxi oproti systémům Linux a MacOS.

Příklad cesty ve Windows: C:\Users\billgates\Documents\chemtrails.txt

Příklad cesty v Linuxu: /home/linustorvalds/linuxftw.txt

Příklad cesty v MacOS: /Users/stevejobs/howtowearasweater.txt

Pro použítí pathlib je nutné tento modul importovat pomocí import pathlib. Případně si můžeme vybrat, které součásti modulu chceme použít. Například from pathlib import Path importuje pouze součást pro práci s cestami.

Modul pathlib je objektově orientovaný, což přispívá k jednoduchému použití.

Path a dostupné metody¶

Path má přímo dostupné základní metody pro zjištění aktuální pracovní složky a také domovského adresáře. Podle použitého operačního systému bude návratová hodnota buď objekt WindowsPath nebo PosixPath. Implementace uvnitř se liší, ale navenek (pro naše použití) jsou identické.

  • Path.cwd() vrátí objekt s aktuální pracovní složkou.
  • Path.home() vrátí objekt s cestou do domovské složky.
In [2]:
from pathlib import Path

path = Path.cwd()
home = Path.home()
print("CWD:", path)
print("HOME:", home)

print(type(path), type(home))

# ukázka i ve windows
CWD: /home/user/zpro-2025-public.git
HOME: /home/user
<class 'pathlib._local.PosixPath'> <class 'pathlib._local.PosixPath'>

Objekt Path lze vytvořit také z libovolné cesty uložené ve stringu. Zde je dobré použít raw string, ve kterém se neprovádí escapování, takže můžeme v klidu psát i zpětná lomítka jako normální znak.

  • path = Path(r"C:\Windows") (Windows)
  • path = Path("/users/hatsunemiku") (Linux/MacOs)

pathlib také dovoluje spojování cest do jedné. K tomu může například posloužit operátor /, který známe jako dělení.

In [16]:
home = Path.home()
download = Path("Download")
file = Path("rickroll.mp4")
print(home, download, file)
/home/user Download rickroll.mp4
In [14]:
full_path = home / download / file
print(full_path)
/home/user/Download/rickroll.mp4
In [17]:
# ukázka: spojování i se stringy
# relativní cesta
path =   Path("fotky") / "Praha" / "can121.jpg"
# path = "dir" / "moje" / "muj.txt" # nejde, chyba (jeden z argumentů musí být Path)
print(path)
fotky/Praha/can121.jpg

Pokud se vám tento zápis nelíbí, lze též použít metodu joinpath(), která bere jako parametry stringy nebo objekty Path.

In [19]:
full_path2 = Path.home().joinpath("Download", Path("rickroll.mp4"))
print(full_path2) 

# rozšířená ukázka:  všechny argumenty typu Path
full_path2 = path.joinpath(download, file)
print(full_path2) 

# rozšířená ukázka:  všechny argumenty typu str
full_path2 = path.joinpath("Download", "rickroll.mp4")
print(full_path2) 
/home/user/Download/rickroll.mp4
fotky/Praha/can121.jpg/Download/rickroll.mp4
fotky/Praha/can121.jpg/Download/rickroll.mp4

Součásti cesty¶

Cestu ke složce nebo souboru lze v operačních systémech rozdělit na několik částí, např. přípona souboru, jméno souboru, cesta k němu, atd. pathlib získávání těchto částí zjednodušuje.

  • .name: Vrací jméno souboru bez cesty.
  • .stem: Vrací jméno souboru bez přípony.
  • .suffix: Vrací příponu
  • .anchor: Část cesty před složkami.
  • .parent: Vrací složku, ve které je soubor, nebo nadřazenou složku dané složce.
In [13]:
from pathlib import Path
path = Path.home() / "recept_na_rumove_pralinky.html"
print("Celá cesta:", path)
print("Name:", path.name)
print("Stem:", path.stem)
print("Suffix:", path.suffix)
print("Anchor:", path.anchor)
print("Parent:", path.parent)
Celá cesta: /home/user/recept_na_rumove_pralinky.html
Name: recept_na_rumove_pralinky.html
Stem: recept_na_rumove_pralinky
Suffix: .html
Anchor: /
Parent: /home/user

Přejmenování a kopírování souborů¶

Pro nahrazení jména souboru nebo jeho přípony něčím novým lze využít následující metody objektu Path:

  • .with_suffix("nová přípona") v cestě k souboru nahradí starou příponu za novou.
  • .with_name("nové jméno souboru včetně přípony") v cestě k souboru nahradí celé jméno souboru za nové.
  • .with_stem("nové jméno souboru") nahradí v cestě původní jméno za nové, příponu zanechá.

Následně je třeba použít metodu .replace(), aby se změny provedly na disku. Více v následujícím příkladu.

In [24]:
# přesunu soubor jinam
from pathlib import Path
original_path = Path("data/text.txt")
new_path = Path.cwd() / "text.txt"

original_path.replace(new_path)
print(f"Moved {original_path} to {new_path}")
Moved data/text.txt to /home/user/zpro-2025-public.git/text.txt
In [25]:
# a zase zpět
original_path, new_path = new_path, original_path
original_path.replace(new_path)
print(f"Moved {original_path} to {new_path}")
Moved /home/user/zpro-2025-public.git/text.txt to data/text.txt
In [36]:
from pathlib import Path
original_path = Path("data/rename.txt")

# změna přípony
html_path = original_path.with_suffix(".html")
original_path.replace(html_path)
print(f"Renamed {original_path} to {html_path}")
Renamed data/rename.txt to data/rename.html
In [37]:
# změna jména
new_stem_path = html_path.with_stem("newname")
html_path.replace(new_stem_path)
print(f"Renamed {html_path} to {new_stem_path}")
Renamed data/rename.html to data/newname.html
In [38]:
# změna celého jména včetně přípony
new_name_path = new_stem_path.with_name("readme.md")
new_stem_path.replace(new_name_path)
print(f"Renamed {new_stem_path} to {new_name_path}")
Renamed data/newname.html to data/readme.md

Při přejmenovávání souborů je případný existující soubor automaticky přepsán. Je třeba na to dávat pozor. Lze si ale pomoci detekcí, zda soubor s novým jménem existuje, pomocí metody .exists(), která vrací True nebo False.

In [40]:
from pathlib import Path

src = Path("data/readme.md")
dest = src.with_suffix(".exe")

if src.exists() and not dest.exists():
    src.replace(dest)

Kopírování souborů je trošku komplikovanější, protože pathlib nemá přímou podporu. Ale lze to vyřešit pomocí metod .read_bytes() a .write_bytes().

In [24]:
from pathlib import Path
src = Path("data/readme.exe")
dest = src.with_name("dontreadme.md")
dest.write_bytes(src.read_bytes())
Out[24]:
39

Soubory lze smazat pomocí metody .unlink(). Složky lze mazat pomocí metody .rmdir().

In [27]:
from pathlib import Path
src = Path("data/dontreadme.md")
if src.exists():
    src.unlink()
In [ ]:
p = Path("data")
# p.rmdir() # chyba ... adresář není prázdný

Vytváření, čtení, zápis souborů a složek¶

Modul pathlib dovoluje i zjednodušený zápis a čtení souborů. K tomu slouží metody třídy Path.

  • .read_text() otevře soubor v textovém režimu a načte obsah do stringu.
  • .read_bytes() otevře soubor v binárním režimu a načte jeho obsah jako byty.
  • .write_text() otevře soubor v textovém režimu a zapíše do něj text.
  • .write_bytes() otevře soubor v binárním režimu a zapíše binární data.
  • .touch() vytvoří prázdný soubor. Pokud existuje, změní čas modifikace na aktuální čas.
  • .mkdir() vytvoří novou složku. Nelze vytvořit více zanořených podsložek najednou.

Tyto metody rovnou soubor otevírají a zavírají. Není třeba se o to starat.

In [28]:
from pathlib import Path
textfile = Path("data/text.txt")
file_contents = textfile.read_text()
print(file_contents)
Okolo lesa pole lán,
hoj jede, jede z lesa pán,
na vraném bujném jede koni,
vesele podkovičky zvoní,
jede sám a sám.

A před chalupou s koně hop
a na chalupu: klop, klop, klop!
„Hola hej! otevřte mi dvéře,
zbloudil jsem při lovení zvěře,
dejte vody pít!“
In [42]:
from pathlib import Path
data = ["Snorlax", "Bulbasaur", "Mewtwo", "Smoochum"]
new_text_file = Path("data/pokemon.txt")
new_text_file.write_text("\n".join(data))
Out[42]:
33
In [43]:
new_text_file.touch()
In [31]:
from pathlib import Path
path = Path("data/touch.html")
path.touch()
In [41]:
# podadresáře je třeba vytvářet a mazat postupně:
from pathlib import Path
path = Path("data/subfolder")
if not path.exists():
    path.mkdir()
path = path / "subsubfolder"
if not path.exists():
    path.mkdir()
In [40]:
path = Path("data/subfolder/subsubfolder")
if  path.exists():
    path.rmdir()
path = path.parent
if path.exists():
    path.rmdir()

Obsah složky (adresáře)¶

Pro získání obsahu složky lze použít metodu .glob(), která podle zadaného filtru vrátí odpovídající položky. Případně lze procházet složku pomocí metody .iterdir().

Path též dovoluje zjišťovat, o jaký typ cesty se jedná. Zda je to soubor, složka, symbolický link nebo další.

  • .is_dir() vrátí True, pokud se jedná o složku (adresář).
  • .is_file() vrátí True, pokud se jedná o soubor.
  • .is_symlink() vrátí True, pokud se jedná o symbolický link.
In [47]:
# zjišťovací funkce:
path = Path("data")
path.is_file(), path.is_dir(), path.is_symlink()
Out[47]:
(False, True, False)
In [55]:
from pathlib import Path
path = Path().cwd() / "data"
for file in path.iterdir():
    print(f"{file.name:20} Folder: {file.is_dir()} File: {file.is_file()}")
inventory_en.csv     Folder: False File: True
readme.exe           Folder: False File: True
touch.html           Folder: False File: True
example-files        Folder: True File: False
pokemon.txt          Folder: False File: True
.ipynb_checkpoints   Folder: True File: False
text.txt             Folder: False File: True
inventory.txt        Folder: False File: True
inventory.csv        Folder: False File: True
testing-folder       Folder: True File: False
text_en.txt          Folder: False File: True
argparse_test.py     Folder: False File: True
data.txt             Folder: False File: True
data_en.txt          Folder: False File: True
In [48]:
# ukázka: iterdir() vrací generátor
it = path.iterdir()
print(it)
print(next(it))
print(next(it))
list(it)
<map object at 0x7f96a607e230>
data/inventory_en.csv
data/readme.exe
Out[48]:
[PosixPath('data/touch.html'),
 PosixPath('data/example-files'),
 PosixPath('data/pokemon.txt'),
 PosixPath('data/.ipynb_checkpoints'),
 PosixPath('data/text.txt'),
 PosixPath('data/inventory.txt'),
 PosixPath('data/inventory.csv'),
 PosixPath('data/testing-folder'),
 PosixPath('data/text_en.txt'),
 PosixPath('data/argparse_test.py'),
 PosixPath('data/data.txt'),
 PosixPath('data/data_en.txt')]
In [51]:
from pathlib import Path
path_list = Path("data").glob("*.txt")
path_list
Out[51]:
<map at 0x7fe92eeefa90>
In [62]:
from pathlib import Path
path_list = Path("data").glob("*.txt")
for file in path_list:
    print(file)
data/pokemon.txt
data/text.txt
data/inventory.txt
data/text_en.txt
data/data.txt
data/data_en.txt

Příklady¶

  1. Ve složce data/testing-folder vytvořte novou podsložku se jménem text-files. V této nové složce vytvořte 20 textových souborů se jménem file##.txt, kde ## odpovídá číslům 0 až 19. Do souboru zapište nějaký neprázdný text.
In [63]:
# Path, mkdir, /, write_text 
s = "8"
s.rjust(2,"0")
Out[63]:
'08'
In [ ]:
# relativní vs. celá cesta k tomu samému souboru:
In [64]:
path_dir = Path.cwd() / Path("data/testing-folder")
path_dir.exists(), path_dir
Out[64]:
(True, PosixPath('/home/user/zpro-2025-public.git/data/testing-folder'))
In [65]:
path_dir =  Path("data/testing-folder")
path_dir.exists(), path_dir
Out[65]:
(True, PosixPath('data/testing-folder'))
In [66]:
path_dir = Path("data/testing-folder/text-files")
if not path_dir.exists():
    path_dir.mkdir()
    
for i in range(20):
    if i < 10:
       i = "0"+ str(i)
    path_file = path_dir / f"file{str(i)}.txt"
    path_file.write_text("ahoj")
    
In [67]:
for p in path_dir.iterdir():
    print(p.name, p.read_text())
file02.txt ahoj
file03.txt ahoj
file07.txt ahoj
file06.txt ahoj
file11.txt ahoj
file05.txt ahoj
file13.txt ahoj
file04.txt ahoj
file17.txt ahoj
file10.txt ahoj
file18.txt ahoj
file01.txt ahoj
file19.txt ahoj
file15.txt ahoj
file14.txt ahoj
file16.txt ahoj
file09.txt ahoj
file12.txt ahoj
file08.txt ahoj
file00.txt ahoj
  1. Soubory .txt vytvořené v minulém příkladě přejmenujte tak, aby měly příponu .md.
In [ ]:
# Path, glob nebo iterdir, with_suffix, replace
In [68]:
path_dir = Path("data/testing-folder/text-files")
#for path_file in path_dir.iterdir():
for path_file in path_dir.glob("*.txt"):
    path_new = path_file.with_suffix(".md")
    #print(path_file, path_new)
    path_file.replace(path_new)
  1. Smažte po sobě všechny soubory .md a také složku text-files.
In [ ]:
# iterdir nebo glob, unlink, rmdir
In [81]:
 
In [82]:
path_dir = Path("data/testing-folder/text-files")
for path_file in path_dir.iterdir():
    path_file.unlink()
if path_dir.exists():
    path_dir.rmdir()
In [73]:
# remove hidden files:
path_hidden = Path("data/testing-folder/text-files/.ipynb_checkpoints")
if path_hidden.exists():
    for p in path_hidden.iterdir():
        print(p)
    p.unlink()
    path_hidden.rmdir()
  1. Spočítejte kolik různých typů souborů existuje ve složce data/example-files a u každého typu též spočítejte, kolik takových souborů existuje.
In [ ]:
# iterdir, suffix
path_dir = Path("data/example-files")
for p in path_dir.iterdir():
    print(p.suffix) # d[p.suffix] += 1
In [74]:
d = {}
for path in Path("data/example-files").iterdir():
    if path.suffix not in d:
        d[path.suffix] = 0
    d[path.suffix] +=1
d        
Out[74]:
{'.html': 22, '.txt': 26, '.md': 17}
  1. Ukázkové soubory ze složky data/example-files roztřiďte podle roku a měsíce uvedeného ve jméně souboru (formát jména je data-YYYY-MM-DD.EXT) do složek s číslem roku a měsíce. Roztříděné soubory nakopírujte do složky data/testing-folder.

Příklad: Soubor data-2002-01-01.md nakopírujeme do složky data/testing-folder/2002/01.

In [ ]:
# iterdir, /, exists, mkdir, name, read_bytes, write_bytes 
In [ ]:
 
  1. Vypište celou strukturu složky data/testing-folder z minulého příkladu včetně souborů v následujícím formátu (včetně odsazení):
2000
 01
  data-2000-01-01.html
  ...
 02
  data-2000-02-01.txt
  ...
2001
 01
   data ...
In [ ]:
# iterdir, is_dir, name
In [ ]:
 

Shrnutí kapitoly¶

In [ ]:
# create Path
from pathlib import Path
path0 = Path.cwd()
path_home = Path.home()
path = Path("data/a")
path1 = path / "text.txt"
path2 = path0.joinpath(path1)
print(path0, path, path1)
# atributs
print("Celá cesta:", path)
print("Name:", path.name)
print("Stem (jméno bez přípony):", path.stem)
print("Suffix:", path.suffix)
print("Anchor:", path.anchor)
print("Parent:", path.parent)
# tests
print(path1.exists())
print(path1.is_file())
print(path1.is_dir())
# rename
path2 = path1.with_suffix(".html") 
path2 = path1.with_name("new.html")
path2 = path1.with_stem("new")
if path1.exists() and not path2.exists():
    path1.replace(path2)
#delete
if path2.exists() and path2.is_file():
    path2.unlink()
#read-write:
path1.touch()
path1.write_text("ahoj")
text = path1.read_text()
path2.write_bytes(path1.read_bytes()) # copy
#directory
if path2.exists() and path2.is_dir():
    path2.rmdir()
if not path2.exists():
    path2.mkdir()
gen1 = Path("data").iterdir()
gen2 = Path("data").glob("*")