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

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

Feldolgozók

Képzeletünk a végsőkig van feszítve, nem azért, hogy irodalmi, nem létező dolgokat képzeljünk el, hanem hogy megértsük a valóban létezőket.
Richard Feynman

 

Ugorj fejest

Minden programozási nyelvnek van egy szolgáltatása, egy bonyolult dolog, amelyet szándékosan egyszerűvé tesz. Ha más nyelvet használsz, könnyen lemaradhatsz róla, mert a régi nyelv nem tette azt a dolgot egyszerűvé (mert inkább valami mást tett egyszerűvé). Ez a fejezet elmagyarázza a listafeldolgozókat, szótárfeldolgozókat és halmazfeldolgozókat: három kapcsolódó fogalom, amelyek egy nagyon hatékony módszer köré csoportosulnak. De előbb tegyünk egy kis kitérőt két modulra, amelyek segíteni fognak a helyi fájlrendszeren való navigációban.

Fájlok és könyvtárak kezelése

A Python 3 tartalmaz egy os nevű modult, amely az „operating system” (operációs rendszer) rövidítése. Az os modul rengeteg függvényt tartalmaz, amelyekkel információk kérhetők a helyi könyvtárakról, fájlokról, folyamatokról és környezeti változókról – és egyes esetekben ezek kezelhetők is. A Python mindent megtesz azért, hogy egy egységesített API-t kínáljon minden támogatott operációs rendszeren, így programjaid bármely számítógépen a lehető legkevesebb platformspecifikus kóddal futhatnak.

Az aktuális munkakönyvtár

Ha kezdő vagy a Pythonban, akkor rengeteg időt fogsz eltölteni a Python Shellben. Ebben a könyvben a következőhöz hasonló példákat fogsz látni:

  1. Importáld az egyik modult az examples mappából
  2. Hívj meg egy függvényt abban a modulban
  3. Magyarázd el az eredményt

Ha nem ismered az aktuális munkakönyvtárat, akkor az első lépés valószínűleg meghiúsul egy ImportError kivétellel. Miért? Mert a Python a példa modult az importálás keresési útvonalán fogja keresni, de nem fogja megtalálni, mert az examples mappa nem a keresési útvonal egyik könyvtára. Ennek megoldásához a következő két lehetőség egyikét használhatod:

  1. Add az examples mappát az importálás keresési útvonalához
  2. Váltsd át az aktuális munkakönyvtárat az examples mappára

Az aktuális munkakönyvtár egy láthatatlan tulajdonság, amelyet a Python mindig a memóriában tárol. Mindig van aktuális munkakönyvtár, akár a Python Shellben vagy, akár egy Python parancsfájlt futtatsz a parancssorból, akár egy Python CGI parancsfájlt futtatsz egy webkiszolgálón.

Az os modul két függvényt tartalmaz az aktuális munkakönyvtár kezeléséhez.

>>> import os                                            
>>> print(os.getcwd())                                   
C:\Python32
>>> os.chdir('/Users/pilgrim/diveintopython3/examples')  
>>> print(os.getcwd())                                   
C:\Users\pilgrim\diveintopython3\examples
  1. Az os modul a Python része: bármikor, bárhol importálhatod.
  2. Az os.getcwd() függvény használatával lekérdezheted az aktuális munkakönyvtárat. Ha a grafikus Python Shellt futtatod, akkor az aktuális munkakönyvtár az, ahonnan a Python Shell végrehajtható. Windowson ez attól függ, hogy hová telepítetted a Pythont; az alapértelmezett könyvtár a c:\Python32. Ha a Python Shellt a parancssorból futtatod, akkor az aktuális munkakönyvtár az, ahol a python3 elindításakor álltál.
  3. Az os.chdir() függvény használatával megváltoztathatod az aktuális munkakönyvtárat.
  4. Amikor az os.chdir() függvényt meghívtam, akkor egy Linux-stílusú útvonalnevet használtam (osztásjel, meghajtó betűjel nélkül) noha épp Windowst használtam. Ez az egyik olyan hely, ahol a Python megpróbálja eltüntetni az operációs rendszerek közti különbségeket.

Fájlnevek és könyvtárnevek kezelése

Ha már a könyvtáraknál vagyunk, szeretném bemutatni az os.path modult. Az os.path fájlnevek és könyvtárnevek kezelésére szolgáló függvényneveket tartalmaz.

>>> import os
>>> print(os.path.join('/Users/pilgrim/diveintopython3/examples/', 'humansize.py'))              
/Users/pilgrim/diveintopython3/examples/humansize.py
>>> print(os.path.join('/Users/pilgrim/diveintopython3/examples', 'humansize.py'))               
/Users/pilgrim/diveintopython3/examples\humansize.py
>>> print(os.path.expanduser('~'))                                                               
c:\Users\pilgrim
>>> print(os.path.join(os.path.expanduser('~'), 'diveintopython3', 'examples', 'humansize.py'))  
c:\Users\pilgrim\diveintopython3\examples\humansize.py
  1. Az os.path.join() függvény legalább egy részleges útvonalnévből készít egy útvonalnevet. Ebben az esetben egyszerűen összefűzi a karakterláncokat.
  2. Ebben a kevésbé triviális esetben az os.path.join() függvény egy extra osztásjelet ad az útvonalnévhez a fájlnévhez fűzés előtt. Ez egy fordított törtvonal az osztásjel helyett, mert a példa Windowson készült. Ha ezt a példát Linuxon vagy Mac OS X-en hajtod végre, akkor ehelyett egy osztásjelet kapsz. Ne vacakolj az osztásjelekkel, mindig használd az os.path.join() függvényt, és a Python megoldja.
  3. Az os.path.expanduser() függvény kiterjeszti az aktuális felhasználó saját könyvtárának ábrázolására ~ jelet használó útvonalnevet. Ez minden platformon működik, ahol a felhasználók rendelkeznek saját könyvtárral, beleértve a Linux, Mac OS X és Windows rendszereket. A visszaadott útvonal nem rendelkezik záró osztásjellel, de az os.path.join() függvényt ez nem zavarja.
  4. Ezen módszerek kombinálásával egyszerűen összeállíthatsz a felhasználó saját könyvtárában lévő könyvtárakra és fájlokra mutató útvonalneveket. Az os.path.join() függvény tetszőleges számú argumentumot képes feldolgozni. Rettenetesen megörültem, amikor ezt felfedeztem, mivel az egyik kis hülye függvényem az addSlashIfNecessary(), amelyet mindig meg kell írnom a szerszámos ládám egy új nyelven való kialakításakor. Ne írd meg ezt a kis hülye függvényt Pythonban, ezt a problémát okos emberek már megoldották helyetted.

Az os.path függvényeket tartalmaz a teljes útvonalnevek, könyvtárnevek és fájlnevek felosztására az azokat alkotó részekre.

>>> útvonal = '/Users/pilgrim/diveintopython3/examples/humansize.py'
>>> os.path.split(útvonal)                                          
('/Users/pilgrim/diveintopython3/examples', 'humansize.py')
>>> (kvtnév, fájlnév) = os.path.split(útvonal)                      
>>> kvtnév                                                          
'/Users/pilgrim/diveintopython3/examples'
>>> fájlnév                                                         
'humansize.py'
>>> (rövidnév, kiterjesztés) = os.path.splitext(fájlnév)            
>>> rövidnév
'humansize'
>>> kiterjesztés
'.py'
  1. A split függvény feloszt egy teljes útvonalnevet, és visszaad egy, az útvonalat és a fájlnevet tartalmazó tuple-t.
  2. Emlékszel, amikor azt mondtam, hogy egy függvényből több érték visszaadására használhatsz többváltozós értékadást? Az os.path.split() függvény pontosan ezt csinálja. A split függvény visszatérési értékét egy két változót tartalmazó tuple-höz rendeli. Minden változó a visszaadott tuple megfelelő elemének értékét kapja.
  3. Az első változó, a kvtnév megkapja az os.path.split() által visszaadott tuple első elemének értékét, a fájl útvonalát.
  4. A második változó, a fájlnév megkapja az os.path.split() által visszaadott tuple második elemének értékét, a fájl nevét.
  5. Az os.path tartalmazza az os.path.splitext() függvényt is, amely feloszt egy fájlnevet, és visszaadja a fájlnevet és a fájlkiterjesztést tartalmazó tuple-t. Ugyanezzel a módszerrel ezeket is önálló változókhoz rendelheted.

Könyvtárak felsorolása

A glob modul a szabványos Python függvénytár másik eszköze. Egyszerű módszert nyújt egy könyvtár tartalmának lekérésére a programodból, és a parancssor használata során már remélhetőleg megszokott helyettesítő karaktereket használja.

>>> os.chdir('/Users/pilgrim/diveintopython3/')
>>> import glob
>>> glob.glob('examples/*.xml')                  
['examples\\feed-broken.xml',
 'examples\\feed-ns0.xml',
 'examples\\feed.xml']
>>> os.chdir('examples/')                        
>>> glob.glob('*test*.py')                       
['alphameticstest.py',
 'pluraltest1.py',
 'pluraltest2.py',
 'pluraltest3.py',
 'pluraltest4.py',
 'pluraltest5.py',
 'pluraltest6.py',
 'romantest1.py',
 'romantest10.py',
 'romantest2.py',
 'romantest3.py',
 'romantest4.py',
 'romantest5.py',
 'romantest6.py',
 'romantest7.py',
 'romantest8.py',
 'romantest9.py']
  1. A glob modul egy helyettesítő karaktert tartalmazó karakterláncot vár, és visszaadja az arra illeszkedő összes fájl és könyvtár elérési útját. Ebben a példában a karakterlánc a könyvtár elérési útja, plusz a „*.xml”, amely az examples alkönyvtárban lévő összes .xml fájlra illeszkedik.
  2. Most váltsd át az aktuális munkakönyvtárat az examples alkönyvtárra. Az os.chdir() függvény elfogad relatív útvonalneveket is.
  3. A glob mintába több helyettesítő karakter is felvehető. Ez a példa megtalálja az aktuális munkakönyvtár összes .py kiterjesztésű, és atest szót a fájlnevükben bárhol tartalmazó fájlját.

Fájlmetaadatok lekérése

Minden korszerű fájlrendszer tárol metaadatokat a fájlokról: létrehozás dátuma, legutóbbi módosítás dátuma, fájlméret és így tovább. A Python egységes API-t biztosít ezen metaadatok eléréséhez. A fájlt nem kell megnyitnod, csak a fájlnévre van szükséged.

>>> import os
>>> print(os.getcwd())                 
c:\Users\pilgrim\diveintopython3\examples
>>> metadata = os.stat('feed.xml')     
>>> metadata.st_mtime                  
1247520344.9537716
>>> import time                        
>>> time.localtime(metadata.st_mtime)  
time.struct_time(tm_year=2009, tm_mon=7, tm_mday=13, tm_hour=17,
  tm_min=25, tm_sec=44, tm_wday=0, tm_yday=194, tm_isdst=1)
  1. Az aktuális munkakönyvtár az examples mappa.
  2. A feed.xml egy fájl az examples mappában. Az os.stat() függvény hívása egy olyan objektumot ad vissza, amely számos különböző típusú metaadatot tartalmaz a fájlról.
  3. Az st_mtime a módosítás ideje, de egy nem különösebben hasznos formátumban van. (Technikailag ez az Epoch, azaz 1970. január 1. első másodperce óta eltelt másodpercek száma. Komolyan.)
  4. A time modul a szabványos Python függvénytár része. A különböző időábrázolások közti átalakításhoz, az időértékek karakterláncokká formázásához és az időzónák kezeléséhez használható függvényeket tartalmaz.
  5. A time.localtime() függvény átalakít egy időértéket az Epoch-óta-eltelt-másodpercek formátumról (ezt az os.stat() függvény által visszaadott st_mtime tulajdonság tartalmazza) a sokkal hasznosabb év, hónap, nap, óra, perc, másodperc stb. formátumra. Ezt a fájlt utoljára 2009. július 13-án, délután 5.25 körül módosították utoljára.
# az előző példa folytatása
>>> metadata.st_size                              
3070
>>> import humansize
>>> humansize.approximate_size(metadata.st_size)  
'3.0 KiB'
  1. Az os.stat() függvény visszaadja a fájl méretét is, az st_size tulajdonságban. A feed.xml fájl 3070 bájt.
  2. Az st_size tulajdonságot átadhatod az approximate_size() függvénynek.

Abszolút útvonalnevek összeállítása

Az előző szakaszban a glob.glob() függvény relatív útvonalnevek listáját adta vissza. Az első példa olyan útvonalneveket tartalmaz, mint az 'examples\feed.xml', és a második példa még rövidebbeket, mint a 'romantest1.py'. Amíg ugyanabban az aktuális munkakönyvtárban maradsz, ezek a relatív útvonalnevek működni fognak a fájlok megnyitásakor vagy a metaadatok lekérésekor. Ha azonban abszolút – azaz a könyvtárneveket egészen a gyökérkönyvtárig vagy a meghajtó betűjelig tartalmazó – útvonalnevet szeretnél összeállítani, akkor az os.path.realpath() függvényt kell használnod.

>>> import os
>>> print(os.getcwd())
c:\Users\pilgrim\diveintopython3\examples
>>> print(os.path.realpath('feed.xml'))
c:\Users\pilgrim\diveintopython3\examples\feed.xml

Listafeldolgozók

A listafeldolgozó használatával tömören le lehet képezni egy listát egy másik listára egy függvény alkalmazásával a lista minden egyes elemére.

>>> a_list = [1, 9, 8, 4]
>>> [elem * 2 for elem in a_list]           
[2, 18, 16, 8]
>>> a_list                                  
[1, 9, 8, 4]
>>> a_list = [elem * 2 for elem in a_list]  
>>> a_list
[2, 18, 16, 8]
  1. Az értelmezéshez nézd jobbról balra. Az a_list a leképezett lista. A Python parancsértelmező elemenként végigjárja az a_list listát, az egyes elemek értékét pedig ideiglenesen az elem változóhoz rendeli. A Python ezután alkalmazza az elem * 2 függvényt, és az eredményt hozzáfűzi a visszaadott listához.
  2. A listafeldolgozó új listát hoz létre; az eredetit nem módosítja.
  3. A listafeldolgozó eredményét biztonságos hozzárendelni a leképezett változóhoz. A Python az új listát a memóriában állítja össze, és amikor a listafeldolgozó kész, akkor rendeli az eredményt az eredeti változóhoz.

Listafeldolgozóban tetszőleges Python kifejezést használhatsz, beleértve az os modul fájlok és könyvtárak kezelésére szolgáló függvényeit is.

>>> import os, glob
>>> glob.glob('*.xml')                                 
['feed-broken.xml', 'feed-ns0.xml', 'feed.xml']
>>> [os.path.realpath(f) for f in glob.glob('*.xml')]  
['c:\\Users\\pilgrim\\diveintopython3\\examples\\feed-broken.xml',
 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed-ns0.xml',
 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed.xml']
  1. Ez visszaadja az aktuális munkakönyvtárban lévő összes .xml fájl listáját.
  2. Ez a listafeldolgozó veszi az .xml fájlok előbbi listáját, és átalakítja azt teljes útvonalnevek listájává.

A listafeldolgozók szűrni is tudják az elemeket, ezáltal az eredeti listánál rövidebb eredményt előállítva.

>>> import os, glob
>>> [f for f in glob.glob('*.py') if os.stat(f).st_size > 6000]  
['pluraltest6.py',
 'romantest10.py',
 'romantest6.py',
 'romantest7.py',
 'romantest8.py',
 'romantest9.py']
  1. Egy lista szűréséhez felvehetsz egy if kifejezést a listafeldolgozó végére. Az if kulcsszó utáni kifejezés kiértékelődik a lista minden egyes elemére. Ha a kifejezés True-ra értékelődik ki, akkor a kimenet tartalmazni fogja az elemet. Ez a listafeldolgozó megnézi az aktuális könyvtár összes .py fájlját, és az if kifejezés annak alapján szűri ezt a listát, hogy az egyes fájlok mérete nagyobb-e 6000 bájtnál. Hat ilyen fájl van, így a listafeldolgozó hat fájlnévből álló listát ad vissza.

A listafeldolgozók az eddigi példákban egyszerű kifejezéseket mutattak be – egy szám konstanssal szorzása, egy függvény hívása, vagy egyszerűen az eredeti listaelem visszaadása (szűrés után). De a listafeldolgozó összetettségének nincs korlátja.

>>> import os, glob
>>> [(os.stat(f).st_size, os.path.realpath(f)) for f in glob.glob('*.xml')]            
[(3074, 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed-broken.xml'),
 (3386, 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed-ns0.xml'),
 (3070, 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed.xml')]
>>> import humansize
>>> [(humansize.approximate_size(os.stat(f).st_size), f) for f in glob.glob('*.xml')]  
[('3.0 KiB', 'feed-broken.xml'),
 ('3.3 KiB', 'feed-ns0.xml'),
 ('3.0 KiB', 'feed.xml')]
  1. Ez a listafeldolgozó megtalálja az összes .xml fájlt az aktuális munkakönyvtárban, lekéri az egyes fájlok méretét (az os.stat() függvény hívásával), és összeállít egy tuple-t a fájlok méretéből és az abszolút elérési útjukból (az os.path.realpath() függvény hívásával).
  2. Ez a feldolgozó az előzőre épít, és meghívja az approximate_size() függvényt az egyes .xml fájlok fájlméretével.

Szótárfeldolgozók

A szótárfeldolgozó olyan, mint a listafeldolgozó, csak lista helyett szótárat állít elő.

>>> import os, glob
>>> metadata = [(f, os.stat(f)) for f in glob.glob('*test*.py')]    
>>> metadata[0]                                                     
('alphameticstest.py', nt.stat_result(st_mode=33206, st_ino=0, st_dev=0,
 st_nlink=0, st_uid=0, st_gid=0, st_size=2509, st_atime=1247520344,
 st_mtime=1247520344, st_ctime=1247520344))
>>> metadata_dict = {f:os.stat(f) for f in glob.glob('*test*.py')}  
>>> type(metadata_dict)                                             
<class 'dict'>
>>> list(metadata_dict.keys())                                      
['romantest8.py', 'pluraltest1.py', 'pluraltest2.py', 'pluraltest5.py',
 'pluraltest6.py', 'romantest7.py', 'romantest10.py', 'romantest4.py',
 'romantest9.py', 'pluraltest3.py', 'romantest1.py', 'romantest2.py',
 'romantest3.py', 'romantest5.py', 'romantest6.py', 'alphameticstest.py',
 'pluraltest4.py']
>>> metadata_dict['alphameticstest.py'].st_size                     
2509
  1. Ez még nem szótárfeldolgozó, hanem egy listafeldolgozó. Megtalálja az összes .py fájlt, amelyek neve tartalmazza a test szót, majd összeállít egy tuple-t a fájlok neveiből és metaadataiból (amelyeket az os.stat() függvény ad vissza).
  2. Az eredményül kapott lista minden eleme egy tuple.
  3. Ez egy szótárfeldolgozó. A szintaxis hasonló a listafeldolgozókéhoz, két különbséggel. Először is, kapcsos és nem szögletes zárójelek közt áll. Másodszor, minden elemhez egy kifejezés helyett két, kettősponttal elválsztott kifejezést tartalmaz. A kettőspont előtti kifejezés (ebben a példában az f) a szótár kulcsa, a kettőspont utáni kifejezés (ebben a példában az os.stat(f)) az érték.
  4. A szótárfeldolgozó szótárat ad vissza.
  5. Ennek a szótárnak a kulcsai a glob.glob('*test*.py') által visszaadott fájlnevek.
  6. Az egyes kulcsokhoz tartozó érték az os.stat() függvény visszatérési értéke. Ez azt jelenti, hogy egy fájl a neve alapján „kikereshető” a szótárból a metaadatainak eléréséhez. Az egyik ilyen metaadat az st_size, a fájlméret. Az alphameticstest.py fájl 2509 bájt hosszú.

A listafeldolgozóhoz hasonlóan a szótárfeldolgozóhoz hozzáadhatsz egy if kifejezést a bemeneti sorozat szűréséhez egy minden elemmel együtt kiértékelésre kerülő kifejezéssel.

>>> import os, glob, humansize
>>> metadata_dict = {f:os.stat(f) for f in glob.glob('*')}                                  
>>> humansize_dict = {os.path.splitext(f)[0]:humansize.approximate_size(meta.st_size) \     
...                   for f, meta in metadata_dict.items() if meta.st_size > 6000}          
>>> list(humansize_dict.keys())                                                             
['romantest9', 'romantest8', 'romantest7', 'romantest6', 'romantest10', 'pluraltest6']
>>> humansize_dict['romantest9']                                                            
'6.5 KiB'
  1. Ez a szótárfeldolgozó létrehozza az aktuális munkakönyvtár fájljainak listáját, (glob.glob('*')), lekéri az egyes fájlok metaadatait (os.stat(f)), és összeállít egy szótárat, amelynek kulcsai a fájlnevek, értékei pedig az egyes fájlok metaadatai.
  2. Ez a szótárfeldolgozó az előző feldolgozóra épül, kiválogatja a 6000 bájtnál nagyobb fájlokat (if meta.st_size > 6000), és a kapott listát használja egy szótár összeállítására, amelynek kulcsai a kiterjesztés nélküli fájlnevek (os.path.splitext(f)[0]), értékei pedig az egyes fájlok becsült méretei (humansize.approximate_size(meta.st_size)).
  3. Ahogyan az előző példában láthattad, hat ilyen fájl van, emiatt ebben a szótárban is hat elem van.
  4. Az egyes kulcsok értéke az approximate_size() függvény által visszaadott karakterlánc.

További menő dolgok szótárfeldolgozókkal

Itt egy szótárfeldolgozókkal kapcsolatos trükk, ami egyszer még jól jöhet: egy szótár kulcsainak és értékeinek felcserélése.

>>> a_dict = {'a': 1, 'b': 2, 'c': 3}
>>> {érték:kulcs for kulcs, érték in a_dict.items()}
{1: 'a', 2: 'b', 3: 'c'}

Természetesen ez csak akkor működik, ha a szótár értékei megváltoztathatatlanok, például karakterláncok vagy tuple-k. Ha ezt egy listákat tartalmazó szótárral próbálod meg, az látványosan nem fog sikerülni.

>>> a_dict = {'a': [1, 2, 3], 'b': 4, 'c': 5}
>>> {érték:kulcs for kulcs, érték in a_dict.items()}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <dictcomp>
TypeError: unhashable type: 'list'

Halmazfeldolgozók

Ne hagyjuk ki a halmazokat se, ezeknek is van saját feldolgozószintaxisuk, amely nagyon hasonlít a szótárfeldolgozók szintaxisához. Az egyetlen különbség, hogy a halmazok csak értékeket tartalmaznak kulcs:érték párok helyett.

>>> a_set = set(range(10))
>>> a_set
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
>>> {x ** 2 for x in a_set}           
{0, 1, 4, 81, 64, 9, 16, 49, 25, 36}
>>> {x for x in a_set if x % 2 == 0}  
{0, 8, 2, 4, 6}
>>> {2**x for x in range(10)}         
{32, 1, 2, 4, 8, 64, 128, 256, 16, 512}
  1. A halmazfeldolgozók bemenete lehet egy halmaz. Ez a halmazfeldolgozó kiszámítja a 0 és 9 közti számok négyzetét.
  2. A listafeldolgozókhoz és szótárfeldolgozókhoz hasonlóan a halmazfeldolgozók is tartalmazhatnak egy if kifejezést az egyes elemek szűréséhez azok visszaadása előtt az eredmény halmazba.
  3. A halmazfeldolgozók bemenete nem csak halmaz lehet, tetszőleges sorozatot is elfogadnak.

További olvasnivaló

© 2001–11 Mark Pilgrim