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

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 [15]:
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-2024-public.git
HOME: /home/user
<class 'pathlib.PosixPath'> <class 'pathlib.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)
In [16]:
path = Path(r"C:\Windows")
print(path)
if path.exists():
    print("  existuje")
else:
    print("  neexistuje") 
    
path = Path("./data/example-files")
print(path)
if path.exists():
    print("  existuje")
else:
    print("  neexistuje")
# ukázka i ve windows
C:\Windows
  neexistuje
data/example-files
  existuje

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 [3]:
home = Path.home()
download = Path("Download")
file = Path("rickroll.mp4")

full_path = home / download / file
print(full_path)

full_path = home / "Download" / "pom.txt"
print(full_path)
/home/user/Download/rickroll.mp4
/home/user/Download/pom.txt

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

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

full_path2 = home.joinpath("Download", "rickroll.mp4")
print(full_path2) 

full_path2 = home.joinpath(download, file)
print(full_path2) 
/home/user/Download/rickroll.mp4
/home/user/Download/rickroll.mp4
/home/user/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 [5]:
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 [7]:
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 [8]:
# 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 [9]:
# 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 [10]:
from pathlib import Path

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

if 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 [11]:
from pathlib import Path
src = Path("data/readme.exe")
dest = src.with_name("dontreadme.md")
dest.write_bytes(src.read_bytes())
Out[11]:
39

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

In [14]:
from pathlib import Path
src = Path("data/dontreadme.md")
if src.exists():
    src.unlink()

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 [17]:
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 [18]:
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[18]:
33
In [19]:
from pathlib import Path
path = Path("data/touch.html")
path.touch()
In [20]:
from pathlib import Path
path = Path("data/subfolder")
path.mkdir()
path = path / "subsubfolder"
path.mkdir()
In [21]:
if path.exists():
    path.rmdir()
if path.parent.exists():
    path.parent.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 [27]:
path = Path("data/testing-folder")
g = path.iterdir()
print(g)
print(next(g))
<generator object Path.iterdir at 0x7f08e8de8520>
data/testing-folder/.ipynb_checkpoints
In [28]:
from pathlib import Path
path = Path().cwd() / "data/testing-folder"
for file in path.iterdir():
    print(f"{file}\nFolder: {file.is_dir()} File: {file.is_file()}")
/home/user/zpro-2024-public.git/data/testing-folder/.ipynb_checkpoints
Folder: True File: False
/home/user/zpro-2024-public.git/data/testing-folder/place_for_testing_file_operations
Folder: False File: True
In [29]:
from pathlib import Path
path_list = Path("data").glob("*.txt")
for file in path_list:
    print(file)
data/pokemon.txt
data/new_file.txt
data/text.txt
data/text_novy.txt
data/inventory.txt
data/sinus.txt
data/n.txt
data/data.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 [ ]:
# Path, mkdir, /, write_text 
"12".rjust(3,'0')
In [ ]:
from pathlib import Path
new_folder_path = Path("data/testing-folder/text-files")
if not new_folder_path.exists():
    new_folder_path.mkdir()
for i in range(20):
    name = f"file{str(i).rjust(2,'0')}.txt"
    new_file_path = new_folder_path / name
    new_file_path.write_text(f"nějaký text {i}")
In [ ]:
path = new_folder_path
for file_path in path.iterdir():
    print(file_path.name, ":", file_path.read_text())
  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 [ ]:
# glob
from pathlib import Path
for filepath in path.glob("*.txt"):
    # with_suffix, replace
    newpath = filepath.with_suffix(".md")
    filepath.replace(newpath)
In [ ]:
for file_path in path.iterdir():
    print(file_path.name)
  1. Smažte po sobě všechny soubory .md a také složku text-files.
In [ ]:
# iterdir nebo glob, unlink, rmdir
In [ ]:
if path.exists():
    for filepath in path.glob("*.md"):
        filepath.unlink()
    path.rmdir()
In [ ]:
# smažeme i skryté soubory:
def remove_all(path):
    if path.is_dir():
        for file in path.iterdir():
           remove_all(file)
        path.rmdir()
    else:
        path.unlink() 
if path.exists():
    remove_all(path)
  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
folder = Path("data/example-files")
d = {}
for file in folder.iterdir():
    if not file.suffix in d: 
        d[file.suffix] = 1
    else:
        d[file.suffix] += 1
print(d)
  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 [30]:
from pathlib import Path
folder = Path("data/example-files")

for file in folder.iterdir():
    s = file.name.split("-")       # ["data", "2000", "01", "01.md"]
    dest_folder = Path(f"data/testing-folder/{s[1]}/{s[2]}") # adresář, kam budu kopírovat
    if not dest_folder.parent.exists(): # vytvořím adresář "data/example-files/2000"
        dest_folder.parent.mkdir()
    if not dest_folder.exists():        # vytvořím adresář "data/example-files/2000/01"
        dest_folder.mkdir()
    new_file = dest_folder / file.name       # nový název souboru včetně cesty
    new_file.write_bytes(file.read_bytes())  # překopíruji soubor
The history saving thread hit an unexpected error (OperationalError('attempt to write a readonly database')).History will not be written to the database.
  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

Shrnutí kapitoly¶

In [ ]:
# opakování:
from pathlib import Path
path0 = Path.cwd()
path_home = Path.home()
path = Path("data/new")
path1 = path / "text.txt"
path0.joinpath(path)
#print(path0, path, path1)

print("Celá cesta:", path)
print("Name:", path.name)
print("Stem:", path.stem)
print("Suffix:", path.suffix)
print("Anchor:", path.anchor)
print("Parent:", path.parent)

# 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("*")
In [ ]: