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 [3]:
from pathlib import Path
path = Path.cwd()
home = Path.home()
print("CWD:", path)
print("HOME:", home)

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

# ukázka relativní cesty
path1 = Path("data/testing-folder")
print("relativní cesta:", path1)

# ukázka absolutní cesty
path2 = Path(r"C:\Windows")
print("absolutní cesta:", path2)

# ukázka testu, zda soubor existuje
if path2.exists():
    print(f"soubor {path2} existuje")
if path1.exists():
    print(f"složka {path1} existuje")

# ukázka i ve Windows
CWD: /home/reitezuz/cv19
HOME: /home/reitezuz
<class 'pathlib.PosixPath'> <class 'pathlib.PosixPath'>
relativní cesta: data/testing-folder
absolutní cesta: C:\Windows
složka data/testing-folder existuje

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

full_path = home / download / file

print(full_path)

# další ukázka ... i string:
full_path = home / "data" / "q.exe"
print(full_path)
/home/reitezuz/Download/rickroll.mp4
/home/reitezuz/data/q.exe

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

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

# další ukázka ... lepeni cesty
full_path = home.joinpath(download,file)
print(full_path)
full_path = download.joinpath("files","x.bin")
print(full_path)
/home/reitezuz/Download/rickroll.mp4
/home/reitezuz/Download/rickroll.mp4
Download/files/x.bin

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)  # jmeno souboru s priponou
print("Stem:   ", path.stem)  # jmeno souboru bez pripony
print("Suffix: ", path.suffix) # jen pripona
print("Anchor: ", path.anchor) # cast cesty pred slozkami
print("Parent: ", path.parent) # nadrazena slozka
Celá cesta: /home/reitezuz/recept_na_rumove_pralinky.html
Name:    recept_na_rumove_pralinky.html
Stem:    recept_na_rumove_pralinky
Suffix:  .html
Anchor:  /
Parent:  /home/reitezuz

Vytváření, čtení, zápis souborů¶

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.

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

In [14]:
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 [15]:
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[15]:
33
In [17]:
from pathlib import Path
path = Path("data/touch.html")
path.touch()
In [11]:
# ukázka textového kopírování s kontrolou
original_path = Path("data/rename.txt")
new_path = Path("data/rename1.txt")
if not new_path.exists():
    new_path.write_text(original_path.read_text())
else:
    print("existuje")
existuje
In [13]:
# ukázka binarniho kopírování
original_path = Path("data/rename.txt")
new_path = Path("data/rename2.txt")
if not new_path.exists():
    new_path.write_bytes(original_path.read_bytes())
else:
    print("existuje")
existuje

Přejmenování, kopírování a mazá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 [11]:
# přejmenování s využitím with...:
from pathlib import Path
original_path = Path("data/rename1.txt")
html_path = original_path.with_suffix(".html")
new_stem_path = html_path.with_stem("newname")
new_name_path = new_stem_path.with_name("readme.md")

print(original_path)
print(html_path)
print(new_stem_path)
print(new_name_path)

original_path.replace(html_path)

print(original_path)
print(html_path)
data/rename1.txt
data/rename1.html
data/newname.html
data/readme.md
data/rename1.txt
data/rename1.html
In [12]:
# přejmenování bez využití with...:
from pathlib import Path
html_path = Path("data/rename1.html")
txt_path = Path("data/rename1.txt")

print(txt_path)
print(html_path)

html_path.replace(txt_path)

print(txt_path)
print(html_path)
data/rename1.txt
data/rename1.html
data/rename1.txt
data/rename1.html
In [24]:
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}")

# 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}")

# 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/rename.txt to data/rename.html
Renamed data/rename.html to data/newname.html
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 [25]:
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 [10]:
from pathlib import Path
src = Path("data/readme.exe")
dest = src.with_name("dontreadme.md")
dest.write_bytes(src.read_bytes())
Out[10]:
39

Soubory lze smazat pomocí metody .unlink().

In [29]:
from pathlib import Path

src = Path("data/dontreadme.md")
if src.exists():
    src.unlink()

Vytváření a mazání složek (adresářů)¶

Modul pathlib má také metody pro vytváření a mazání složek:

  • .mkdir() vytvoří novou složku. Nelze vytvořit více zanořených podsložek najednou.
  • .rmdir() smaže prázdnou složku.
In [13]:
from pathlib import Path
path = Path("data/subfolder")
path.mkdir()
path = path / "subsubfolder"
path.mkdir()

# ukazka ošetření pokud už existuje
In [14]:
# ukázka jak zase složky smažu
from pathlib import Path
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 [18]:
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/reitezuz/cv19/data/testing-folder/place_for_testing_file_operations
Folder: False File: True
In [29]:
# ukázka: vypsat jmeno a typ do tabulky:
In [39]:
from pathlib import Path
path = Path().cwd() / "data/testing-folder/"
for file in path.iterdir():
    if file.is_file():
        type= "File"
    elif file.is_dir():
        type = "Folder"
    else:
        type = "Other"
    print(f"{file.name:35} {type}")
place_for_testing_file_operations   File
.ipynb_checkpoints                  Folder
In [48]:
from pathlib import Path
path = Path("data")
path_list = path.glob("t*.*")
for file in path_list:
    print(file)
print("")

for file in path.glob("*.txt"):
    print(file)
# ukázka ... trochu více
data/text.txt
data/touch.html

data/data.txt
data/inventory.txt
data/text.txt
data/new_file.txt
data/sinus.txt
data/data_new.txt
data/cisla.txt
data/soubor.txt
data/rename2.txt
data/sin_values.txt
data/rename3.txt
data/jiny.txt
data/pokemon.txt
data/dontreadme.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 [18]:
# Path, mkdir, /, write_text
In [54]:
from pathlib import Path
new_folder = Path("data/testing-folder/text-files")
if not new_folder.exists():
    new_folder.mkdir()
for i in range(20):
    jmeno = f"file{str(i).rjust(2,'0')}.txt"
    new_file = new_folder / jmeno
    new_file.write_text(f"nějaký text\n{i}")
    
In [51]:
# vypsat soubory ve složce:
for file in new_folder.iterdir():
    print(file.name, end = " ")
file00.txt file01.txt file02.txt file03.txt file04.txt file05.txt file06.txt file07.txt file08.txt file09.txt file10.txt file11.txt file12.txt file13.txt file14.txt file15.txt file16.txt file17.txt file18.txt file19.txt 
  1. Soubory .txt vytvořené v minulém příkladě přejmenujte tak, aby měly příponu .md.
In [32]:
# změna přípony
#html_path = original_path.with_suffix(".html")
#original_path.replace(html_path)

# Path, iterdir nebo glob, with_suffix, replace
from pathlib import Path
folder = Path("data/testing-folder/text-files")
for file in folder.glob("*.txt"):
    new_name = file.with_suffix(".md")
    file.replace(new_name)
  1. Smažte po sobě všechny soubory .md a také složku text-files.
In [52]:
# Path, iterdir, unlink, rmdir
from pathlib import Path
folder = Path("data/testing-folder/text-files")
for file in folder.iterdir():
    file.unlink()
folder.rmdir()    
In [54]:
# Pokud jsou tam skryté Jupyterovské soubory navíc 
#... nešikovné řešení:
from pathlib import Path
folder = Path("data/testing-folder/text-files")
for file in folder.iterdir():
    if file.is_file():
        file.unlink()
    if file.is_dir():
        for file1 in file.iterdir():
            file1.unlink()
        file.rmdir()
folder.rmdir()    
In [34]:
# Pokud jsou tam skryté Jupyterovské soubory navíc 
#... lepší řešení pomocí rekurze:
from pathlib import Path
folder = Path("data/testing-folder/text-files")

def delete_rek(folder):
    for file in folder.iterdir():
        if file.is_file():
            file.unlink()
        if file.is_dir():
            delete_rek(file)
    folder.rmdir() 

delete_rek(folder)
In [55]:
# Pokud jsou tam skryté Jupyterovské soubory navíc 
#... řešení pomocí modulu shutil:
import shutil

# Pokud jsou tam skryté Jupyterovské soubory navíc 
#... nešikovné řešení:
from pathlib import Path
folder = Path("data/testing-folder/text-files")

# shutil.rmtree(folder) # to by taky šlo :-)

for file in folder.iterdir():
    if file.is_file():
        file.unlink()
    if file.is_dir():
        shutil.rmtree(file)
folder.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 [24]:
# iterdir
folder = Path("data/example-files")
d = {}
#s = []
for file in folder.iterdir():
    if not file.suffix in d: 
        d[file.suffix] = 1
    else:
        d[file.suffix] += 1
    #s.append(file.suffix)
    #print(file.suffix)
print(d)
#print(s)
{'.html': 22, '.md': 17, '.txt': 26}
  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 [ ]:
#  iterdirm split, parent, exists, mkdir, write_bytes, name
  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 [ ]:
# rekurze, iterdir, is_dir, name