7. Bemenet és kimenet

Egy program kimenete többféleképpen jelenhet meg: például ember által olvasható nyomtatott formában, vagy későbbi feldolgozás céljából fájlba írva. Ez a fejezet a lehetőségek közül többet is bemutat.

7.1. Esztétikus kimenet kialakítása

Az értékek kiírására két lehetőségünk van: kifejezéssel és a print() függvénnyel. (Egy harmadik lehetőség a fájlobjektumok write() metódusának használata; a szabványos kimeneti fájlt a sys.stdout-ként érhetjük el. A referencia könyvtárt érdemes megnézni (Library Reference), ott több információt találunk a szabványos kimenet használatáról.)

Gyakori igény, hogy a programozó az egyszerű - szóközökkel tagolt kimeneti formázásnál több lehetőséget szeretne. A kétféleképpen formázhatod a kimenetet. Az egyik esetben a kimeneti karakterláncok teljes formázását te kezeled – a karakterláncok szeletelésével és összeillesztésével bármilyen elképzelt kimenetet előállíthatsz. A karakterlánc típusnak van pár hasznos metódusa pl. karakterlánc kitöltésére adott szélesség eléréséhez – ezeket hamarosan bemutatjuk. A második lehetőség a formatted string literals vagy a str.format() metódus használata.

A string modulnak van egy Template osztálya ami még egy újabb lehetőséget nyújt arra, hogy karakterláncokba értékeket helyettesítsünk.

Egy kérdés maradt hátra: hogy hogyan konvertálhatsz egy értéket karakterlánccá? Szerencsére a Python erre több lehetőséget is biztosít: add át a karakterláncot a repr() vagy az str() függvényeknek.

Az str() függvény az értékek ember által olvasható formájával tér vissza, míg a repr() függvény visszatérési értéke az interpreter számára értelmezhető adat (vagy SyntaxError kivételt vált ki ha nincs megfelelő szintaxis.)

Azon objektumok esetében, amelyeknek nincs emberi olvasásra szánt ábrázolása, az str() függvény ugyanazzal az értékkel tér vissza, mintha a repr() függvényt hívtuk volna meg. Néhány érték, például a számok vagy a struktúrák esetében – mint például a listák vagy a szótárak, bármely két fent említett függvényt használva ugyanazt az eredményt kapjuk. A karakterláncoknak nevezetesen két eltérő ábrázolási módjuk van.

Néhány példa:

>>> s = 'Helló, világ.'
>>> str(s)
'Helló, világ.'
>>> repr(s)
"'Helló, világ.'"
>>> str(1/7)
'0.14285714285714285'
>>> x = 10 * 3.25
>>> y = 200 * 200
>>> s = 'x értéke: ' + repr(x) + ', és y értéke: ' + repr(y) + '...'
>>> print(s)
x értéke: 32.5, és y értéke: 40000...
>>> # A repr() függvény idézőjelek közé rakja a karakterláncot,
>>> # és kijelzi a különleges karaktereket is:
... hello = 'helló világ\n'
>>> hellos = repr(hello)
>>> print(hellos)
'helló világ\n'
>>> # A repr() függvénynek akármilyen objektumot is átadhatunk:
... repr((x, y, ('hús', 'tojás')))
"(32.5, 40000, ('hús', 'tojás'))"

Ha akarunk készíteni egy táblázatot, amiben a számok második és harmadik hatványai szerepelnek, két lehetőségünk is van:

>>> for x in range(1, 11):
...     print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')
...     # Figyeljük meg az end használatát az előző sorban.
...     print(repr(x*x*x).rjust(4))
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

>>> for x in range(1,11):
...     print('{0:2d} {1:3d} {2:4d}'.format(x, x*x, x*x*x))
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

(Megjegyzés: az oszlopok között egy szóköznyi helyet a print utasítás hagyott ki, mindig szóközöket hagy az argumentumai között.)

Ez a példa bemutatja a karakterlánc objektumok str.rjust() metódusát, ami a megadott karakterláncot jobbra igazítja, majd a megadott szélességig feltölti üres karakterekkel a baloldalt. Ehhez hasonlóak a ljust() és a center() függvények. Ezek írási műveletet nem végeznek, egyszerűen visszatérnek az új karakterlánccal. Ha a bemenetként megadott szöveg túl hosszú, azt nem csonkolják, hanem változatlanul adják vissza az eredeti karakterláncot. Ez elrontja ugyan a kimenet rendezettségét, de rendszerint jobb, mintha a függvény valótlan értékkel térne vissza. (Ha valóban szeletelni akarod a karakterláncot, ezt így tudod megtenni: x.ljust(n)[:n].)

Létezik egy másik metódus, a zfill(), amely az adott numerikus karakterláncot balról nulla karakterekkel tölti fel. Ez érti a plusz és mínusz előjeleket:

>>> '12'.zfill(5)
'00012'
>>> '-3.14'.zfill(7)
'-003.14'
>>> '3.14159265359'.zfill(5)
'3.14159265359'

A str.format() metódus legalapvetőbb használata így működik:

>>> print('Mi vagyunk {} akik azt mondják "{}!"'.format('a lovagok', 'Ni'))
Mi vagyunk a lovagok, akik azt mondják "Ni!"

A kapcsos zárójelek, és az azon belül található karakterek (az úgynevezett formátum-mezők) helyettesítődnek a str.format() metódusnak átadott objektumokkal. A zárójelben szerepelhet egy szám, amely az objektum helyére utal a str.format() metódusban.

>>> print('{0} és {1}'.format('spam', 'tojások'))
spam és tojások
>>> print('{1} és {0}'.format('spam', 'tojások'))
tojások és spam

Ha kulcsszavas argumentumokat használunk a str.format() metódusban, az értékeikre az argumentum neve alapján hivatkozhatunk.

>>> print('Ez {etel} meglehetősen {jelzo}.'.format(
...       etel='a sajt', jelzo='sós'))
Ez a sajt meglehetősen sós.

A hely szerinti és a kulcsszavas argumentumok tetszőlegesen keverhetőek:

>>> print('{0}, {1} és {other} története.'.format('Asterix', 'Obelix',
                                                       other='Idefix'))
Asterix, Obelix és Idefix története.

A '!a' (ascii() alkalmazása), a '!s' (str() alkalmazása) és a '!r' (repr() alkalmazása) használható arra, hogy az értékeket konvertáljuk a formázás előtt:

>>> tartalom = 'angolnával'
>>> print('A légpárnás hajóm tele van {}.'.format(tartalom))
A légpárnás hajóm tele van angolnával.
>>> print('A légpárnás hajóm tele van {!r}.'.format(tartalom))
A légpárnás hajóm tele van 'angolnával'.

Egy ':' és egy formátumleírás követheti a mező nevét. Ez nagyobb szabadságot biztosít az érték formázásában. A következő példa 3 tizedesjegyre kerekíti a pi értékét.

>>> import math
>>> print('PI értéke megközelítőleg {0:.3f}.'.format(math.pi))
PI értéke megközelítőleg 3.142.

Ha egy egészet írunk a ':' után, akkor az a mező legalább olyan széles lesz. Ez szép táblázatok készítésekor hasznos.

>>> tablazat = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
>>> for nev, telefon in tablazat.items():
...     print('{0:10} ==> {1:10d}'.format(nev, telefon))
...
Jack       ==>       4098
Dcab       ==>       7678
Sjoerd     ==>       4127

Ha nagyon hosszú formátum-karakterláncunk van, amit nem akarunk feldarabolni, jól jönne, ha a változókra hely helyett névvel tudnánk hivatkozni. Ezt megtehetjük úgy, hogy szótárat adunk meg, és szögletes zárójelet '[]' használunk, hogy elérjük a kulcsokat

>>> tablazat = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print('Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '
...       'Dcab: {0[Dcab]:d}'.format(tablazat))
Jack: 4098; Sjoerd: 4127; Dcab: 8637678

Ezt úgy is megtehetjük, hogy a tablazat-ot kulcsszavas argumentumokkal adjuk meg a ‘**’ jelölésmóddal.

>>> tablazat = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print('Jack: {Jack:d}; Sjoerd: {Sjoerd:d}; Dcab: {Dcab:d}'.format(**tablazat))
Jack: 4098; Sjoerd: 4127; Dcab: 8637678

Ez különösen hasznos az új vars() függvénnyel együtt, amely egy szótár típusú változóval tér vissza, amely az összes helyi változót tartalmazza.

A str.format() metódussal történő karakterlánc-formázás teljes áttekintése itt található meg: Format String Syntax.

7.1.1. A régi típusú formázás

A % operátort is használhatjuk karakterlánc-formázására. A baloldali argumentumot nagyjából a C programozási nyelv sprintf() függvény formátum-karakterláncához hasonlít amelyet a jobboldali argumentumra alkalmazunk:

>>> import math
>>> print('PI értéke megközelítőleg %5.3f.' % math.pi)
PI értéke megközelítőleg 3.142.

További információ a printf-style String Formatting szakaszban található.

7.2. Fájlok írása és olvasása

Az open() függvény egy fájlobjektummal tér vissza, és rendszerint két paraméterrel használjuk: open(fájlnév, mód).

>>> f=open('munkafile', 'w')

>>> print(f)
<open file 'munkafile', mode 'w' at 80a0960>

Az első paraméter egy fájlnevet tartalmazó karakterlánc. A második paraméter néhány karakterből áll csupán, és a fájl használatának a módját írja le. A megnyitás módja (mód) lehet 'r' mikor csak olvassuk a fájlt, 'w', ha kizárólag írni szeretnénk (a már esetleg ugyanezen néven létező fájl tartalma törlődik!), és 'a', amikor hozzáfűzés céljából nyitjuk meg a fájlt; ilyenkor bármilyen adat, amit a fájlba írunk, automatikusan hozzáfűződik annak a végéhez. A 'r+' megnyitási mód egyszerre nyitja meg a fájlt írásra és olvasásra. A mód paraméter beállítása nem kötelező; elhagyása esetén 'r' alapértelmezett értéket vesz fel.

Normál esetben a fájlt szövegmódban nyitjuk meg, ami azt jelenti, hogy a fájlból olvasunk, írunk bele egy speciális karakterkódolás szerint. Ha nincs megadva a kódolás, akkor az alapértelmezett érték platformfüggő (lásd az open() függvényt). A megnyitási módhoz hozzáfűzött 'b' karakterrel bináris mód használatával nyithatjuk meg a fájlt: ilyenkor az adat olvasása és írása bájtobjektumok formájában történik. Ezt használjuk, ha a fájl nem szöveget tartalmaz.

A szövegmódban az az alapértelmezett, hogy olvasáskor a platform-specifikus sorvégek (\n Unixon, \r\n Windowson) simán \n alakra konvertálódnak. Ha szöveg módban írunk, az \n visszakonvertálódik platform-specifikussá. Ez a színfal mögötti módosítás megfelelő szövegfájlok esetén, de a bináris fájlokat használhatatlanná teheti (pl. JPG vagy .EXE fájlokat). Ezért nagyon figyeljünk oda, hogy a bináris módot használjunk ilyen fájlok olvasáskor és íráskor.

7.2.1. A fájl objektumok metódusai

A fejezet további példái feltételezik, hogy már létezik az f fájl objektum.

A fájl tartalmának olvasásához hívd meg az f.read(méret) metódust, ami a megadott adatmennyiségnek megfelelő hosszúságú karakterlánccal tér vissza. A méret egy opcionális paraméter – elhagyása, vagy negatív értéke esetén a teljes tartalmat visszaadja a metódus – ha esetleg a fájl kétszer akkora, mint a gépedben lévő memória, az esetleg problémát jelenthet neked.

Ha használod a méret paramétert, akkor a visszatérési értékként kapott karakterlánc hosszát maximalizálni tudod. Ha eléred a fájl végét, az f.read() egy üres karakterlánccal tér vissza ("").

>>> f.read()
'Ez a fájl teljes tartalma.\n'
>>> f.read()
''

A f.readline() egy sort olvas ki a fájlból. A sor végét az újsor karakter (\n) jelenti, amely a beolvasott karakterlánc végén található. Ez a karakter egyetlen esetben maradhat ki a visszaadott karakterlácból: ha a fájl utolsó sorát olvassuk be, és az nem újsor karakterre végződik.

Ez a visszatérési értéket egyértelművé teszi: ha a f.readline() metódus üres karakterlánccal tér vissza, az olvasás elérte a fájl végét, üres sor esetén viszont a '\n' jelképezi a sort, a karakterlánc, amely egyetlen egy újsor karaktert tartalmaz.

>>> f.readline()
'Ez a fájl első sora.\n'
>>> f.readline()
'A fájl második sora.\n'
>>> f.readline()
''

Ahhoz, hogy egy fájl sorain végighaladjunk, egy ciklust használhatunk a fájlobjektumon. Ez memóriahasználat szempontjából hatékony, gyors, és egyszerű kódhoz vezet:

>>> for sor in f:
...     print(sor, end='')
...
Ez a fájl első sora.
A fájl második sora.

Ha a fájl összes sorát egy listába szeretnénk beolvastatni, akkor használhatjuk ezeket is: list(f) vagy f.readlines().

>>> list(f)
['Ez a fájl első sora.\n', 'Ez pedig a második\n']

Az f.write(karakterlánc) metódus a karakterlánc tartalmát a fájlba írja, és kiírt karakterek számával tér vissza.

>>> f.write('Tesztszöveg, az írás bemutatására\n')
34

Ha egy karakterlánctól eltérő típusú változót szeretnénk kiírni, akkor azt előbb karakterlánccá kell konvertálni:

>>> ertek = ('a válasz', 42)
>>> s = str(ertek)
>>> f.write(s)
16

Az f.tell() metódus egy egésszel tér vissza, amely a fájlobjektum aktuális pozícióját adja meg bájtban mérve a fájl kezdetétől számolva, ha bináris módban vagyunk, szövegmódban pedig egy nehezen értelmezhető számot.

A fájlobjektum pozíciójának a megváltoztatásához a f.seek(offset, innen_kezdve) metódust használjuk. Az új pozíciót úgy kapjuk, hogy a hivatkozási ponthoz hozzáadjuk az offset értékét; a referenciapont pedig az innen_kezdve értékétől függ.

A hivatkozási pont az innen_kezdve 0 értéke esetén a fájl eleje lesz, 1 esetén aktuális pozíció, 2 esetén a fájl vége. Az innen_kezdve érték elhagyása esetén 0 az alapértelmezett érték, azaz a fájl kezdőpontját használja hivatkozási pontnak.

>>> f = open('munkafajl', 'rb+')
>>> f.write(b'0123456789abcdef')
16
>>> f.seek(5)     # Ugorj a 6. bájthoz a fájlban
5
>>> f.read(1)
b'5'
>>> f.seek(-3, 2) # Ugorj a fájl végéhez képest három karakterrel vissza
>>> f.read(1)
b'd'

A szövegfájlokban (azokban, amelyeket a b mód-karakterlánc nélkül nyitottunk meg), csak a fájl elejétől számított keresés engedélyezett (kivéve a fájl legvégétől a seek(0, 2) utasítással) és az egyetlen megengedett offset érték az, amelyikkel az f.tell() tér vissza, vagy a nulla. Minden egyéb offset érték nem meghatározott viselkedést eredményezhet.

Ha már minden módosítást elvégeztél a fájllal, hívd meg az f.close() metódust a bezárásához, és megnyitott fájl által felhasznált rendszererőforrások felszabadításához. A f.close() hívása után a fájl objektum használatára tett kísérletek automatikusan meghiúsulnak.

>>> f.close()
>>> f.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: I/O operation on closed file

Jó gyakorlat a with kulcsszó használata, amikor fájlobjektummal foglalkozunk. Ennek az az előnye, hogy a fájl megfelelően bezáródik, akkor is, ha közben kivétel lép fel. És sokkal rövidebb is, mint a megfelelő try-finally blokk használata:

>>> with open('munkafajl', 'r') as f:
...     olvasott_adatok = f.read()
>>> f.closed
True

A fájl objektumoknak van néhány további metódusa, például az isatty() és a truncate(), ezeket ritkábban használjuk. A Referencia könyvtárban teljes útmutatót találsz a fájl objektumok használatához.

7.2.2. Strukturált adat mentése json formátumba

A karakterláncokat könnyen lehet fájlba írni és onnan beolvasni. A számokkal egy fokkal nehezebb a helyzet, mert a

read() metódus csak karakterlánccal tér vissza, amelyet át kell

adni egy olyan függvénynek, mint az int(), amely egy karakterláncot vár, mint amilyen a '123' és visszatér a számértékével, a 123-mal. Ha még összetettebb adattípusokat szeretnénk elmenteni, mint amilyenek az egymásba ágyazott listák, vagy szótárak, a visszaállítás vagy a mentés (szerializáció) bonyolulttá válik.

Ahelyett, hogy a felhasználónak állandóan kódot kellene írnia és tesztelnie, a Python lehetőséget nyújt egy közkedvelt adatcserélő formátum, a JSON (JavaScript Object Notation), használatára. A json sztenderd modul a Python adatszerkezeteit képes karakterlánccá alakítani; ezt a folyamat a szerializáció. A karakterláncból az adat visszaállítása pedig a deszerializáció. A szerializáció és a deszerializáció között a karakterlánc-reprezentációt fáljban tárolhatjuk, vagy elküldhetjük hálózati kapcsolaton keresztül egy távoli számítógépnek.

Note

A JSON formátumot gyakran használják modern alkalmazásokban, hogy lehetővé tegyék az adatcserét. Sok programozó számára ismerős már, amely jó választássá teszi az együttműködés szempontjából.

Ha van egy x objektumunk, annak a JSON karakterlánc-reprezentációját egyetlen sor kóddal megkaphatjuk:

>>> json.dumps([1, 'simple', 'list'])
'[1, "simple", "list"]'

A dumps() függvény egy másik változata, amelynek dump(), a neve az objektumot egy szövegfájlba szerializálja. Tehát, ha f egy szövegfájl-objektum, amelyet írásra nyitottunk meg, megtehetjük az alábbit:

json.dump(x, f)

Az objektum visszaállítása pedig, ha f egy olvasásra megnyitott szövegfájl-objektum, így történik:

x = json.load(f)

Ez az egyszerű szerializációs technika képes listákat és szótárakat kezelni, de általános osztálypéldányok szerializációja további erőfeszítést igényel. A json modul referenciájában magyarázatot találhatunk erre.

See also

pickle - a pickle modue

Szemben a JSON-nal, a pickle egy protokoll, amely lehetővé teszi tetszőlegesen összetett Python-objektum szerializációját. Mint ilyen, a Pythonhoz kötődik, és nem alkalmas más nyelven írott alkalmazásokkal történő adatcserére. És alapvetően nem biztonságos: megbízhatatlan forrásból származó pickle adat deszerializációja képes végrehajtani tetszőleges kódot, ha az adatot gyakorlott támadó hozta létre.