Itt vagy: Kezdőlap Ugorj fejest a Python 3-ba

Nehézségi szint: ♦♦♦♢♢

Fájlok

Egy kilenc mérföldes séta nem vicc, különösen nem az esőben.
– Harry Kemelman, The Nine Mile Walk

 

Ugorj fejest

A windowsos laptopomon 38 493 fájl volt, mielőtt egyetlen alkalmazást is telepítettem volna. A Python 3 telepítése majdnem 3 000 fájlt adott ehhez a számhoz. A fájlok minden jelentős operációs rendszer elsődleges tárolási paradigmáját jelentik, a fogalom annyira be van égve, hogy egyeseknek gondot okoz elképzelni az alternatívákat. A számítógéped képletesen szólva fuldoklik a fájlokban.

Olvasás szövegfájlokból

Mielőtt olvashatnál egy fájlból, meg kell azt nyitnod. A fájlok Pythonból való megnyitása nem is lehetne egyszerűbb:

egy_fájl = open('examples/chinese.txt', encoding='utf-8')

A Python rendelkezik egy beépített open() függvénnyel, amely egy fájlnevet vár argumentumként. A fájlnév itt az 'examples/chinese.txt'. A fájlnévvel kapcsolatban öt édekességet figyelhetünk meg:

  1. Nem egyszerűen egy fájl neve, hanem egy könyvtárútvonal és egy fájlnév kombinációja. Egy elméletben elképzelhető fájlmegnyitó függvény várhatna két argumentumot – egy könyvtárútvonalat és egy fájlnevet – de az open() függvény csak egyet vár. A Pythonban amikor egy „fájlnévre” van szükség, megadhatod egy könyvtárútvonal egy részét vagy egészét is.
  2. A könyvtárútvonal normál osztásjelet használ, de nem mondtam meg, melyik operációs rendszert használom. A Windows fordított osztásjelet használ az alkönyvtárak jelzésére, míg a Mac OS X és a Linux normál osztásjelet. De Pythonban a normál osztásjelek Csak Működnek(TM), még Windows alatt is.
  3. A könyvtárútvonal nem osztásjellel vagy meghajtó betűjellel kezdődik, így a relatív útvonalnak nevezzük. Relatív? Mihez képest? Türelem, prücsök.
  4. Ez egy karakterlánc. Minden korszerű operációs rendszer (még a Windows is!) Unicode-ot használ a fájlok és könyvtárak neveinek tárolására. A Python 3 teljesen támogatja a nem ASCII útvonalneveket.
  5. Nem kell a helyi lemezeden lennie. Lehet egy csatolt hálózati meghajtón is. A „fájl” lehet, hogy csak egy teljesen virtuális fájlrendszer képzeletében létezik. Ha a számítógéped fájlnak tekinti, és el tudja érni fájlként, akkor a Python meg tudja nyitni.

Azonban az open() függvény hívása nem állt meg a fájlnévnél. Van még egy argumentum, amelynek neve encoding (kódolás). Jaj ne, ez félelmetesen ismerősen hangzik.

A karakterkódolás kidugja ronda fejét

A bájtok bájtok, a karakterek absztrakciók. A karakterlánc Unicode karakterek sorozata. De egy lemezen lévő fájl nem Unicode karakterek sorozata, a lemezen lévő fájl bájtok sorozata. De hogyan konvertálja a Python a bájtsorozatot karakterek sorozatává, amikor beolvasol egy „szövegfájlt” a lemezről? A bájtokat egy adott karakterkódolási algoritmus szerint dekódolja, és visszaadja Unicode karakterek egy sorozatát (más néven egy karakterláncot).

# Ez a példa Windowson készült. Más operációs rendszerek
# másképp viselkedhetnek, a lentebb vázolt okokból.
>>> fájl = open('examples/chinese.txt')
>>> egy_karakterlánc = fájl.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python31\lib\encodings\cp1252.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x8f in position 28: character maps to <undefined>
>>> 

Mi történt? Nem adtál meg karakterkódolást, így a Pythonnak az alapértelmezett kódolást kell használnia. Mi az alapértelmezett kódolás? Ha alaposan megnézed a nyomkövetést, láthatod, hogy a cp1252.py fájlban keletkezik a kivétel, vagyis a Python alapértelmezett kódolása itt a CP-1252. (A CP-1252 egy gyakori kódolás a Microsoft Windowst futtató számítógépeken.) A CP-1252 karakterkészlet nem támogatja a fájlban lévő karaktereket, így az olvasás egy ronda UnicodeDecodeError kivétellel meghiúsul.

De várj, a helyzet ennél is rosszabb! Az alapértelmezett kódolás platformfüggő, így ez a kód akár működhet is a számítógépeden (ha az alapértelmezett kódolásod UTF-8), de hibát okoz, ha odaadod valaki másnak (akinek az alapértelmezett kódolása eltérő, mint például a CP-1252).

Ha meg szeretnéd kapni az alapértelmezett karakterkódolást, akkor importáld a locale modult, és hívd meg a locale.getpreferredencoding() metódust. A windowsos laptopomon ez a 'cp1252' értéket adja vissza, de az emeleten lévő linuxos gépemen az 'UTF8' értéket. Még a saját házamban sem tudok rendet tartani! A te eredményeid eltérhetnek (még Windowson is) az operációs rendszer telepített verziójától és a területi/nyelvi beállításaidtól függően. Ezért annyira fontos megadni a kódolást minden alkalommal, amikor megnyitsz egy fájlt.

Adatfolyam-objektumok

Egyelőre azt tudjuk, hogy a Python rendelkezik egy beépített, open() nevű függvénnyel. Az open() függvény egy adatfolyam objektumot ad vissza, amely rendelkezik a karakterek folyamának kezelésére és az azzal kapcsolatos információk lekérésére szolgáló metódusokkal és attribútumokkal.

>>> egy_fájl = open('examples/chinese.txt', encoding='utf-8')
>>> egy_fájl.name                                              
'examples/chinese.txt'
>>> egy_fájl.encoding                                          
'utf-8'
>>> egy_fájl.mode                                              
'r'
  1. A name attribútum az open() függvénynek a fájl megnyitásakor átadott nevet tükrözi. Ez nincs abszolút útvonalnévvé normalizálva.
  2. Hasonlóképpen az encoding attribútum az open() függvénynek a fájl megnyitásakor átadott kódolást tükrözi. Ha nem adtad meg a kódolást a fájl megnyitásakor (rossz fejlesztő!), akkor az encoding attribútum a locale.getpreferredencoding() értékét fogja tükrözni.
  3. A mode attribútum megadja, hogy milyen módban lett megnyitva a fájl. Az open() függvénynek átadhatsz egy elhagyható mode paramétert is. Nem adtál meg módot a fájl megnyitásakor, így a Python alapértelmezésben az 'r', azaz „megnyitás csak olvasásra, szöveges módban ” módot használja. Ahogyan majd később ebben a fejezetben látni fogod, a fájl módja több célt szolgál, a különböző módok lehetővé teszik a fájlba írást, fájlhoz fűzést, vagy fájl megnyitását bináris módban (amikor szövegek helyett bájtokat kezelsz).

Az open() függvény dokumentációja felsorolja az összes lehetséges fájlmódot.

Adatok olvasása szövegfájlból

Miután megnyitottál egy fájlt olvasásra, előbb-utóbb olvasni akarsz majd belőle.

>>> egy_fájl = open('examples/chinese.txt', encoding='utf-8')
>>> egy_fájl.read()                                            
'Dive Into Python 是为有经验的程序员编写的一本 Python 书。\n'
>>> egy_fájl.read()                                            
''
  1. Miután megnyitottál egy fájlt (a megfelelő kódolással), az olvasás csupán az adatfolyam objektum read() metódusának hívásából áll. Az eredmény egy karakterlánc.
  2. Talán némiképp meglepő, de a fájl ismételt olvasása nem dob kivételt. A Python nem tekinti a fájl vége utáni olvasást hibának, ilyenkor egyszerűen egy üres karakterláncot ad vissza.

Mi van, ha újra akarod olvasni a fájlt?

# az előző példa folytatása
>>> egy_fájl.read()                      
''
>>> egy_fájl.seek(0)                     
0
>>> egy_fájl.read(16)                    
'Dive Into Python'
>>> egy_fájl.read(1)                     
' '
>>> egy_fájl.read(1)
'是'
>>> egy_fájl.tell()                      
20
  1. Mivel még mindig a fájl végén vagy, az adatfolyam objektum read() metódusának további hívásai egyszerűen egy üres karakterláncot adnak vissza.
  2. A seek() metódus egy adott bájtpozícióra lép a fájlban.
  3. A read() metódus egy elhagyható paramétert vár, a beolvasandó karakterek számát.
  4. Ha szeretnéd, egyszerre akár egy bájtot is beolvashatsz.
  5. 16 + 1 + 1 = … 20?

Fussunk neki ennek még egyszer

# az előző példa folytatása
>>> egy_fájl.seek(17)                    
17
>>> egy_fájl.read(1)                     
'是'
>>> egy_fájl.tell()                      
20
  1. Mozgás a 17. bájtra.
  2. Egy karakter beolvasása.
  3. Most a 20. bájton vagy.

Látod már? A seek() és tell() metódusok mindig bájtokat számolnak, de mivel a fájlt szövegként nyitottad meg, a read() metódus karaktereket számol. A kínai karakterek több bájtot igényelnek az UTF-8 kódoláshoz. A fájlban lévő angol karakterek egyenként csak egy bájtot igényelnek, így azt hiheted, hogy a seek() és a read() metódusok ugyanazt számolják. De ez csak néhány karakterre igaz.

De várj, lesz ez még rosszabb is!

>>> egy_fájl.seek(18)                         
18
>>> egy_fájl.read(1)                          
Traceback (most recent call last):
  File "<pyshell#12>", line 1, in <module>
    egy_fájl.read(1)
  File "C:\Python31\lib\codecs.py", line 300, in decode
    (result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf8' codec can't decode byte 0x98 in position 0: unexpected code byte
  1. Lépjünk a 18. bájtra, és próbáljunk meg egy karaktert beolvasni.
  2. Miért nem sikerül ez? Mert nincs karakter a 18. bájtnál. A legközelebbi karakter a 17. bájtnál kezdődik (és három bájton át folytatódik). Egy karakter olvasása a közepéről sikertelen lesz, és UnicodeDecodeError kivételt dob.

Fájlok bezárása

A nyitott fájlok erőforrásokat fogyasztanak, és a fájl módjától függően más programok esetleg nem tudják elérni azokat. Fontos a fájlokat azonnal bezárni, amint végeztél a feldolgozásukkal.

# folytatás az előző példából
>>> egy_fájl.close()

Hát ez nem volt túl izgalmas.

Az egy_fájl nevű adatfolyam-objektum továbbra is létezik, a close() metódusának meghívása nem semmisíti meg magát az objektumot. De azért így nem túl hasznos.

# az előző példa folytatása
>>> egy_fájl.read()                           
Traceback (most recent call last):
  File "<pyshell#24>", line 1, in <module>
    a_file.read()
ValueError: I/O operation on closed file.
>>> egy_fájl.seek(0)                          
Traceback (most recent call last):
  File "<pyshell#25>", line 1, in <module>
    a_file.seek(0)
ValueError: I/O operation on closed file.
>>> egy_fájl.tell()                           
Traceback (most recent call last):
  File "<pyshell#26>", line 1, in <module>
    a_file.tell()
ValueError: I/O operation on closed file.
>>> egy_fájl.close()                          
>>> egy_fájl.closed                           
True
  1. Megpróbálhatsz olvasni egy bezárt fájlból, de ez IOError kivételt okoz.
  2. A bezárt fájlban nem tudsz pozicionálni sem.
  3. A bezárt fájlban nincs aktuális pozíció, így a tell() metódus hívása is meghiúsul.
  4. Talán meglepő, hogy a close() metódus hívása egy olyan adatfolyam-objektumon, amely fájlja be lett zárva, nem okoz kivételt. Egyszerűen nem csinál semmit.
  5. A bezárt adatfolyam-objektumoknak egy hasznos attribútumuk van: a closed attribútum megerősíti, hogy a fájl be van zárva.

Fájlok automatikus bezárása

Az adatfolyam-objektumok rendelkeznek explicit close() metódussal, de mi történik, ha a kódod hibás, és még a close() hívása előtt összeomlik? Az a fájl elméletileg sokkal tovább is nyitva maradhat, mint szükséges. Amíg a helyi számítógépen keresel hibát, ez nem nagy ügy. Egy éles szerveren már talán.

A Python 2-nek volt erre egy megoldása: a try..finally blokk. Ez továbbra is működik Python 3-ban, és láthatod is mások kódjában, vagy olyan kódban, amit Python 3-ra portoltak. De a Python 2.6 bevezetett egy tisztább megoldást, amit a Python 3-ban előnyben részesítenek: a with utasítást.

with open('examples/chinese.txt', encoding='utf-8') as egy_fájl:
    egy_fájl.seek(17)
    egy_karakter = egy_fájl.read(1)
    print(egy_karakter)

Ez a kód meghívja az open() metódust, de soha nem jívja meg az egy_fájl.close()-t. A with utasítás kódblokkot kezd, mint egy if utasítás, vagy for ciklus. Ezen a kódblokkon belül az egy_fájl változót úgy használhatod, mint az open() által visszaadott adatfolyam-objektumot. Az összes normális adatfolyam-objektum metódus rendelkezésre áll  – seek(), read(), amit csak akarsz. Amikor a with blokknak vége, a Python automatikusan meghívja az egy_fájl.close()-t.

Itt a trükk: nem számít, hogyan vagy mikor lépsz ki a with blokkból, a Python bezárja a fájlt… még ha egy nem kezelt kivételen át is „lépsz ki” belőle. Így van: még ha a kódod kivételt is dob, és az egész program csikorogva leáll, a fájl be lesz zárva. Garantáltan.

Technikailag a with utasítás egy futási kontextust hoz létre. Ezekben a példákban az adatfolyam-objektum kontextuskezelőként működik. A Python létrehozza az egy_fájl nevű adatfolyam-objektumot, és közli vele, hogy most belép egy futási kontextusba. Amikor a with kódblokk befejeződött, a Python közli az adatfolyam-objektummal, hogy most kilép a futási kontextusból, és az adatfolyam-objektum meghívja a saját close() metódusát. Lásd a B függelék, „A with blokkban használható osztályok” szakaszt a részletekért.

Nincs semmi fájlspecifikus a with utasításban; ez egy általános keretrendszer futási kontextusok létrehozására, és annak közlésére objektumokkal, hogy épp belépnek egy futási kontextusba vagy kilépnek abból. Ha a kérdéses objektum egy adatfolyam-objektum, akkor ez hasznos fájlközeli dolgokat csinál (például automatikusan bezárja a fájlt). De ez a viselkedés az adatfolyam-objektumban van meghatározva, nem a with utasításban. A kontextuskezelők használatára rengeteg más mód is van, a fájloktól függetlenül. Akár sajátot is létrehozhatsz, ahogyan majd a fejezet későbbi részében látni fogod.

Adatok olvasása soronként

A szövegfájlok egy „sora” pontosan az, aminek elképzeled – beírsz pár szót, és megnyomod az ENTER-t, és máris egy új sorban vagy. Egy szövegsor karakterek sorozata, amiket elválaszt egy… mi is pontosan? Nos, ez bonyolult, mert a szövegfájlok több különböző karaktert is használhatnak a sor végének jelzésére. Minden operációs rendszernek megvan a saját megállapodása. Néhány a kocsivissza karaktert használja, mások a soremelés karaktert, és néhány mindkettőt elhelyezi minden sor végén.

Lélegezz fel, mert a Python alapértelmezésben automatikusan kezeli a sorvégeket. Ha azt mondod, „Soronként be akarom olvasni ezt a szövegfájlt,I want” akkor a Python kitalálja, milyen sorvégződést használ a fájl, és innentől minden Csak Működik.

Ha részletesebben szeretnéd befolyásolni, hogy mi számít sorvégnek, akkor átadhatod a newline paramétert az open() függvénynek. A véres részletekért lásd az open() függvény dokumentációját.

Nos, akkor valójában hogy csinálod? Mármint a fájl soronkénti beolvasását. Olyan egyszerű, hogy gyönyörű.

[az oneline.py letöltése]

line_number = 0
with open('examples/favorite-people.txt', encoding='utf-8') as egy_fájl:   
    for egy_sor in egy_fájl:                                               
        sorszám += 1
        print('{:>4} {}'.format(sorszám, egy_sor.rstrip()))                
  1. A with minta használatával biztonságosan megnyitod a fájlt, és hagyod, hogy Python bezárja helyetted.
  2. Egy fájl soronkénti beolvasásához használj for ciklust. Ennyi. Azon kívül, hogy a read()-hez hasonló explicit metódusai vannak, az adatfolyam-objektum egyben iterátor is, ami minden értékkéréskor egy sort ad vissza.
  3. A format() karakterlánc-metódus használatával kiírathatod a sorszámot és magát a sort is. A {:>4} formátumleíró azt jelenti: „írd ki ezt az argumentumot jobbra igazítva 4 szóközön belül.” Az egy_sor változó tartalmazza az egész sort, a kocsivissza karakterekkel együtt. Az rstrip() karakterlánc-metódus eltávolítja a záró üres helyeket, beleértve a kocsivissza karaktereket is.
you@localhost:~/diveintopython3$ python3 examples/oneline.py
   1 Dora
   2 Ethan
   3 Wesley
   4 John
   5 Anne
   6 Mike
   7 Chris
   8 Sarah
   9 Alex
  10 Lizzie

Ezt a hibát kaptad?

te@localhost:~/diveintopython3$ python3 examples/oneline.py
Traceback (most recent call last):
  File "examples/oneline.py", line 4, in <module>
    print('{:>4} {}'.format(line_number, a_line.rstrip()))
ValueError: zero length field name in format

Ha igen, akkor valószínűleg a Python 3.0-át használod. Komolyan frissítened kellene legalább Python 3.1-re.

A Python 3.0 támogatta a karakterlánc-formázást, de csak expliciten számozottformátumleírókkal. A Python 3.1 lehetővé teszi az argumentumindexek kihagyását a formátumleírókban. Összehasonlításul itt a Python 3.0-kompatibilis verzió:

print('{0:>4} {1}'.format(sorszám, egy_sor.rstrip()))

Írás szövegfájlokba

A fájlokba nagyjából ugyanúgy írhatsz, ahogy olvasod azokat. Először megnyitsz egy fájlt, és kapsz egy adatfolyam-objektumot, aztán metódusokat használsz az adatfolyam-objektumon, hogy adatokat írj a fájlba, majd bezárod a fájlt.

Egy fájl írásra való megnyitásához használd az open() függvényt, és add meg az írás módot. Két fájlmód használható az íráshoz:

Mindkét mód automatikusan létrehozza a fájlt, ha még nem létezik, így nincs szükség semmi szöszmötölős „ha a fájl még nem létezik, akkor hozz létre egy új fájlt csak hogy először megnyithasd” függvényre. Csak nyisd meg a fájlt, és kezdd írni.

Mindig zárd be a fájlokat, amint végeztél az írásukkal, hogy felszabadítsd a fájlhivatkozást, és biztosítsd, hogy az adatok valójában kiírásra kerülnek a háttértárra. Ahogyan az adatok fájlból olvasásakor, itt is meghívhatod az adatfolyam-objektum close() metódusát, vagy használhatod a with utasítást, és rábízhatod a fájl bezárását a Pythonra. Fogadni mernék, hogy kitalálod, melyik módszert javaslom.

>>> with open('teszt.log', mode='w', encoding='utf-8') as egy_fájl:  
...     egy_fájl.write('a teszt sikerült')                            
>>> with open('teszt.log', encoding='utf-8') as egy_fájl:
...     print(egy_fájl.read())
a teszt sikerült
>>> with open('teszt.log', mode='a', encoding='utf-8') as egy_fájl:  
...     a_file.write('és újra')
>>> with open('teszt.log', encoding='utf-8') as egy_fájl:
...     print(egy_fájl.read())
a teszt sikerültés újra                                           
  1. Bátran kezdesz a teszt.log nevű új fájl létrehozásával (vagy a meglévő fájl felülírásával), és a fájl írásra megnyitásával. A mode='w' paraméter azt jelenti, hogy írásra nyitod meg a fájlt. Igen, ez tényleg olyan veszélyes, mint amilyennek hangzik. Remélem, nem érdekelt annak a fájlnak a tartalma (ha volt), mert azok az adatok már elvesztek.
  2. Az open() függvény által visszaadott adatfolyam-objektum write() metódusával adhatsz adatokat az újonnan megnyitott fájlhoz. Awith blokk végén a Python automatikusan bezárja a fájlt.
  3. Ez olyan jó móka volt, csináljuk meg újra. De most a mode='a' használatával, hogy hozzáfűzzünk a fájlhoz a felülírása helyett. A hozzáfűzéssoha nem bántja a fájl meglévő tartalmát.
  4. Mind az eredetileg kiírt sor, mind a hozzáfűzött második most a teszt.log fájlban van. Vedd észre, hogy sem kocsivissza, sem soremelés karakterek nincsenek. Mivel nem írtál ki explicit módon ilyeneket egyik alkalommal sem, a fájl nem tartalmaz egyet sem. Kocsivissza karaktert a '\r' sorozat használatával, soremelést pedig a '\n' sorozattal írhatsz. Mivel egyiket sem tetted, a fájlba írt minden szöveg egy sorba került.

Karakterkódolás megint

Észrevetted az encoding paramétert, amit az open() függvény kapott, miközben megnyitottál egy fájlt írásra? Ez fontos, soha ne hagyd ki! Ahogyan a fejezet elején láttad, a fájlok nem karakterláncokat tartalmaznak, hanembájtokat. A „karakterlánc” szövegfájlból olvasása csak azért működik, mert megmondtad a Pythonnak, hogy milyen kódolást használjon a bájtok sorozatának olvasásához, és karakterlánccá konvertálásához. A szöveg fájlba írása ugyanazt a problémát veti fel, csak fordítva. Nem írhatsz karaktereket egy fájlba,a karakterek absztrakciók. Ahhoz, hogy fájlba írhasd őket, a Pythonnak tudnia kell, hogyan konvertálja a karakterláncodat bájtok sorozatává. Egyetlen módszer van a helyes konverzió végrehajtására: az encoding paraméter megadása a fájl írásra való megnyitásakor.

Bináris fájlok

a kutyám, Beauregard

Nem minden fájl tartalmaz szöveget. Néhány a kutyám képét tartalmazza.

>>> egy_kép = open('examples/beauregard.jpg', mode='rb')                
>>> egy_kép.mode                                                        
'rb'
>>> egy_kép.name                                                        
'examples/beauregard.jpg'
>>> egy_kép.encoding                                                    
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: '_io.BufferedReader' object has no attribute 'encoding'
  1. A fájlok bináris módban való megnyitása egyszerű, de xxxészrevétlen. Az egyetlen különbség a szöveg módban való megnyitáshoz képest, hogy a mode paraméter tartalmaz egy 'b' karaktert.
  2. A fájl bináris módban való megnyitásakor kapott adatfolyam-objektumnak sok attribútuma ugyanolyan, beleértve a mode-ot, ami az open() függvénynek átadott mode paramétert tükrözi.
  3. A bináris adatfolyam-objektumoknak van name attribútumuk is, ahogyan a szöveges adatfolyam-objektumoknak.
  4. Van itt azért egy különbség: a bináris adatfolyam-objektumnak nincs encoding attribútuma. Logikus, nem? Bájtokat, és nem karakterláncokat olvasol (vagy írsz), így nem kell a Pythonnak konverziót végeznie. Amit egy bináris fájlba teszel, pontosan azt veszed ki, nincs szükség konverzióra.

Említettem, hogy bájtokat olvasol? Ó, igen, azokat.

# folytatás az előző példából
>>> egy_kép.tell()
0
>>> adat = egy_kép.read(3)  
>>> adat
b'\xff\xd8\xff'
>>> type(adat)               
<class 'bytes'>
>>> egy_kép.tell()          
3
>>> egy_kép.seek(0)
0
>>> adat = egy_kép.read()
>>> len(adat)
3150
  1. A szövegfájlokhoz hasonlóan a bináris fájlok is beolvashatók egyszerre kis darabokban. De van egy alapvető különbség…
  2. …bájtokat olvasol, nem karakterláncokat. Mivel a fájlt bináris módban nyitottad meg, a read() metódus abeolvasandó bájtok számát várja, nem a karakterek számát.
  3. Ez azt jelenti, hogy soha nincs váratlan eltérés a read() metódusnak átadott szám és a tell() metódus által visszaadott pozícióindex között. A read() metódus bájtokat olvas, és a seek() és tell() metódusok a beolvasott bájtok számát tartják nyilván. A bináris fájlok esetén ezek mindig megegyeznek.

Adatfolyam-objektumok nem fájl forrásokból

Képzeld el, hogy egy függvénytárat írsz, és a függvénytárad egyik függvénye adatokat fog olvasni egy fájlból. A függvény egyszerűen várhat egy fájlnevet karakterláncként, megnyithatja a fájlt olvasásra, és bezárhatja kilépés előtt. De ne csináld ezt. Ehelyett az API-d várhat egy tetszőleges adatfolyam-objektumot.

A legegyszerűbb esetben az adatfolyam-objektum bármi lehet, ha rendelkezik egy read() metódussal, ami egy elhagyható size paramétert vár, és visszaad egy karakterláncot. Amikor size paraméter nélkül hívják, a read() metódus be kell olvasson mindent a bemeneti forrásból, és vissza kell adnia az összes adatot egyetlen értékként. Amikor size paraméterrel hívják, annyit olvas a bemeneti forrásból, és annyi adatot ad vissza. Ha újra meghívják, ott folytatja, ahol abbahagyta, és visszaadja a következő adatdarabot.

Ez pontosan úgy hangzik, mint a valódi fájl megnyitásakor kapott adatfolyam-objektum. A különbség, hogy nem korlátozod magad valódi fájlokra. Az „olvasott” bemeneti forrás bármi lehet: egy weboldal, egy karakterlánc a memóriában, akár egy másik program kimenete is. Amíg a függvényeid egy adatfolyam-objektumot várnak, és egyszerűen meghívják az objektum read() metódusát, tetszőleges bemeneti forrást kezelhetsz, ami fájlként viselkedik, a különböző bemenettípusokat külön-külön kezelő kód nélkül.

>>> egy_karakterlánc = 'PapayaWhip is the new black.'
>>> import io                                  
>>> egy_fájl = io.StringIO(egy_karakterlánc)   
>>> egy_fájl.read()                              
'PapayaWhip is the new black.'
>>> egy_fájl.read()                              
''
>>> egy_fájl.seek(0)                             
0
>>> egy_fájl.read(10)                            
'PapayaWhip'
>>> egy_fájl.tell()
10
>>> egy_fájl.seek(18)
18
>>> egy_fájl.read()
'new black.'
  1. Az io modul definiálja a StringIO osztályt, amelyet egy memóriabeli karakterlánc fájlként kezelésére használhatsz.
  2. A karakterláncból adatfolyam-objektum létrehozásához példányosítsd az io.StringIO() osztályt, és add át neki a „fájladatként” használandó karakterláncot. Most már van egy adatfolyam-objektumod, és mindenféle adatfolyam-szerű dolgot csinálhatsz vele.
  3. A read() metódus hívása „beolvassa” az egész „fájlt,” ami a StringIO objektum esetén egyszerűen visszaadja az eredeti karakterláncot.
  4. Ahogyan egy valódi fájlnál, a read() metódus ismételt hívása üres karakterláncot ad vissza.
  5. Expliciten a karakterlánc elejére pozicionálhatsz, ahogyan egy valódi fájlban pozicionálnál, aStringIO objektum seek() metódusnak használatával.
  6. A karakterláncot darabonként is beolvashatod, a size paraméter átadásával a read() metódusnak.

Az io.StringIO lehetővé teszi egy karakterlánc szövegfájlként kezelését. Létezik egy io.BytesIO osztály is, ami lehetővé teszi egy bájttömb bináris fájlként kezelését.

Tömörített fájlok kezelése

A Python szabványos függvénytára tartalmaz a tömörített fájlok olvasását és írását támogató modulokat. Számos különböző tömörítési eljárás létezik, a két legnépszerűbb nem Windows rendszereken a gzip és bzip2. (Találkozhattál PKZIP archívumokkal és GNU Tar archívumokkal is. A Python rendelkezik modulokkal ezekhez is.)

A gzip modul lehetővé teszi gzip-pel tömörített fájl olvasásához vagy írásához használható adatfolyam-objektum létrehozását. Az általa visszaadott adatfolyam-objektum támogatja a read() metódust (ha olvasásra nyitottad meg), vagy a write() metódust (ha írásra nyitottad meg). Ez azt jelenti, hogy használhatod a normális fájlokhoz megismert metódusokat gzip-pel tömörített fájl közvetlen olvasására vagy írására, anélkül, hogy ideiglenes fájlt kellene létrehoznod a kibontott adatok tárolásához.

További extraként támogatja a with utasítást is, így hagyhatod, hogy a Python automatikusan bezárja a gzip-pel tömörített fájlodat, ha végeztél vele.

te@localhost:~$ python3

>>> import gzip
>>> with gzip.open('out.log.gz', mode='wb') as z_fájl:                                      
...   z_fájl.write('Egy kilenc mérföldes séta nem vicc, különösen nem az esőben.'.encode('utf-8'))
... 
>>> exit()

te@localhost:~$ ls -l out.log.gz                                                           
-rw-r--r--  1 mark mark    79 2009-07-19 14:29 out.log.gz
te@localhost:~$ gunzip out.log.gz                                                          
te@localhost:~$ cat out.log                                                                
Egy kilenc mérföldes séta nem vicc, különösen nem az esőben.
  1. A gzip-pel tömörített fájlokat mindig bináris módban kell megnyitni. (Vedd észre a 'b' karaktert a mode argumentumban.)
  2. Ezt a példát Linuxon állítottam elő. Ha nem ismered a parancssort: ez a parancs a Python parancsértelmezőben létrehozott, gzip-pel tömörített fájl „hosszú kiírását” jeleníti meg. Ez a kiírás megmutatja, hogy a fájl létezik (jó), és hogy 79xxx bájt hosszú. Ez valójában hosszabb, mint az a karakterlánc, amivel indultunk! A gzip fájlformátum tartalmaz egy rögzített hosszúságú fejlécet, ami néhány metaadatot tartalmaz a fájlról, így nagyon kis fájlok esetén nem hatékony.
  3. A gunzip parancs (ejtsd: „gé-unzip”) kibontja a fájlt, és a tartalmát egy új fájlban tárolja, aminek ugyanaz lesz a neve, mint a tömörített fájlnak, de a .gz fájlkiterjesztés nélkül.
  4. A cat parancs megjeleníti egy fájl tartalmát. Ez a fájl tartalmazza azt a karakterláncot, amit eredetileg közvetlenül az out.log.gz fájlba írtál a Python parancsértelmezőben.

Ezt a hibát kaptad?

>>> with gzip.open('out.log.gz', mode='wb') as z_fájl:
...         z_fájl.write('Egy kilenc mérföldes séta nem vicc, különösen nem az esőben.'.encode('utf-8'))
... 
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
AttributeError: 'GzipFile' object has no attribute '__exit__'

Ha igen, akkor valószínűleg a Python 3.0-át használod. Komolyan frissítened kellene legalább Python 3.1-re.

A Python 3.0 rendelkezett egy gzip modullal, de nem támogatta a gzip-pel tömörített fájl objektum kontextuskezelőként való használatát. A Python 3.1 már képes a gzip-pel tömörített fájl objektumok használatára with utasításban.

Szabványos bemenet, kimenet és hibakimenet

A parancssori guruknak már ismerős a szabványos bemenet, szabványos kimenet és szabványos hibakimenet fogalma. Ez a szakasz a többieknek szól.

A szabványos kimenet és a szabványos hibakimenet (gyakran stdout és stderr rövidítésekkel használják) olyan adatcsatornák, amik minden UNIX-szerű rendszerbe be vannak építve, beleértve a Mac OS X-et és Linuxot. Amikor a print() függvényt hívod, a kiírt dolog az stdout adatcsatornába kerül. Amikor a programod összeomlik, és nyomkövetést ír ki, az az stderr adatcsatornába kerül. Alapértelmezésben ez a két adatcsatorna egyaránt arra a terminálablakra van irányítva, amiben dolgozol, amikor a programod kiír valamit, a kimenetet a terminálablakban látod, és amikor a program összeomlik, az is a terminálablakban jeleníti meg a nyomkövetést. A grafikus Python parancsértelmezőben az stdout és stderr adatcsatornák alapértelmezésben az „interaktív ablakba” vannak irányítva.

>>> for i in range(3):
...     print('PapayaWhip')                
PapayaWhip
PapayaWhip
PapayaWhip
>>> import sys
>>> for i in range(3):
...     l = sys.stdout.write('is the')     
is theis theis the
>>> for i in range(3):
...     l = sys.stderr.write('new black')  
new blacknew blacknew black
  1. A print() függvény, ciklusban. Semmi meglepő.
  2. Az stdout a sys modulban van definiálva, és ez egy adatfolyam-objektum. A write() függvényének meghívása kiírja a neki átadott tetszőleges karakterláncot, majd visszaadja a kimenet hosszát. Valójában ez az, amit a print függvény ténylegesen csinál; hozzáad egy kocsivissza karaktert a kiírandó karakterlánc végéhez, és meghívja a sys.stdout.write-ot.
  3. A legegyszerűbb esetben a sys.stdout és a sys.stderr a kimenetüket ugyanoda küldik: a Python IDE-nek (ha ezt használod) vagy a terminálnak (ha a Pythont a parancssorból futtatod). A szabványos kimenethez hasonlóan a szabványos hibakimenet sem ad kocsivissza karaktereket a szöveghez. Ha kocsivissza karaktereket akarsz, akkor kocsivissza karaktereket kell írnod.

A sys.stdout és sys.stderr adatfolyam-objektumok, de csak írhatók. A read() metódusuk hívására tett kísérlet mindig IOError kivételt dob.

>>> import sys
>>> sys.stdout.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: not readable

A szabványos kimenet átirányítása

A sys.stdout és sys.stderr adatfolyam-objektumok, noha csak az írást támogatják. De nem konstansok, hanem változók. Ez azt jelenti, hogy új értéket rendelhetsz hozzájuk – bármely másik adatfolyam-objektumot – hogy az irányítsa át a kimenetét.

[az stdout.py letöltése]

import sys

class RedirectStdoutTo:
    def __init__(self, ki_új):
        self.ki_új = ki_új

    def __enter__(self):
        self.ki_régi = sys.stdout
        sys.stdout = self.ki_új

    def __exit__(self, *args):
        sys.stdout = self.ki_új

print('A')
with open('out.log', mode='w', encoding='utf-8') as egy_fájl, RedirectStdoutTo(egy_fájl):
    print('B')
print('C')

Nézd csak:

te@localhost:~/diveintopython3/examples$ python3 stdout.py
A
C
te@localhost:~/diveintopython3/examples$ cat out.log
B

Ezt a hibát kaptad?

te@localhost:~/diveintopython3/examples$ python3 stdout.py
  File "stdout.py", line 15
    with open('out.log', mode='w', encoding='utf-8') as egy_fájl, RedirectStdoutTo(egy_fájl):
                                                                ^
SyntaxError: invalid syntax

Ha igen, akkor valószínűleg a Python 3.0-át használod. Komolyan frissítened kellene legalább Python 3.1-re.

A Python 3.0 támogatta a with utasítást, de minden utasítás csak egy kontextuskezelőt tud használni. A Python 3.1 lehetővé teszi több kontextuskezelő összekötését egyetlen with utasításban.

Vegyük először az utolsó részt.

print('A')
with open('out.log', mode='w', encoding='utf-8') as egy_fájl, RedirectStdoutTo(egy_fájl):
    print('B')
print('C')

Ez egy bonyolult with utasítás. Hadd írjam át valami felismerhetőbbre.

with open('out.log', mode='w', encoding='utf-8') as egy_fájl:
    with RedirectStdoutTo(egy_fájl):
        print('B')

Ahogyan az újraírás mutatja, valójában két with utasításunk van, az egyik beágyazva a másik hatókörébe. A „külső” with utasításnak mostanra ismerősnek kell lennie: megnyit egy UTF-8-kódolású, out.log nevű fájlt írásra, és az adatfolyam-objektumot hozzárendeli az egy_fájl nevű változóhoz. De itt nem ez az egyetlen fura dolog.

with RedirectStdoutTo(egy_fájl):

Hol van az as kulcsszó? Valójában a with utasítás nem igényli. Mint ahogy meghívsz egy függvényt, és figyelmen kívül hagyod a visszatérési értékét, ugyanúgy használhatsz olyan with utasítást, ami nem rendeli a with kontextust változóhoz. Ebben az esetben csak a RedirectStdoutTo kontextus mellékhatásai érdekelnek.

Mik ezek a mellékhatások? Vessünk egy pillantást a RedirectStdoutTo osztályra. Ez az osztály egy egyéni kontextuskezelő. Bármely osztály lehet kontextuskezelő, ezen két speciális metódus definiálásával: __enter__() és __exit__().

class RedirectStdoutTo:
    def __init__(self, ki_új):    
        self.ki_új = ki_új

    def __enter__(self):            
        self.ki_régi = sys.stdout
        sys.stdout = self.ki_új

    def __exit__(self, *args):      
        sys.stdout = self.ki_régi
  1. Az __init__() metódus a példány létrehozása után azonnal meghívásra kerül. Egy paramétert vár, a kontextus élete során szabványos kimenetként használni kívánt adatfolyam-objektumot. Ez a metódus egyszerűen egy példányváltozóban menti az adatfolyam-objektumot, hogy később más metódusok is használhassák.
  2. Az __enter__() metódus egy speciális osztálymetódus. A Python akkor hívja meg, amikor belép a kontextusba (azaz a with utasítás elején). Ez a metódus elmenti a sys.stdout aktuális értékét a self.ki_régi változóba, majd átirányítja a szabványos kimenetet a self.ki_új hozzárendelésével a sys.stdout-hoz.
  3. Az __exit__() metódus egy másik speciális osztálymetódus. A Python a kontextusból való kilépéskor hívja meg (azaz a with utasítás végén). Ez a metódus visszaállítja a szabványos kimenetet az eredeti értékére az elmentett self.ki_régi hozzárendelésével a sys.stdout-hoz.

Összegezve:


print('A')                                                                               
with open('out.log', mode='w', encoding='utf-8') as egy_fájl, RedirectStdoutTo(a_file):  
    print('B')                                                                           
print('C')                                                                               
  1. Ez az IDE-be „interaktív ablakba” (vagy a terminálba, ha a parancssorból futtatod a parancsfájlt) fog írni.
  2. Ez a with utasítás kontextusok vesszőkkel elválasztott listáját várja. A vesszőkkel elválasztott lista egymásba ágyazott with blokkok sorozataként működik. Az elsőként felsorolt kontextus a „külső” blokk, az utolsóként felsorolt a „belső” blokk. Az első kontextus megnyit egy fájlt, a második kontextus átirányítja a sys.stdout-ot az első kontextusban létrehozott adatfolyam-objektumra.
  3. Mivel ez a print() függvény a with utasítással létrehozott kontextussal kerül végrehajtásra, nem a képernyőre fog írni, hanem az out.log fájlba.
  4. A with kódblokknak vége van. A Python közölte minden egyes kontextuskezelővel, hogy tegyék meg azt, amit a kontextusból kilépéskor szoktak. A kontextuskezelők egy utolsó-be, első-ki (LIFO) vermet alkotnak. Kilépéskor a második kontextus visszaállította a sys.stdout-ot az eredeti értékére, majd az első kontextus bezárta az out.log nevű fájlt. Mivel a szabványos kimenet vissza lett állítva az eredeti értékére, a print() függvény hívása újra a képernyőre fog írni.

A szabványos hibakimenet átirányítása pontosan ugyanígy működik, a sys.stderr-t használva a sys.stdout helyett.

További olvasnivaló

© 2001–11 Mark Pilgrim