4. Iterátory, generátory, binárne súbory¶
Iterátory¶
Už vieme, že v Pythone sú niektoré základné typy iterovateľné - môžeme prechádzať ich prvky, napríklad pomocou for cyklu, Stretli sme sa s týmito iterovateľnými typmi:
list
,tuple
,str
,dict
,set
postupnosť celých čísel
range(...)
, otvorený súbor na čítanieopen(...)
výsledky funkcií
map()
afilter()
aj generátorová notácia[... for ...]
Aj pre vlastný definovaný typ môžeme zabezpečiť iterovateľnosť. Možností je niekoľko, ukážeme dve z nich:
v triede zadefinujeme magickú metódu
__getitem__()
: v prípade prechádzania pomocou for-cyklu, Python zabezpečí postupné generovanie indexov od 0 vyššie a pri prvom neexistujúcom prvku, skončív triede zadefinujeme dvojicu magických metód
__iter__()
a__next__()
, ktoré zabezpečia iterovateľnosť
Pozrime sa najprv na 2. spôsob vytvorenia iterovateľnosti a to pomocou štandardných funkcií iter()
a next()
(pomocou metód __iter__()
a __next__()
vieme zabezpečiť funkčnosť aj pre našu novú triedu). Aby sme lepšie pochopili ich princíp fungovania, vysvetlime, ako Python „vidí“ obyčajný for-cyklus. Python ho vnútorne realizuje pomocou while-cyklu a iterátora. Napríklad takýto for-cyklus:
pole = [2, 3, 5, 7, 11, 13, 17]
for i in pole:
print(i, i*i)
v skutočnosti Python realizuje pomocou iterátora približne takto:
iterator = iter(pole)
while True:
try:
i = next(iterator)
print(i, i*i)
except StopIteration:
break
Funguje to takto:
Python si najprv z daného typu vyrobí špeciálny objekt (tzv. iterátor), pomocou ktorého bude neskôr postupne prechádzať všetky prvky
iterátor sa vytvára štandardnou funkciou
iter()
, ktorá by pre neiterovateľný typ spadla s chybovou hláškouďalšia štandardná funkcia
next()
z iterátora vráti nasledovnú hodnotu, alebo vyhlási chybuStopIteration
, ak už ďalšia neexistuje
Môžete to vidieť aj na tomto príklade s iterovaním znakového reťazca:
>>> it = iter('ahoj')
>>> next(it)
'a'
>>> next(it)
'h'
>>> next(it)
'o'
>>> next(it)
'j'
>>> next(it)
...
StopIteration
Ak v nejakej našej triede zadefinujeme metódy __iter__()
a __next__()
, tieto metódy sa automaticky zavolajú zo štandardných funkcií iter()
a next()
. Metóda __iter__()
najčastejšie obsahuje vrátenie seba ako svojej hodnoty return self
, lebo predpokladáme, že samotná inštancia je potom iterátor a teda tento iterátor musí obsahovať aj definíciu metódy __next__()
. Táto druhá metóda sa automaticky zavolá pri volaní štandardnej funkcie next()
. Preto musí metóda __next__()
skontrolovať, či má ešte nasledovnú hodnotu (vtedy ju vráti) alebo vyvolá výnimku StopIteration
.
Vyskúšajte:
class Moj:
def __init__(self):
self.p = []
def append(self, x):
self.p.append(x)
def __iter__(self):
self.ix = 0
return self
def __next__(self):
if self.ix >= len(self.p):
raise StopIteration
self.ix += 1
return self.p[self.ix-1]
Otestujeme:
>>> a = Moj()
>>> for i in 2, 3, 5, 7, 11:
a.append(i)
>>> for i in a:
print(i, end=', ')
2, 3, 5, 7, 11,
>>> print(*a)
2 3 5 7 11
>>> it = iter(a)
>>> while 1:
try:
next(it)
except StopIteration:
break
2
3
5
7
11
Poznámka: táto verzia iterátora je veľmi zjednodušená a niekedy nepracuje úplne korektne. Napríklad pre obyčajné polia funguje:
a = [2, 3, 5, 7, 11]
for i in a:
for j in a:
print(i, j)
sa vypíše 25 dvojíc čísel. Pre náš iterovateľný objekt:
a = Moj()
for i in 2, 3, 5, 7, 11:
a.append(i)
for i in a:
for j in a:
print(i, j)
sa vypíše len 5 dvojíc. Zamyslite sa nad tým, ako by sa to dalo opraviť.
Iterátor pomocou __getitem__()
V predchádzajúcej prednáške sme videli príklad, v ktorom sa vďaka metóde __getitem__()
stala nejaká trieda (spájaný zoznam) iterovateľnou. Ak nemáme v triede definované metódy __iter__()
a __next__()
, ale nachádza sa v nej __getitem__()
, Python z vlastnej iniciatívy „pochopí“, že takáto štruktúra by sa mohla dať prechádzať aj for-cyklom (iterovať). Veď zrejme mu stačí postupne indexovať s indexom 0, potom 1, potom 2, atď. až kým to nespadne na chybe a vtedy ukončí aj for-cyklus (bez chybovej správy).
Zadefinujme vlastnú triedu s metódou __getitem__()
:
class Moj:
def __init__(self):
self.p = []
def append(self, x):
self.p.append(x)
def __getitem__(self, i):
return self.p[i]
Otestujme:
>>> a = Moj()
>>> for i in 'Python':
a.append(i)
>>> a
<__main__.Moj object at 0x02AD0F70>
>>> for i in a:
print(i, end=' ')
P y t h o n
>>> len(a)
...
TypeError: object of type 'Moj' has no len()
>>> list(a)
['P', 'y', 't', 'h', 'o', 'n']
>>> len(list(a))
6
>>> it = iter(a)
>>> while 1:
try:
next(it)
except StopIteration:
break
'P'
'y'
't'
'h'
'o'
'n'
Zhrňme: iterátor je taký objekt, pomocou ktorého môžeme postupne prechádzať prvky nejakej „kolekcie“. Zrejme sa táto kolekcia bude skladať z nejakých prvkov. Hovoríme, že objekt je iterovateľný (iterable), keď sa dajú prechádzať jeho prvky buď pomocou iter()
alebo indexovaním __getitem__
.
Generátory¶
V Pythone existuje zaujímavý spôsob ako generovať postupnosti. Najčastejšie sme to doteraz robili pomocou zoznamu, napríklad generovanie všetkých deliteľov nejakého čísla:
def delitele(n):
res = []
for i in range(1, n + 1):
if n % i == 0:
res.append(i)
return res
>>> delitele(100)
[1, 2, 4, 5, 10, 20, 25, 50, 100]
Pre postupnosti je základnou vlastnosťou to, aby boli iterovateľné, t. j. aby sa jeho prvky dali postupne prechádzať pomocou for-cyklu. Napríklad:
>>> for i in delitele(100):
print(i, end=' ')
1 2 4 5 10 20 25 50 100
Teda nie je dôležité mať k dispozícii naraz všetky prvky v nejakej dátovej štruktúre, ale je dôležité ich postupne získavať až vždy, keď ich budeme potrebovať (teda vlastne niečo ako __iter__()
a __next__()
). Na toto využijeme nový mechanizmus generátorov - bude to ďalší spôsob vytvárania iterátora. Tieto sa podobajú na bežné funkcie, ale namiesto return
používajú príkaz yield
. Generátory fungujú na takomto princípe:
keď takúto generátorovú funkciu zavoláme, nevytvorí sa ešte žiadna hodnota, ale vytvorí sa generátorový objekt (pri iterátoroch sme tomu hovorili iterátorový objekt)
keď si od generátorového objektu teraz vypýtame jednu hodnotu, dozvieme sa prvú z nich (slúži na to štandardná funkcia
next()
)každé ďalšie vypýtanie hodnoty (funkcia
next()
) nám odovzdá ďalšiu hodnotu postupnostikeď už generátorový objekt nemá ďalšiu hodnotu, tak volanie funkcie
next()
vyvolá chybovú správuStopIteration
Samotná generátorová funkcia pri výskyte príkazu yield
nekončí „len“ odovzdá jednu z hodnôt postupnosti a pokračuje ďalej. Funkcia končí až na príkaze return
(alebo na konci funkcie) a vtedy automaticky vygeneruje chybu (exception) StopIteration. Samotné odovzdanie hodnoty (príkazom yield
) preruší vykonávanie generátorovej funkcie s tým, že sa presne zapamätá miesto, kde sa bude pokračovať aj s momentálnym menným priestorom. Volanie next()
pokračuje na tomto mieste, aby odovzdal ďalšiu hodnotu.
Zadefinujme funkciu delitele()
ako generátor:
def delitele(n):
for i in range(1, n+1):
if n % i == 0:
yield i
vytvoríme generátorový objekt:
>>> d = delitele(15)
premenná d
je naozaj generátorový objekt, ktorý zatiaľ nevygeneroval žiadny prvok postupnosti:
>>> d
<generator object delitele at 0x0000000003042828>
keď chceme prvý prvok, zavoláme metódu next()
rovnako ako pri iterátoroch:
>>> next(d)
1
každé ďalšie volanie next()
vygeneruje ďalšie prvky:
>>> next(d)
3
>>> next(d)
5
>>> next(d)
15
>>> next(d)
...
StopIteration
Po poslednom prvku funkcia next()
vyvolala výnimku StopIteration
. Mohli by sme to zapísať aj pomocou for-cyklu:
>>> for i in delitele(15):
print(i, end=' ')
1 3 5 15
Ukážky generátorových funkcií¶
postupnosť piatich hodnôt:
def prvo(): yield 2 yield 3 yield 5 yield 7 yield 11
>>> list(prvo()) [2, 3, 5, 7, 11]
to isté pomocou for-cyklu:
def prvo(): for i in [2, 3, 5, 7, 11]: yield i
>>> list(prvo()) [2, 3, 5, 7, 11]
For-cyklus v generátorových funkciách, ktorý generuje yield
, môžeme skrátene zapísať aj pomocou verzie yield from:
to isté ako predchádzajúca verzia:
def prvo(): yield from [2, 3, 5, 7, 11]
>>> list(prvo()) [2, 3, 5, 7, 11]
Parametrom yield from
môže byť ľubovoľný iterovateľný objekt nielen zoznam, napríklad aj range()
alebo aj iný generátorový objekt (napríklad v rekurzívnych funkciách).
využitie
range()
:def test(n): yield from range(n+1) yield from range(n-1, -1, -1) # alebo reversed(range(n))
>>> list(test(3)) [0, 1, 2, 3, 2, 1, 0] >>> list(test(5)) [0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0]
skoro to isté ale rekurzívne:
def urob(n): if n < 1: yield 0 else: yield n yield from urob(n-1) yield n
>>> list(urob(3)) [3, 2, 1, 0, 1, 2, 3] >>> list(urob(5)) [5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5]
fibonacciho postupnosť:
def fib(n): a, b = -1, 1 while n > 0: a, b = b, a+b yield b n -= 1
>>> list(fib(10)) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] >>> for i in fib(10): print(i, end=' ') 0 1 1 2 3 5 8 13 21 34
ak by sme chceli z fibonacciho postupnosti vypísať len po prvý člen, ktorý je aspoň 10000 a my nevieme odhadnúť, koľko ich budeme potrebovať, zapíšeme napríklad:
>>> for i in fib(10000): print(i, end=' ') if i > 10000: break 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946
vďaka tomu, že fib()
je generátor a nie funkcia, ktorá vytvára zoznam hodnôt, nebolo pre tento for-cyklus potrebné vyrobiť 10000 prvkov, ale len toľko, koľko ich bolo treba v cykle.
Generátorovým funkciám se niekedy hovorí lenivé vyhodnocovanie (lazy evaluation), lebo funkcia počíta ďalšiu hodnotu až keď je o ňu požiadaná (pomocou next()
) - teda nič nepočíta zbytočne dopredu.
Generované zoznamy¶
už sme sa dávnejšie stretli so zápismi:
>>> zoznam = [i for i in range(20) if i%7 in [2,3,5]]
>>> zoznam
[2, 3, 5, 9, 10, 12, 16, 17, 19]
>>> mn = {i for i in range(20) if i%7 in [2,3,5]}
>>> mn
{2, 3, 5, 9, 10, 12, 16, 17, 19}
>>> ntica = tuple(i for i in range(20) if i%7 in [2,3,5])
>>> ntica
(2, 3, 5, 9, 10, 12, 16, 17, 19)
Podobne vieme vygenerovať nielen zoznam (list
), množinu (set
) a n-ticu (tuple
), ale aj slovník (dict
). Hovoríme tomu list comprehension (resp. iný typ) - po slovensky generované zoznamy (niekedy aj generátorový zápis alebo notácia). Všimnite si, že n-ticu musíme generovať pomocou funkcie tuple()
, lebo inak:
>>> urob = (i for i in range(20) if i%7 in [2,3,5])
>>> urob
<generator object <genexpr> at 0x022A6760>
>>> list(urob)
[2, 3, 5, 9, 10, 12, 16, 17, 19]
dostávame generátorový objekt úplne rovnaký ako napríklad:
def gg():
for i in range(20):
if i%7 in [2,3,5]:
yield i
>>> urob = gg()
>>> urob
<generator object gg at 0x022A6828>
>>> list(urob)
[2, 3, 5, 9, 10, 12, 16, 17, 19]
Takže jednoduché generátorové objekty môžeme vytvárať aj takto zjednodušene:
def gg(*zoznam):
return (i for i in range(20) if i%7 in zoznam)
>>> urob = gg(2, 3, 5)
>>> urob
<generator object <genexpr> at 0x0229E8A0>
>>> list(urob)
[2, 3, 5, 9, 10, 12, 16, 17, 19]
Zhrnutie: generátor je funkcia, ktorá postupne generuje prvky nejakej kolekcie. Generátor je vlastne ďalším spôsobom, ako vytvoriť iterátor (každý generátor je iterátor, ale nie každý iterátor je generátor), teda aj generátor je iterovateľný.
Binárne súbory¶
V minulom semestri sme sa zoznámili s prácou s textovými súbormi:
textový súbor môžeme chápať ako postupnosť riadkov, pričom každý riadok je znakový reťazec ukončený znakom
'\n'
takýto súbor sa najčastejšie nachádza na nejakom externom médiu, napríklad na disku alebo USB (najčastejšie majú príponu
'.txt'
,'.py'
,'.html'
,'.js'
,'.css'
,'.xml'
,'.svg'
, …) a môžeme ich pozrieť alebo upravovať v rôznych textových editoroch, napríklad aj v pythonovskom IDLEaby sme mohli pracovať so súborom, musíme ho najprv otvoriť (jedna z možností):
na čítanie - ďalej sa umožní čítať riadok za riadkom (alebo postupnsoť znakov)
na zápis - vymaže doterajší obsah a umožní postupne zapisovať riadok za riadkom (alebo postupnsoť znakov)
na zápis s ponechaním pôvodného obsahu
po ukončení práce so súborom by sme ho mali zatvoriť,aby sme ho neblokovali pre operačný systém
Nie všetky súbory v súborových systémoch sú ale textové. Sú to napríklad rôzne grafické súbory, napríklad '.png'
, '.bmp'
, '.jpg'
, … ale aj komprimované súbory ako '.zip'
, '.rar'
, '.cab'
, …, vykonateľné súbory ako '.exe'
, '.com'
, '.dll'
, … alebo rôzne dátové súbory so známou alebo s neznámou štruktúrou '.doc'
, '.odt'
, '.pdf'
, '.dat'
, …
Takéto súbory, ak otvoríme v textovom editore, sa zobrazia s množstvom divných znakov. Ak by sme ich ale zobrazili v editoroch, ktoré zvládajú šestnástkový výpis, videli by sme niečo takéto:

Zobrazuje sa postupnosť bajtov, pričom niektoré z nich zodpovedajú niektorým ASCII znakom.
Typ bytes¶
Binárny súbor je postupnosť bajtov. V Pythone budeme zapisovať do binárneho súboru a čítať z neho nie znakové reťazce, ale nový typ bytes
. Tento typ je nemeniteľnou (immutable) postupnosťou bajtov, teda celých čísel z intervalu <0, 255>. V Pythone zapisujeme takéto postupnosti v tvare znakového reťazca, pred ktorým je prilepený znak b
:
>>> b'+ A' # trojbajtová postupnosť
b'+ A'
>>> list(b'+ A') # tento zápis reprezentuje ASCII-hodnoty týchto znakov
[43, 32, 65]
>>> bytes((45, 49, 70, 100)) # postupnosť bajtov vieme skonštruovať aj zo zoznamu čísel
b'-1Fd'
>>> bytes() # prázdna postupnosť bajtov
b''
>>> bytes('Python', 'utf8') # postupnosť bajtov vieme skonštruovať aj zo znakového reťazca
b'Python' # vtedy musíme uviesť aj kódovanie
>>> tuple(b'Python')
(80, 121, 116, 104, 111, 110)
Takto vidíme zobrazené bajty, ktoré majú v ASCII svoju reprezentáciu. Ak potrebujeme zadať bajt, ktorý nemá v ASCII svoje zobrazenie, použije sa zadávanie kódu bajtu v šestnástkovej sústave:
>>> b'A\x05B' # trojbajtová postupnosť
b'A\x05B'
>>> tuple(b'A\x05B') # '\x05' označuje jeden bajt s hodnotou 5
(65, 5, 66)
>>> b = bytes(range(130, 250, 10)) # dvanásť bajtová postupnosť čísel
>>> b
b'\x82\x8c\x96\xa0\xaa\xb4\xbe\xc8\xd2\xdc\xe6\xf0' # čísla sú v šestnástkovej sústave
>>> tuple(b)
(130, 140, 150, 160, 170, 180, 190, 200, 210, 220, 230, 240)
>>> for i in b: # postupnosť vieme prechádzať for-cyklom
print(i, end=' ')
130 140 150 160 170 180 190 200 210 220 230 240
Okrem nemeniteľného (immutable) štandardného typy bytes
v Pythone môžete využiť meniteľný typ bytearray
, ktorý funguje skoro presne rovnako. Viac informácií bytearray.
Práca s binárnym súborom¶
S binárnym súborom pracujeme veľmi podobne ako s textovým. Zápis do súboru:
with open('subor.dat', 'wb') as subor: # 'wb' označuje zápis do binárneho súboru
subor.write(b'student')
subor.write(bytes(range(10)))
Zapíše 17 bajtov, najprv 7 bajtov ASCII kodov reťazca 'student'
a za tým 10 bajtov s hodnotami 0, 1, 2, …, 9.
Čítanie zo súboru:
with open('subor.dat', 'rb') as subor: # 'rb' označuje čítanie z binárneho súboru
prvy = subor.read(7)
bajt = subor.read(1)
druhy = b''
while bajt != b'': # kým nie je koniec súboru
druhy += bajt
bajt = subor.read(1)
Tento program najprv prečíta prvých 7 bajtov do premennej prvy
(zrejme bude obsahovať postupnosť b'student'
) a do premennej druhy
prečíta zvyšné bajty súboru (zrejme b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09'
, čo sa môže zobraziť ako b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t'
). Dalo by sa to zapísať aj takto jednoducho:
with open('subor.dat', 'rb') as subor:
prvy = subor.read(7) # prvých 7 bajtov
druhy = subor.read() # celý zvyšok súboru až do konca
Konverzie dát z a do postupnosti bajtov¶
Na stránke Bytes Objects v Pythonovom helpe si môžete prečítať ďalšie možnosti práce s týmto typom (napríklad, užitočnú metódu decode
), ale užitočné môžu byť aj niektoré metódy s celočíselným typom (napríklad, to_bytes
a from_bytes
). Preštudujte si nasledovné ukážky:
ako prerobiť postupnosť bajtov (typ
bytes
) na obyčajný znakový reťazec (typstr
):>>> bajty = b'Kovac Igor' >>> ''.join(chr(i) for i in bajty) # pomocou chr pre každý bajt a potom join 'Kovac Igor' >>> ''.join(map(chr, bajty)) # pomocou mapovania každého bajtu na znak a join 'Kovac Igor' >>> bajty.decode() # pomocou metódy decode 'Kovac Igor'
ako z celého čísla vyrobiť postupnosť štyroch bajtov (na začiatku je najnižší bajt):
>>> cislo = 1234567 >>> bajty = b'' >>> for i in range(4): # pomocou for-cyklu a postupným delením 256 bajty += bytes((cislo%256,)) cislo //= 256 >>> bajty b'\x87\xd6\x12\x00'
alebo pomocou metódy:
>>> cislo = 1234567 >>> cislo.to_bytes(4, 'little') # parameter 'little' označuje, že začína najnižším bajtom b'\x87\xd6\x12\x00'
ako z postupnosti štyroch bajtov vyrobiť celé číslo:
>>> bajty = b'\xb1\x7f\x39\x05' >>> cislo = 0 >>> for bajt in reversed(bajty): cislo = cislo*256 + bajt >>> cislo 87654321
alebo pomocou metódy:
>>> bajty = b'\xb1\x7f\x39\x05' >>> int.from_bytes(bajty, 'little') # parameter 'little' označuje, že začína najnižším bajtom 87654321
Príklady práce s binárnym súborom¶
Uvedieme niekoľko užitočných príkladov.
obsah binárneho súboru ako 16-ové hodnoty:
def to_hex(subor, pocet=None): # pocet urcuje pocet precitanych bajtov with open(subor, 'rb') as file: return ' '.join(f'{x:02X}' for x in file.read(pocet))
>>> to_hex('python.exe', 1000)
to isté by sa dalo zapísať aj takto pomocou metódy
hex
:def to_hex(subor, pocet=None): with open(subor, 'rb') as file: return file.read(pocet).hex(' ')
obsah binárneho súboru so zobrazitelnými znakmi:
def to_str(subor, pocet=None): # pocet urcuje pocet precitanych bajtov with open(subor, 'rb') as file: return ''.join(chr(x) if 32<=x<127 else '.' for x in file.read(pocet))
>>> to_str('python.exe', 1000)
zapísať do súboru zoznam dvojbajtových čísel (kladné čísla od 0 do 65535) - čísla sa zapisujú tak, že prvý bajt z dvojice je ten vyšší:
def zapis_zoznam(subor, zoznam): with open(subor, 'wb') as file: for x in zoznam: file.write(x.to_bytes(2, 'big'))
>>> zapis_zoznam('zoznam.dat', range(1000, 2000, 7))
prečítať zo súboru zoznam dvojbajtových čísel (kladné čísla od 0 do 65535) - čísla sa čítajú tak, že prvý bajt z dvojice je ten vyšší:
def citaj_zoznam(subor): zoznam = [] with open(subor, 'rb') as file: x = file.read(2) while x != b'': zoznam.append(int.from_bytes(x, 'big')) x = file.read(2) return zoznam
>>> zoz = citaj_zoznam('zoznam.dat') >>> zoz
zapísať do súboru zoznam znakových reťazcov - každý reťazec sa zapíše tak, že najprv je v jednom bajte dĺžka reťazca v bajtoch (po zakódovaní do
bytes
) a za tým samotný reťazec:def zapis_retazce(subor, retazce): with open(subor, 'wb') as file: for r in retazce: r = r.encode() file.write(bytes((len(r),))+r)
>>> ret = ('prvý', 'druhý\na tretí', '', '\n\n\n') >>> zapis_retazce('retazce.dat', ret)
prečítať zo súboru zoznam znakových reťazcov - každý reťazec sa prečíta tak, že najprv je v jednom bajte dĺžka reťazca v bajtoch (po zakódovaní do
bytes
) a za tým samotný reťazec:def citaj_retazce(subor): ret = [] with open(subor, 'rb') as file: dlzka = file.read(1) while dlzka != b'': ret.append(file.read(dlzka[0]).decode()) dlzka = file.read(1) return ret
>>> r = citaj_retazce('retazce.dat') >>> r
vytvoriť kópiu súboru:
def kopia(subor1, subor2): with open(subor1, 'rb') as file1, open(subor2, 'wb') as file2: file2.write(file1.read())
>>> kopia('subor.dat', 'subor1.dat')
skontrolovať, či majú dva súboru identický obsah:
def zhoda(subor1, subor2): with open(subor1, 'rb') as file1, open(subor2, 'rb') as file2: return file1.read() == file2.read()
>>> zhoda('subor.dat', 'subor1.dat')
Cvičenia¶
iterátory¶
Vytvor triedy Fib s metódami
__iter__
a__next__
, ktora postupne generujen
-prvkovú postupnosť fibonacciho čísel:class Fib: def __init__(self, n): ... def __iter__(self): ... def __next__(self): ... for f in Fib(100): print(f, end=' ') if f > 20: break
Vypíše:
0 1 1 2 3 5 8 13 21
Vytvor iterátor, ktorý bude generovať nekonečnú postupnosť súčtov celých čísel, t.j. postupne 1, 1+2, 1+2+3, 1+2+3+4, … . Použi
__getitem__
:class Sucty: def __init__(self): ... def __getitem__(self, ix): ... sucty = iter(Sucty()) for i in range(5): print(next(sucty), end=' ')
Vypíše:
1 3 6 10 15
Samozrejme, že to má fungovať pre ľubovoľné počty súčtov.
Funkcia
test()
prechádza pomocou dvoch for-cyklov dve iterovateľné hodnoty (napríklad množiny):def test(m1, m2): for i in m1: for j in m2: print(i, j, i+j) test({'a', 'b'}, {'x', 'y', 'z'})
Zapíš funkciu
test1
, ktorá urobí rovnaké cykly akotest
, ale for-cykly nahradí pomocou iterátorov a while-cyklov.
generátory¶
Napíš funkciu
grange(start, stop, krok)
, ktorá bude generátorom (použijeyield
) a bude generovať rovnakú postupnosť celých čísel ako štandardnýrange()
. Môžeš predpokladať, že parameterkrok
je väčší ako0
. Samozrejme, že vo funkcii nesmieš použiť funkciurange()
. Napríklad:>>> tuple(grange(3, 50, 7)) (3, 10, 17, 24, 31, 38, 45)
Generátor by mohol fungovať aj pre desatinné čísla:
>>> tuple(grange(1, 5, 0.5)) (1, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5)
Vytvor a otestuj tieto štyri verzie funkcie
mocniny(n)
, ktorávráti postupnosť (
list
) druhých mocnín[1, 4, 9, 16, ..., n**2]
vytvorenú pomocou for-cyklu a metódyappend()
vráti postupnosť (
list
) ale vytvorené pomocou generátorovej notácie (napríklad[... for i in ...]
)túto postupnosť vráti ako generátorovú funkciu (použitím
yield
)túto postupnosť vráti ako generátorovú funkciu (použitím generátorového zápisu
(... for i in ...)
bezyield
)
Zapíš funkciu
zdvoj(gen)
, ktorá vygeneruje každý prvok 2-krát za sebou - funkcia vráti generátorvyskúšaj nielen s parametrom typu generátor, ale napríklad aj so zoznamom alebo s reťazcom
na riešenie asi použiješ funkcie
iter
anext
a potom aj príkazyield
Napríklad:
>>> g = zdvoj(i**2 for i in range(1, 5)) >>> g <generator object zdvoj at 0x022A6828> >>> list(g) [1, 1, 4, 4, 9, 9, 16, 16] >>> zdvoj('Python') ... >>> zdvoj([2, 3, 5]) ...
Zapíš dve verzie funkcie
spoj(gen1, gen2)
, ktorá vygeneruje (vráti ako generátor) najprv všetky prvkygen1
potom všetky prvkygen2
vyskúšaj nielen s parametrami typu generátor, ale napríklad aj so zoznamami a reťazcami
na riešenie môžeš použiť (ale nemusiš) funkcie
iter
anext
a zrejme aj príkazyield
zapíš verziu funkcie
spoj(*gen)
, v ktorej sa spája ľubovoľne veľa generátorov
Napríklad:
>>> g = spoj(iter(range(5)), iter(range(10, 0, -2))) >>> g <generator object spoj at 0x00A823C0> >>> print(*g) 0 1 2 3 4 10 8 6 4 2 >>> g = spoj(iter(range(5)), iter('ahoj'), iter(range(10, 0, -2))) >>> print(*g) 0 1 2 3 4 a h o j 10 8 6 4 2
Zapíš tri verzie funkcie
mix(gen1, gen2)
, ktorá generuje prvky na striedačku - ak v jednom skončí skôr, tak už berie len zvyšné druhéhonajprv s pomocným zoznamom: prvý generátor najprv presype prvky do zoznamu, a potom počas prechodu druhým generátorom dáva aj prvky z pomocného zoznamu
bez pomocného zoznamu len pomocou štandardnej funkcie
next()
porozmýšľaj nad verziou
mix(*gen)
, v ktorej sa mixuje ľubovoľne veľa generátorov
Napríklad:
>>> print(*mix(iter('PYTHON'), iter(range(4)), iter('ahoj'))) P 0 a Y 1 h T 2 o H 3 j O N
binárne súbory¶
Napíš funkciu
vyrob(meno_suboru, n, pocet)
, ktorá vygeneruje binárny súbor spocet
náhodných celýchn
-bajtových čísel (čísla začínajú najvyšším bajtom). Náhodné hodnoty nech sú napríklad z rozsahurange(0, 256**n, 100)
. Riešenie otestuj pre rôznen
.
Napíš funkciu
citaj(meno_suboru, n)
, ktorá prečíta súbor z predchádzajúcej úlohy a vráti jeho prvky v tvare zoznamu (list
). Svoje riešenie otestuj.
V binárnom súbore
'cisla.dat'
je uložená postupnosť celýchn
-bajtových čísel (čísla začínajú najvyšším bajtom). Napíš funkciuvyhod(meno_suboru='cisla.dat', n=1, hodnota=0)
, ktorá z daného súboru vyhodí všetky výskyty zadanej hodnoty. Napríklad, volanievyhod('cisla4.dat', 4, 1000)
vyhodí z daného súboru 4-bajtových celých čísel všetky výskytu hodnoty1000
. Svoje riešenie otestuj s rôznymi binárnymi súbormi, v ktorých je rôzna veľkosť zapísaných celých čísel (napríklad pre jednobajtové, dvojbajtové, štvorbajtové).
Napíš funkciu
pocet_bajtov(x)
, ktorá pre kladné celé číslox
zistí minimálny počet bajtov, ktoré dané číslo zaberá. Napríklad:>>> pocet_bajtov(0) 1 >>> pocet_bajtov(200) 1 >>> pocet_bajtov(2000) 2
Napíš funkciu
zapis_zoznam(meno_suboru, zoznam)
, ktorá do binárneho súboru zapíše prvky daného zoznamu (list
). Prvkami sú buď kladné celé čísla alebo znakové reťazce. Navrhni formát binárneho súboru tak, aby sa dal spätne prečítať a vytvoriť pôvodný zoznam: asi najlepšie tak, že v ňom bude pre každý prvok informácia o tom, či je to celé číslo alebo znakový reťazec (napríklad znakom'i'
alebo's'
) a tiež počet bajtov, ktoré zaberá (môžeš predpokladať, že tento počet bude do255
). Otestuj, napríklad pre:zapis_zoznam('zoznam.dat', ['Abc', 42, '', '*'*100, 7**100])
Napíš funkciu
citaj_zoznam(meno_suboru)
, ktorá vráti prečítaný zoznam z binárneho súboru z predchádzajúcej úlohy.
Napíš funkciu
zapis_desatinne(meno_suboru, zoznam)
, ktorá do binárneho súboru zapíše zoznam desatinných čísel (float
). Navrhni takú reprezentáciu, aby sa takýto súbor dal naspäť prečítať. Otestuj, napríklad:zapis_desatinne('float.dat', list(1/i for i in range(1, 8)))
Napíš funkciu
citaj_desatinne(meno_suboru)
, ktorá vráti prečítaný zoznam z binárneho súboru z predchádzajúcej úlohy.
4. Týždenný projekt¶
Toto domáce zadanie bude riešiť takúto úlohu:
v súbore sa nachádzajú informácie o študentoch nejakej školy (osobné číslo, meno a priezvisko, zoznam udelených známok);
tento súbor treba prečítať a vytvoriť z neho jednosmerný spájaný zoznam (metoda
read
), v ktorom bude v každom vrchole informácia o jednom študentovi; vrcholy budú v spájanom zozname v tom poradí, v akom boli v súbore;z tohto zoznamu budeme vedieť nájsť študenta (metóda
__getitem__
) podľa zadaného osobného číslatento zoznam bude treba vedieť zapísať späť do súboru (metóda
write
), ale v inom poradí:metóda
remove_min
nájde študenta s najmenším osobným číslom, tento vrchol zo spájaného zoznamu odstráni a vráti ho ako výsledok funkcieúdaje tohto študenta zapíše v požadovanom formáte do súboru
toto opakuje, kým nebude spájaný zoznam prázdny
Formát súboru so študentmi nebude textový, ale binárny. Pre každého študenta bude v súbore postupnosť takýchto bajtov:
najprv 4 bajty s osobným číslom (osobné číslo je celé číslo z intervalu <
0
,4294967295
>, teda256**4-1
), kde najnižší bajt je prvý v štvorici, napríklad číslo74565
bude v štyroch bajtoch ako(69, 35, 1, 0)
, alebo v šestnástkovej sústave ako(0x45, 0x23, 0x01, 0x00)
potom nasleduje postupnosť znakov (postupnosť bajtov s ASCII-kódmi, znaky nebudú obsahovať diakritiku), pričom prvý bajt postupnosti obsahuje dĺžku reťazca, napríklad meno a priezvisko
'Abc Def'
, bude uložené v 8 bajtoch:(7, 65, 98, 99, 32, 68, 101, 102)
, čo vieme zapísať aj v 16-ovej sústave:(0x07, 0x41, 0x62, 0x63, 0x20, 0x44, 0x65, 0x66)
; zrejme takéto reťazce môžu mať maximálnu dĺžku 255 znakov; ASCII-kódy znakov budú len z intervalu<32, 126>
na záver je to postupnosť známok v tvare čísel od
1
do6
, ktorá je ukončená číslom0
, pričom1
zodpovedá známke A,2
zodpovedá B, atď. až6
zodpovedá Fx,0
tu označuje koniec postupnosti a nereprezentuje známku, napríklad postupnosť(3, 1, 6, 4, 0)
popisuje 4 známky (C, A, Fx, D); ak budeme niekedy potrebovať vypočítať priemer, tak použijeme prepočet, v ktorom známka A má hodnotu1
, známka B má hodnotu1.5
, známka C má hodnotu2
, atď. až známka Fx má hodnotu4
; potom priemer známok je(2+1+4+2.5)/4
, teda2.375
ak sa v súbore hneď na začiatku objaví táto postupnosť bajtov (zapísali sme ju v 16-ovej sústave):
0x45, 0x23, 0x01, 0x00, 0x07, 0x41, 0x62, 0x63, 0x20, 0x44, 0x65, 0x66, 0x03, 0x01, 0x06, 0x04, 0x00
týchto 17 bajtov reprezentuje informácie o jednom študentovi s osobným číslom
74565
, s menom a priezviskom'Abc Def'
a so známkami (C, A, Fx, D); za týmito bajtami môže v súbore nasledovať ďalšia postupnosť bajtov, ktorá popisuje ďalšieho študenta
Napíš modul s menom riesenie.py
, ktorý bude obsahovať jedinú triedu s ďalšou vnorenou podtriedou a týmito metódami:
class LinkedList:
class Student:
def __init__(self, number, name, grades): # osobné číslo, meno a priezvisko, zoznam známok
self.number = number # celé číslo z <0, 4294967295>
self.name = name # znakový reťazec
self.grades = grades # n-tica (tuple) celých čísel z <1, 6>
self.next = None
def __repr__(self):
return f'Student({self.number}, {self.name!r}, {self.grades})'
def __init__(self):
self.zac = self.kon = None
...
def read(self, file_name):
...
def write(self, file_name):
...
def remove_min(self):
...
return ...
def __getitem__(self, number):
...
return None
def __len__(self):
...
return 0
Trieda LinkedList
implementuje jednosmerný spájaný zoznam študentov, pričom metódy triedy by mali mať takúto funkčnosť (môžeš si dodefinovať aj ďalšie pomocné metódy):
metóda
__init__()
inicializuje atribútyzac
akon
pre referencie na začiatok a koniec zoznamu;metóda
read(file_name)
otvorí binárny súbor a informácie o študentoch pridá na koniec momentálneho spájaného zoznamu; zrejme by takto mohol z viacerých súborov vyrobiť jeden spájaný zoznam;metóda
write(file_name)
zapíše kompletný spájaný zoznam do binárneho súboru; použije na to metóduremove_min
, pomocou ktorej získa študenta s najmenším osobným číslom, študenta zapíše do súboru; ak túto metódu bude volať, kým nebude spájaný zoznam prázdny, zapíše do súboru všetkých študentov v usporiadanom poradí;metóda
remove_min()
vyhľadá študenta s najmenším osobným číslom, tohto študenta vyhodí zo spájaného zoznamu a samotný vrchol (typuself.Student
) vráti ako výsledok funkcie; ak bol zoznam už prázdny, funkcia vrátiNone
;metóda
__getitem__(number)
vráti informácie o študentovi s daným osobným číslom v tvare dvojice(meno, priemer)
, kdemeno
je znakový reťazec s menom a priezviskom študenta,priemer
je priemer jeho známok; ak je zoznam študentových známok prázdny, jeho priemer je0
; ak študenta s daným osobným číslom nenajde, funkcia vrátiNone
;metóda
__len__()
vráti aktuálny počet prvkov zoznamu.
Obmedzenia¶
svoje riešenie odovzdaj v súbore
riesenie.py
, pričom sa v ňom bude nachádzať len jedna definícia triedyLinkedList
, triedaStudent
bude vnorená v triedeLinkedList
prvé tri riadky tohto súboru budú obsahovať:
# 4. zadanie: binarny subor # autor: Janko Hrasko # datum: 16.3.2023
zrejme ako autora uvedieš svoje meno
atribúty
zac
akon
v triedeLinkedList
musia obsahovať referencie na začiatok a koniec zoznamutvoj program by nemal počas testovania testovačom nič vypisovať (žiadne testovacie
print()
)
Testovanie¶
Keď budeš spúšťať svoje riešenie na počítači, môžeš do súboru riesenie.py
pridať testovacie riadky, ktoré ale testovač vidieť nebude, napríklad, ak subor1.dat
obsahuje takúto postupnosť bajtov (zapísali sme ich v 16-ovej sústave a rozsekali sme ich tak, aby bolo lepšie vidieť informácie o štyroch študentoch):
4523010007416263204465660301060400
0004000007476820496A6B6C0200
B17F3905094D204E6F707172737400
87D61200075576777879205A0101010300
môžeme zapísať takýto test:
if __name__ == '__main__':
zoz = LinkedList()
zoz.read('subor1.dat')
print('pocet =', len(zoz))
p = zoz.zac
while p:
print(p)
p = p.next
print()
for cislo in 74565, 87654321, 8765432:
print(f'student[{cislo}] =', zoz[cislo])
print('min =', zoz.remove_min())
print('pocet po remove_min =', len(zoz))
zoz.write('subor2.dat')
print('pocet po write =', len(zoz))
Tento test by mal vypísať:
pocet = 4
Student(74565, 'Abc Def', (3, 1, 6, 4))
Student(1024, 'Gh Ijkl', (2,))
Student(87654321, 'M Nopqrst', ())
Student(1234567, 'Uvwxy Z', (1, 1, 1, 3))
student[74565] = ('Abc Def', 2.375)
student[87654321] = ('M Nopqrst', 0)
student[8765432] = None
min = Student(1024, 'Gh Ijkl', (2,))
pocet po remove_min = 3
pocet po write = 0
Projekt riesenie.py
odovzdaj na úlohový server https://list.fmph.uniba.sk/.