Itt vagy: Kezdőlap ‣ Ugorj fejest a Python 3-ba ‣
Nehézségi szint: ♦♦♦♢♢
❝ Az én írásom imbolygó. Jó írás, de imbolyog, és a betűk rossz helyre kerülnek. ❞
– Micimackó
Egy könyvtáros és egy angoltanár fiaként felnőve mindig is lenyűgöztek a nyelvek. Nem a programozási nyelvek. Na jó, a programozási nyelvek is, de a természetes nyelvek. Vegyük az angolt. Az angol egy skizofrén nyelv, amely szavakat kölcsönöz a németből, franciából, spanyolból és latinból (hogy csak párat említsek). Valójában a „kölcsönöz” nem is a jó szó, a „rablás” sokkal jellemzőbb. Vagy talán az „asszimilál” – mint a Borg. Igen, ez lesz az.
Itt a Borg. Nyelvi és etimológiai sajátosságaikat a miénkhez adjuk. Az ellenállás hasztalan.
Ebben a fejezetben a többesszámú angol főnevekről fogsz tanulni. Továbbá függvényeket visszaadó függvényekről, további reguláris kifejezésekről és generátorokról. De előbb hadd beszéljek az többesszámú angol főnevek felépítéséről. (Ha még nem olvastad a reguláris kifejezésekről szóló fejezetet, akkor itt az ideje. Ez a fejezet feltételezi, hogy megérted a reguláris kifejezések alapjait, és gyorsan belemerül a speciálisabb felhasználási módokba.)
Ha angolul beszélő országban nőttél fel, vagy az iskolában tanultál angolt, akkor valószínűleg tisztában vagy az alapvető szabályokkal:
(Tudom, rengeteg kivétel van. A man-ből men lesz és a woman-ből women, de a human-ből humans. A mouse-ból mice és a louse-ból lice, de a house-ból houses. A knife-ból knives lesz, és a wife-ból wives, de a lowlife-ból lowlifes. Az olyan szavakba pedig ne is menjünk bele, amelyek megegyeznek a többesszámaikkal, mint a sheep, deer és haiku.)
Más nyelvek természetesen teljesen mások.
Tervezzünk egy Python függvénytárat, amely automatikusan többesszámba teszi az angol főneveket. Csak ezzel a négy szabállyal indulunk, de ne felejtsd el, hogy elkerülhetetlenül szükség lesz továbbiak hozzáadására is.
⁂
Tehát szavakkal állunk szemben, ez legalábbis angolul azt jelenti, hogy karakterek sorozatai vannak előttünk. Vannak szabályaid, amelyek szerint különböző karakterkombinációkat kell keresned, majd különböző dolgokat kell velük csinálnod. Ez a feladat úgy hangzik, mintha a reguláris kifejezésekre szabták volna!
import re
def többes(főnév):
if re.search('[sxz]$', főnév): ①
return re.sub('$', 'es', főnév) ②
elif re.search('[^aeioudgkprt]h$', főnév):
return re.sub('$', 'es', főnév)
elif re.search('[^aeiou]y$', főnév):
return re.sub('y$', 'ies', főnév)
else:
return főnév + 's'
[sxz]
jelentése „s
vagy x
vagy z
”, de csak az egyik. A $
már ismerős, a karakterlánc végére illeszkedik. Egyesítve ez a reguláris kifejezés azt teszteli, hogy a főnév s
, x
vagy z
betűre végződik-e.
re.sub()
függvény reguláris kifejezésekre épülő karakterlánc-helyettesítéseket végez.
Nézzük meg részletesebben a reguláris kifejezések helyettesítéseit.
>>> import re >>> re.search('[abc]', 'Mark') ① <_sre.SRE_Match object at 0x001C1FA8> >>> re.sub('[abc]', 'o', 'Mark') ② 'Mork' >>> re.sub('[abc]', 'o', 'rock') ③ 'rook' >>> re.sub('[abc]', 'o', 'caps') ④ 'oops'
Mark
karakterlánc tartalmaz a
, b
vagy c
betűt? Igen, tartalmaz egy a
-t.
a
, b
vagy c
valamelyikét, és cseréljük le egy o
-ra. A Mark
-ból Mork
lesz.
rock
-ból rook
-ot csinál.
caps
-ból oaps
-ot csinál, de nem. A re.sub
minden találatot helyettesít, nem csak a legelsőt. Így ez a reguláris kifejezés a caps
-ból oops
-ot csinál, mert a c
és az a
is o
-vá változik.
Most térjünk vissza a többes()
függvényhez…
def többes(főnév):
if re.search('[sxz]$', főnév):
return re.sub('$', 'es', főnév) ①
elif re.search('[^aeioudgkprt]h$', főnév): ②
return re.sub('$', 'es', főnév)
elif re.search('[^aeiou]y$', főnév): ③
return re.sub('y$', 'ies', főnév)
else:
return főnév + 's'
$
jelre illeszkedik) cseréled az es
karakterláncra. Más szóval hozzáteszed az es
-t a karakterlánchoz. Ugyanezt elérhetnéd karakterláncok összefűzésével is, például így: főnév + 'es'
, de minden szabályhoz a reguláris kifejezések használatát választottam, a fejezet későbbi részeiben világossá váló okok miatt.
^
a szögletes zárójelen belüli első karakterként valami különlegeset jelent: tagadást. A [^abc]
azt jelenti: „bármely karakter, kivéve az a
, b
vagy c
”. Így a [^aeioudgkprt]
jelentése: bármely karakter, kivéve az a
, e
, i
, o
, u
, d
, g
, k
, p
, r
vagy t
. Ezután a karaktert egy h
, majd a karakterlánc vége kell kövesse. Olyan szavakat keresel, amelyek hallható H-ra végződnek.
a
, e
, i
, o
vagy u
. Olyan szavakat keresel, amelyek I-ként hangzó Y-ra végződnek.
Nézzük meg részletesebben a tagadó reguláris kifejezéseket.
>>> import re >>> re.search('[^aeiou]y$', 'vacancy') ① <_sre.SRE_Match object at 0x001C1FA8> >>> re.search('[^aeiou]y$', 'boy') ② >>> >>> re.search('[^aeiou]y$', 'day') >>> >>> re.search('[^aeiou]y$', 'pita') ③ >>>
vacancy
illeszkedik erre a reguláris kifejezésre, mert cy
-ra végződik, és a c
nem a
, e
, i
, o
vagy u
.
boy
nem illeszkedik, mert oy
-ra végződik, és kifejezetten megadtad, hogy az y
előtti karakter nem lehet o
. A day
sem illeszkedik, mert ay
-ra végződik.
pita
nem illeszkedik, mert nem y
-ra végződik.
>>> re.sub('y$', 'ies', 'vacancy') ① 'vacancies' >>> re.sub('y$', 'ies', 'agency') 'agencies' >>> re.sub('([^aeiou])y$', r'\1ies', 'vacancy') ② 'vacancies'
vacancy
-ből vacancies
-t csinál, az agency
-ből pedig agencies
-t, és pontosan ezt akartad. Ne feledd el, hogy a boy
-ból is boies
-t csinálna, de a függvényben ez soha nem fog megtörténni, mert előbb a re.search
hívásával eldöntötted, hogy végre kell-e hajtani ezt a re.sub
hívást.
y
előtti karakter kerül megjegyzésre. Ezután a helyettesítési karakterláncban új szintaxist használsz: a \1
jelentése: „hé, az elsőként megjegyzett csoport? Ide tedd.” Ebben az esetben a c
-t jegyzed meg az y
előtt; a helyettesítés végrehajtásakor a c
a c
helyére, az ies
az y
helyére kerül. (Ha több megjegyzett csoportod is van, akkor használhatod a \2
és \3
stb. jelöléseket.)
A reguláris kifejezések helyettesítései nagyon hatékonyak, és a \1
szintaxis csak még hatékonyabbá teszi őket. A teljes művelet egy reguláris kifejezéssé kombinálása azonban sokkal nehezebben olvasható, és nem felel meg a többesszám-szabályokban először leírt módszernek. Eredetileg ilyen szabályokat fektettél le: „ha a szó S, X vagy Z betűre végződik, tegyél utána egy ES-t”. Ha megnézed a függvényt, akkor két kódsorod van, amelyek azt mondják: „ha a szó S, X vagy Z betűre végződik, tegyél utána egy ES-t”. Ennél sokkal közvetlenebb már nem lehet.
⁂
Most hozzáadunk egy absztrakciós szintet. Egy szabálylista definiálásával kezdted: ha ez van, csináld azt, egyébként lépj a következő szabályra. Ideiglenesen bonyolítsuk meg a program egy részét, hogy egy másik rész egyszerűsíthető legyen.
import re
def match_sxz(főnév):
return re.search('[sxz]$', főnév)
def apply_sxz(főnév):
return re.sub('$', 'es', főnév)
def match_h(főnév):
return re.search('[^aeioudgkprt]h$', főnév)
def apply_h(főnév):
return re.sub('$', 'es', főnév)
def match_y(főnév): ①
return re.search('[^aeiou]y$', főnév)
def apply_y(főnév): ②
return re.sub('y$', 'ies', főnév)
def match_default(főnév):
return True
def apply_default(főnév):
return főnév + 's'
szabályok = ((match_sxz, apply_sxz), ③
(match_h, apply_h),
(match_y, apply_y),
(match_default, apply_default)
)
def többes(főnév):
for matches_rule, apply_rule in szabályok: ④
if matches_rule(főnév):
return apply_rule(főnév)
re.search()
függvény hívásának eredményét adja vissza.
re.sub()
függvényt a megfelelő többes számba tevő szabály alkalmazásához.
többes()
) helyett a szabályok
nevű adatszerkezetet használjuk, amely függvénypárok sorozatát tartalmazza.
többes()
függvény néhány sornyi kódra csökkenthető. Egy for
ciklus használatával kettesével veheted ki az illesztési és alkalmazási szabályokat (egy illesztés, egy alkalmazás) a szabályok adatszerkezetből. A for
ciklus első ismétlésében a matches_rule értéke match_sxz
, az apply_rule értéke pedig apply_sxz
lesz. A második ismétlésben (feltételezve, hogy eljut odáig), a matches_rule értéke match_h
, az apply_rule értéke apply_h
lesz. A függvény garantáltan visszaad valamit, mert az utolsó illesztési szabály (match_default
) egyszerűen a True
értéket adja vissza, ami azt jelenti, hogy a megegyező alkalmazási szabály (apply_default
) mindig felhasználásra kerül.
Ez a módszer azért működik, mert Pythonban minden objektum, beleértve a függvényeket is. A szabályok adatszerkezet függvényeket tartalmaz – nem függvényneveket, hanem tényleges függvényobjektumokat. Amikor a for
ciklusban értékként átadásra kerülnek, akkor a matches_rule és az apply_rule tényleges, meghívható függvényekké válnak. A for
ciklus első ismétlésében ez egyenértékű a matches_sxz(főnév)
hívásával, és ha ez találatot ad vissza, akkor az apply_sxz(noun)
hívásával.
Ha ez a további absztrakciós szint bonyolult, akkor próbáld meg lebontani a függvényt az azonosság megértéséhez. A teljes for
ciklus azonos a következővel:
def többes(főnév):
if match_sxz(főnév):
return apply_sxz(főnév)
if match_h(főnév):
return apply_h(főnév)
if match_y(főnév):
return apply_y(főnév)
if match_default(főnév):
return apply_default(főnév)
Ennek az az előnye, hogy a többes()
függvény egyszerűbbé vált. Máshol megadott szabályok sorozatát várja, és általános módon halad rajtuk végig.
A szabályok bárhol megadhatók, tetszőleges módon. A többes()
függvényt nem érdekli.
Na most, megérte bevezetni ezt az absztrakciós szintet? Hát, még nem. Fontoljuk meg, hogy mibe kerülne egy új szabályt hozzáadni a függvényhez. Az első példában egy if
utasítást kellene hozzáadni a többes()
függvényhez. Ebben a második példában két függvény, a match_foo()
és az apply_foo()
hozzáadása lenne szükséges, majd a szabályok sorozat frissítése annak megadásához, hogy az új illesztés és alkalmazás függvények milyen sorrendben hívandók meg a többi szabályhoz képest.
De ez csak egy lépcső a következő szakaszhoz. Haladjunk tovább…
⁂
Igazából nem muszáj külön megnevezett függvényeket megadni minden illesztési és alkalmazási szabályhoz. Ezeket soha nem hívod meg közvetlenül, ugyanis ezeket hozzáadod a rules sorozathoz, és azon keresztül hívod. Továbbá minden függvény egy vagy két mintát követ. Minden illesztési függvény a re.search()
metódust hívja, és minden alkalmazási függvény a re.sub()
metódust. Szervezzük ki a mintákat, hogy az új szabályok megadása egyszerűbb lehessen.
import re
def build_match_and_apply_functions(pattern, search, replace):
def matches_rule(word): ①
return re.search(pattern, word)
def apply_rule(word): ②
return re.sub(search, replace, word)
return (matches_rule, apply_rule) ③
build_match_and_apply_functions()
egy más függvényeket dinamikusan felépítő függvény. A pattern, search és replace változókat várja, majd definiál egy matches_rule()
függvényt, amely meghívja a re.search()
metódust a build_match_and_apply_functions()
függvénynek átadott pattern változóval, és a felépítendő matches_rule()
függvénynek átadott word változóval. Bámulatos, hol tart már a tudomány.
re.sub()
metódust a build_match_and_apply_functions()
függvénynek átadott search és replace változókkal, és a felépítendő apply_rule()
függvénynek átadott word változóval. Ezt a módszert, a külső paraméterek értékeinek dinamikus függvényeken belüli felhasználását closure-öknek nevezzük. Lényegében konstansokat definiálsz a felépítendő alkalmaz függvényen belül: egy paramétert vár (word), de aztán ezen és még két másik értéken (search és replace) dolgozik, amelyek az alkalmaz függvény definiálásakor lettek megadva.
build_match_and_apply_functions()
függvény egy két értékből álló tuple-t ad vissza: az éppen létrehozott két függvényt. A függvényeken belül definiált konstansok (pattern a matches_rule()
függvényen belül, illetve a search és replace az apply_rule()
függvényen belül) megmaradnak a függvényekkel, még akkor is, amikor visszatér a build_match_and_apply_functions()
függvény. Ez őrülten menő!
Ha ez hihetetlenül zavarosnak is tűnik (ahogy annak lennie is kell, ezek fura dolgok), világosabbá válhat, ha megmutatom hogyan használd.
patterns = \ ①
(
('[sxz]$', '$', 'es'),
('[^aeioudgkprt]h$', '$', 'es'),
('(qu|[^aeiou])y$', 'y$', 'ies'),
('$', '$', 's') ②
)
rules = [build_match_and_apply_functions(pattern, search, replace) ③
for (pattern, search, replace) in patterns]
re.search()
metódusban használsz a szabály illeszkedésének meghatározásához. A csoportok második és harmadik karakterláncai a keresés és csere kifejezések, amelyeket a re.sub()
metódusban használsz a szabály tényleges alkalmazására a főnév többes számúvá tételéhez.
match_default()
függvény egyszerűen a True
értéket adta vissza, amely azt jelentette, hogy a megadott szabályok egyike sem illeszkedett, a kód pedig egy s
karaktert illesztett az adott szó végéhez. Ez a példa valami funkcionálisan egyenértékűt tesz. Az utolsó reguláris kifejezés azt ellenőrzi, hogy a szónak vége van-e(a $
a karakterlánc végére illeszkedik). Természetesen minden karakterláncnak van vége, még az üres karakterláncnak is, így ez a kifejezés mindig illeszkedik. Így ugyanazt a célt szolgálja, mint a match_default()
függvény, amely mindig a True
értéket adta vissza: biztosítja, hogy ha több szabály nem illeszkedik, akkor a kód egy s
karaktert fűzzön az adott szó végéhez.
build_match_and_apply_functions()
függvényre. Vagyis veszi a karakterlánc-hármasokat, és meghívja a build_match_and_apply_functions()
függvényt, argumentumként használva ezt a három karakterláncot. A build_match_and_apply_functions()
függvény egy két függvényből álló tuple-t ad vissza. Ez azt jelenti, hogy a rules lista funkcionálisan egyenértékű lesz az előző példával: tuple-ök listája, amelyben minden tuple egy függvénypár. Az első függvény az illesztési függvény, amely meghívja a re.search()
metódust, a második függvény pedig az alkalmazási függvény, amely a re.sub()
metódust hívja meg.
A parancsfájl ezen változatát a fő belépési ponttal, a plural()
függvénnyel zárjuk.
def plural(noun):
for matches_rule, apply_rule in rules: ①
if matches_rule(noun):
return apply_rule(noun)
plural()
függvény egyáltalán nem változott. Teljesen általános, a szabályfüggvények listáját várja, és sorban meghívja azokat. A szabályok definiálásának módjával nem foglalkozik. Az előző példában ezek önálló nevesített függvényekként voltak definiálva. Most dinamikusan állnak össze, a build_match_and_apply_functions()
függvény kimenetének leképezésével nyers karakterláncok listájára. Nem számít, a plural()
függvény ugyanúgy működik.
⁂
A többször szereplő kódot felszámoltad, és elég absztrakciót adtál hozzá, így a többes számot előállító szabályok karakterláncok listájában vannak meghatározva. A következő logikus lépés ezen karakterláncok önálló fájlba helyezése, ahol az ezeket használó kódtól függetlenül tarthatók karban.
Elsőként hozzunk létre egy szövegfájlt, amely tartalmazza a kívánt szabályokat. Nem kellenek csicsás adatszerkezetek, csak üres helyekkel elválasztott karakterláncok három oszlopban. Nevezzük plural4-rules.txt
-nek.
[a plural4-rules.txt
letöltése]
[sxz]$ $ es
[^aeioudgkprt]h$ $ es
[^aeiou]y$ y$ ies
$ $ s
Nézzük, hogyan használhatjuk ezt a szabályfájlt.
import re
def build_match_and_apply_functions(pattern, search, replace): ①
def matches_rule(word):
return re.search(pattern, word)
def apply_rule(word):
return re.sub(search, replace, word)
return (matches_rule, apply_rule)
rules = []
with open('plural4-rules.txt', encoding='utf-8') as pattern_file: ②
for line in pattern_file: ③
pattern, search, replace = line.split(None, 3) ④
rules.append(build_match_and_apply_functions( ⑤
pattern, search, replace))
build_match_and_apply_functions()
függvény nem változott. Még mindig closure-öket használsz két függvény dinamikus felépítésére, amelyek a külső függvényben definiált változókat használnak.
open()
függvény megnyit egy fájlt, és visszaad egy fájl objektumot. Ebben az esetben a megnyitott fájl minta karakterláncokat tartalmaz, a főnevek többes számba tételéhez. A with
utasítás egy kontextusnak nevezett dolgot hoz létre: amikor a with
blokk befejeződik, a Python automatikusan lezárja a fájlt, még ha kivétel is történt a with
blokkon belül. A with
blokkokról és a fájlobjektumokról a Fájlok fejezetben fogsz többet megtudni.
for line in <fileobject>
szerkezet soronként beolvassa az adatokat a megnyitott fájlból, és a szöveget a line változóhoz rendeli. A fájlokból olvasásról a Fájlok fejezetben fogsz többet megtudni.
split()
karakterláncmetódust. A split()
metódus első argumentuma a None
, amely azt jelenti, hogy „vágd szét az üres helynél (tab vagy szóköz, nem számít).” A második argumentum a 3
, ami azt jelenti, hogy „vágd szét az üres helynél háromszor, és hagyd békén a sor többi részét.” Egy ilyen sor: [sxz]$ $ es
a következő listává lesz szétvágva: ['[sxz]$', '$', 'es']
, amely azt jelenti, hogy a pattern értéke '[sxz]$'
, a search értéke '$'
, a replace értéke pedig 'es'
lesz. Rengeteg erő van ebben a pici kódsorban!
pattern
, search
és replace
változókat a build_match_and_apply_functions()
függvénynek, amely függvények tuple-jét adja vissza. Ezt a tuple-t a rules listához fűzöd, a rules pedig az illesztés és alkalmazás függvények listáját fogja tárolni, ahogy azokat a plural()
függvény várja.
A fejlődés itt abban áll, hogy teljesen elkülönítetted a többes számot előállító szabályokat egy külső fájlba, így az azt használó kódtól külön lehet karbantartani. A kód kód, az adat adat, az élet szép.
⁂
Nem lenne nagyszerű egy olyan általános plural()
függvény, amely a szabályok fájlját dolgozza fel? Fogd a szabályokat, keress illeszkedést, alkalmazd a megfelelő átalakítást, menj a következő szabályra. Ez minden, amit a plural()
függvénynek muszáj megoldania, és ez minden, amit a plural()
függvénynek el kell végeznie.
def rules(rules_filename):
with open(rules_filename, encoding='utf-8') as pattern_file:
for line in pattern_file:
pattern, search, replace = line.split(None, 3)
yield build_match_and_apply_functions(pattern, search, replace)
def plural(noun, rules_filename='plural5-rules.txt'):
for matches_rule, apply_rule in rules(rules_filename):
if matches_rule(noun):
return apply_rule(noun)
raise ValueError('no matching rule for {0}'.format(noun))
Hogy a csudába működik ez? Nézzünk meg először egy interaktív példát.
>>> def make_counter(x): ... print('entering make_counter') ... while True: ... yield x ① ... print('incrementing x') ... x = x + 1 ... >>> counter = make_counter(2) ② >>> counter ③ <generator object at 0x001C9C10> >>> next(counter) ④ entering make_counter 2 >>> next(counter) ⑤ incrementing x 3 >>> next(counter) ⑥ incrementing x 4
yield
kulcsszó jelenléte a make_counter
függvényben azt jelenti, hogy ez nem egy átlagos függvény. Ez egy különleges függvény, amely időről időre értékeket állít elő. Úgy képzelheted el, mint egy folytatható függvényt. A meghívása egy generátort ad vissza, amely az x egymást követő értékeinek előállítására használható.
make_counter
generátor egy példányának előállításához csak hívd meg, mint bármely függvényt. Ne feledd, hogy ez nem futtatja le ténylegesen a függvény kódját. Ezt abból láthatod, hogy a make_counter()
függvény első sora meghívja a print()
függvényt, de még semmi sem került kiírásra.
make_counter()
függvény egy generátor objektumot ad vissza.
next()
függvény egy generátor objektumot vár, és visszaadja a következő értékét. A next()
a counter generátorral való első hívásakor végrehajtja a make_counter()
kódját az első yield
utasításig, majd visszaadja az eredményül kapott értéket. Ebben az esetben ez a 2
lesz, mert a generátort eredetileg a make_counter(2)
hívásával hoztad létre.
next()
ismételt hívása ugyanazzal a generátor objektummal pontosan ott folytatja a végrehajtást, ahol abbahagyta, és addig folytatja, amíg nem találja meg a következő yield
utasítást. Minden változó, helyi állapot stb. mentésre kerül a yield
hívásakor, és visszaállításra kerül a next()
hívásakor. A végrehajtásra váró következő kódsor a print()
függvényt hívja, amely kiírja az incrementing x kifejezést. Ezután végrehajtja az x = x + 1
utasítást. Majd végighalad újra a while
cikluson, és az első dolog, amivel találkozik, az a yield x
utasítás, amely elmenti az összes állapotot, és visszaadja az x aktuális értékét (most 3
).
next(counter)
újabb hívásakor minden ugyanúgy történik, de most az x értéke 4
.
Mivel a make_counter
egy végtelen ciklust tartalmaz, elméletileg ezt a végtelenségig csinálhatnád, folyamatosan növelné az x értékét és írná ki azt. De nézzük meg inkább a generátorok értelmesebb felhasználási módjait.
def fib(max):
a, b = 0, 1 ①
while a < max:
yield a ②
a, b = b, a + b ③
0
-val és 1
-gyel kezdődik, először lassan, majd egyre gyorsabban növekszik. A sorozat elindításához két változóra van szükséged: az a 0
-nál kezdődik, a b pedig 1
-nél.
a + b
) és hozzárendeljük a b-hez későbbi felhasználásra. Ne feledd, hogy ez párhuzamosan történik: ha a = 3
és b = 5
, akkor az a, b = b, a + b
beállítja az a-t 5
-re (a b előző értékére) és a b-t 8
-ra (az a és b előző értékeinek összegére).
Így van egy függvényed, amely egymást követő Fibonacci számokat ír ki. Persze, lehetne rekurziót is használni, de így olvashatóbb. Továbbá remekül működik for
ciklusokkal.
>>> from fibonacci import fib >>> for n in fib(1000): ① ... print(n, end=' ') ② 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 >>> list(fib(1000)) ③ [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]
fib()
-hez hasonló generátort közvetlenül használhatod egy for
ciklusban. A for
ciklus automatikusan meghívja a next()
függvényt az értékek kinyeréséhez a fib()
generátorból, és hozzárendeli azokat a for
ciklus indexváltozójához (n).
for
ciklus minden végrehajtásakor az n új értéket kap a yield
utasítástól a fib()
generátorban, csak ki kell íratnod. Miután a fib()
kifogy a számokból (az a nagyobbá válik, mint a max, amely ebben az esetben 1000
), akkor a for
ciklus kilép.
list()
függvénynek, és az végigjárja az egész generátort (mint a for
ciklus az előző példában), és visszaadja az összes érték listáját.
Térjünk vissza a plural5.py
programhoz, és nézzük meg, hogyan működik a plural()
függvény ezen változata.
def rules(rules_filename):
with open(rules_filename, encoding='utf-8') as pattern_file:
for line in pattern_file:
pattern, search, replace = line.split(None, 3) ①
yield build_match_and_apply_functions(pattern, search, replace) ②
def plural(noun, rules_filename='plural5-rules.txt'):
for matches_rule, apply_rule in rules(rules_filename): ③
if matches_rule(noun):
return apply_rule(noun)
raise ValueError('no matching rule for {0}'.format(noun))
line.split(None, 3)
használatával kaphatod meg a három „oszlopot”, majd három helyi változóhoz rendeled azokat.
build_match_and_apply_functions()
épít fel dinamikusan. Más szavakkal a rules()
egy generátor, amely illesztési és alkalmazási függvényeket ad vissza igény szerint.
rules()
egy generátor, közvetlenül használhatod egy for
ciklusban. A for
ciklus első futásakor meghívod a rules()
függvényt, amely megnyitja a mintafájlt, beolvassa az első sort, dinamikusan felépít egy illesztési és egy alkalmazási függvényt a sorban lévő mintákból, és a yield használatával visszaadja a dinamikusan felépített függvényeket. A for
ciklus második futásakor pontosan ott folytatod, ahol a rules()
függvényt abbahagytad (ami a for line in pattern_file
ciklus közepén volt). Ez elsőként a (még mindig nyitva lévő) fájl következő sorának beolvasását fogja elvégezni, majd dinamikusan felépít egy újabb illesztési és alkalmazási függvényt a fájl adott sorának mintái alapján, és a yield használatával visszaadja a két függvényt.
Mit nyertél a 4. lépéshez képest? Indítási időt. Amikor a 4. lépésben importáltad a plural4
modult, az beolvasta a teljes mintafájlt, és felépítette az összes lehetséges szabály listáját, mielőtt akár csak gondolhattál volna a plural()
függvény hívására. A generátorokkal mindent lustán csinálhatsz: beolvashatod az első szabályt, létrehozhatod a függvényeket és kipróbálhatod azokat, és ha ez sikeres, akkor nem is olvasod be a fájl többi részét, és újabb függvényeket sem hozol létre.
Mit vesztettél? Teljesítményt! A plural()
függvény minden hívásakor a rules()
generátor újraindul az elejétől – azaz újra megnyitja a mintafájlt, és soronként beolvassa az elejétől.
Mi lenne, ha egyszerre tudnád kihasználni a két módszer előnyeit: minimális indítási költség (ne hajtson végre kódot az import
hívásakor), és maximális teljesítmény (ne építse fel ugyanazokat a függvényeket újra és újra). Ja, és továbbra is önálló fájlban szeretnéd tartani a szabályokat (mert a kód az kód, az adat az adat), hogy soha ne kelljen kétszer beolvasnod ugyanazt a sort.
Hogy ezt megtehesd, saját iterátort kell felépítened. De hogy ezt megtehesd, meg kell ismerkedned a Python osztályokkal.
⁂
© 2001–11 Mark Pilgrim