9. seminář - Práce se soubory

TEACHINGZPP2

Práce se soubory je ve většině programovacích jazyků velmi podobná. Každý soubor musí být nejprve otevřen. Tímto krokem získá program přístup k obsahu souboru, přičemž k souboru je možné přistoupit v režimu (1) čtení, (2) zápisu nebo (3) čtení a zápisu.

Otevření souboru

Pro otevření souboru slouží funkce open(), která akceptuje jako parametr adresu souboru, jenž má být otevřen, a režim přístupu k danému souboru. Funkce open() vrací objekt reprezentující daný soubor, se kterým je možné dále pracovat. Pro objekt reprezentující soubor se běžně používá termín handler. Jednotlivé módy, které lze při práci se soubory využít, jsou shrnuty v tabulce

File open modes

Otevření souboru v režimu čtení po znacích, což je výchozí režim, se provádí následovně.


    # otevření souboru pro čtení po znacích (výchozí)
    f = open("soubor.txt")
    
    # stejné jako předchozí
    f = open("soubor.txt", "rt")

Otevřený soubor je možné uzavřít pomocí metody .close().


    # uzavření souboru
    f.close()

Čtení ze souboru

Pro čtení souboru (v textovém režimu) slouží metody .read() a .readline(). V následujících ukázkách budeme předpokládat, že existuje soubor s názvem soubor.txt, který obsahuje


    Programování
    v Pythonu je zábava.

Soubor je umístěn ve stejném adresáři, ve kterém se nachází náš program.

Metoda .read() přečte celý soubor (od začátku do konce) a vrátí jej v podobě textového řetězce.


    f = open("soubor.txt")
    print(f.read())
    f.close()
    
    # Metodě .read() můžeme předar i počet znaků které má přečíst
    f = open("soubor.txt")
    print(f.read(7)) # přečteme 7 znaků
    f.close()
    
    # Při čtení souboru je uchován kurzor (ukazatel) na doposud
    # nepřečtený znak. Při dalším čtení ze souboru se pokračuje od 
    # tohoto kurzoru.
    f = open("soubor.txt")
    print(f.read(7)) # přečteme 7 znaků
    print(f.read(7)) # přečteme dalších 7 znaků
    print(f.read(7)) # přečteme dalších 7 znaků
    f.close()

Jakmile je celý soubor přečten, metoda .read() vrací prázdný řetězec. Pokud bychom chtěli nějakou část souboru číst opakovaně, je třeba přesunout kurzor na tuto část. Pro přesun kurzoru slouží metoda .seek() akceptující pořadové číslo znaku, na který má být kurzor nastaven. Například následující kód dvakrát přečte obsah souboru soubor.txt.


    f = open("soubor.txt")
    print(f.read()) # přečte celý soubor
    f.seek(0) # nastavíme kurzor na začátek souboru
    print(f.read()) # znovu přečteme celý soubor
    f.close()

Analogicky jako lze číst celý soubor metodou .read(), je možné číst soubor po jednotlivých řádcích pomocí metody .readline(). Rovněž je možné metodě .readline() předat počet znaků, které mají být z řádku přečteny. Pokud již není žádný další nepřečtený řádek, případně znaky na řádku, vrací metoda .readline() prázdný řetězec.


    f = open("soubor.txt")
    print(f.readline()) # vypíše první řádek souboru
    f.close()
    
    # Následující kód ukazuje, jak je možné přečíst celý soubor řádek po řádku.
    f = open("soubor.txt")
    radek = f.readline()

    while radek:
        print(radek, end="")
        radek = f.readline()
    f.close()

Zápis do souboru

Aby bylo možné do souboru zapisovat, je zapotřebí jej otevřít v režimu umozňující zápis. Zápis probíhá vždy na aktuální pozici kurzoru. Pokud je soubor otevřen v režimu a (přidávání), je zachován původní obsah tohoto souboru a kurzor je nastaven na poslední znak v tomto souboru. V případě, že je soubor otevřen v režimu w, je původní obsah souboru smazán a kurzor je přesunut na začátek tohoto souboru.

V režimech r+, a+ a w+ je soubor otevřen pro čtení a zápis. V případě r+ a w+ je kurzor nastaven na první znak v souboru, při použití w+ je navíc původní soubor přepsán. V režimu a+ je kurzor nastaven na poslední znak v souboru. Pro zápis textového řetězce do souboru slouží metoda .write(). Následující ukázka vytvoří soubor, se kterým jsme doposud pracovali.


    f = open("soubor.txt", "w")
    f.write("Programování\n")
    f.write("v Pythonu je zábava.")
    f.close()

Ošetření chyb při práci se soubory

Chyby při práci se soubory mohou být zapříčiněny celou řadou okolností. Například program nemá dostatečná oprávnění pracovat se souborem, soubor je poškozen, soubor je blokován jiným programem, chyba při přenosu dat a další. Při práci se soubory je vždy nutné s těmito chybami počítat.

Veškerá práce se souborem (včetně jeho otevření) by měla být umístěna v try-bloku. V případě, že dojde k chybě (ke vzniku výjimky), je třeba všechny otevřené soubory uzavřít.


    try:
        # práce se souborem
        f = open("soubor.txt", "w+")
        f.write("Programování\n")
        f.write("v Pythonu je zábava.")
        f.close() # uzavření souboru
    except:
        # ošetření výjimek
        print("Chyba při práci se souborem.")
        # uzavření souboru při výjimce
        f.close()
        
    # Mnohem výhodnější je umístění metody .close() do bloku finally.
    # Vyhneme se tím dvojímu volání .close().
    try:
        # práce se souborem
        f = open("soubor.txt", "w+")
        f.write("Programování\n")
        f.write("v Pythonu je zábava.")
    except:
        # ošetření výjimek
        print("Chyba při práci se souborem.")
    finally:
        # uzavření souboru
        f.close()

Práce s binárními soubory

Binární soubory, na rozdíl od textových souborů, neukládají data po jednotlivých znacích ale po bajtech. Předpokládejme, že chceme uložit číslo 42. V textovém souboru zabírá toto číslo 2 bajty. Číslo 42 lze uložit do jednoho bajtu. V jazyce Python je bajt imutabilní datový typ a lze jej vytvořit pomocí funkce bytes(), která akceptuje seznam hodnot (0–255) ze kterých je vytvořena posloupnost bitů̊. Například.


    bytes([0]) # 00000000
    bytes([16]) # 00010000
    bytes([42]) # 00101010 (42)
    bytes([255]) # 11111111
    bytes([255, 255]) # 1111111111111111 (dva bajty)

Pro zápis a čtení binárních souborů se používají již dříve popsané metody, je ale zapotřebí je otevřít v režimu b. Například zápis jednoho bajtu do již otevřeného souboru.


    # zapíšeme jeden bajt
    f.write(bytes([42])) 
    
    # Přečteme jeden bajt
    f = open("soubor.bin", "rb")
    print(ord(f.read()))
    f.close()
    
    # Výpis pomocí formátovacího řetězce
    f = open("soubor.txt", "rb")
    bajt = ord(f.read(1))
    print(f"{bajt}") # vypíše: 42
    print(f"{bajt:x}") # vypíše: 2a
    print(f"{bajt:b}") # vypíše: 101010
    f.close()

Rozdělení na více bajtů̊

Čísla, která zabírají více než jeden bajt, jsou v paměti uložena za sebou. Například hodnota 2161 je uložena následovně:


   | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 1 |
   |-------------------------------|-------------------------------|
               1 bajt                           1 bajt

Pořadí jednotlivých bajtů, tzv. endianita, je určené architekturou počítače. Pro lepší názornost jsme použili Big-endian,který je ale na rozdíl od Little-endian, méně používaný. Pokud tedy chceme uložit číslo 2161 do souboru, musíme jej rozdělit na jednotlivé bajty a ty následně uložit do souboru. Následující funkce realizuje zmíněné rozdělení.


    def convert_number_to_bytes(number):
        bytes_array = []
        if number:
            while number:
                byte = number & 0xff
                bytes_array.append(byte)
                number = number >> 8
        else:
            bytes_array.append(0)
        return bytes_array[::-1]
    
    # Použití
    bytes_array = convert_number_to_bytes(9308) # vrátí: [36, 92]
    try:
        f = open("soubor.bin", "wb")
      f.write(bytes(bytes_array))
    except:
        # ošetření výjimek
        print("Chyba při práci se souborem.")
    finally:
        # uzavření souboru
        f.close()

Úkoly

Napište funkci save_matrix(file, matrix), která uloží číselnou matici (reprezentovanou jako seznam seznamů) do souboru v CSV formátu. Například matice matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] bude uložena v souboru následovně.

    1,2,3
    4,5,6
    7,8,9

Napište funkci load_matrix(file), která načte matici uloženou ve formátu z předchozího úkolu.
Napište program, který pomocí cyklu while přečte celý soubor po znacích.
Napište funkci convert_bytes_to_number(bytes), která zadané bajty převede na číslo.