Имплицитне секвенце¶
Низ се може представити без да се сваки члан експлицитно чува у меморији рачунара. Односно, може да се направи објекат који омогућава приступ свим члановима неког секвенцијалног скупа података без да се израчунавају вредност сваког члана унапред. Уместо тога, чланови се израчунавају на захтев.
Пример ове идеје јавља се у типу података распона или range
представљеном у другом поглављу. Распон, односно range
представља узастопну, ограничену секвенцу целих бројева. Међутим, није случај да је сваки члан те секвенце експлицитно представљен у рачунарској меморији. Уместо тога, када се члан из range
то јест распона затражи, он се израчунава. Дакле, могу се представити веома велики распони целих бројева без употребе великих блокова меморије. Само су крајње тачке распона похрањене као део range
објекта.
>>> р = range(10000, 1000000000)
>>> р[45006230]
45016230
У овом примеру се не складиште свих 999’990’000 целих бројева у овом распону када се инстанца распона ствара. Уместо тога, објект распона додаје први члан 10’000 индексу 45’006’230 да би произвео члан 45’016’230. Израчунавање вредности на захтев, уместо преузимања истих из постојеће представе, пример је такозваног лењег израчунавања. У рачунарству, лења израчунавања описују било који програм који одлаже израчунавања вредности док те вредности нису неопходне.
Итератори¶
Пајтон као и многи други програмски језици пружају јединствени начин секвенцијалне обраде чланова неког садржаоца истих који се назива итератор. Итератор је објекат који пружа секвенцијални приступ вредностима, једну по једну.
Апстракција итератора има две компоненте: механизам проналажења следећег члана у секвенци која се обрађује и механизам сигнализације да је достигнут крај секвенце и да не преостаје више ниједан члан. За било који садржалац, попут низова или опсега, итератор се може добити позивањем уграђене iter
функције. Садржају итератора може се приступити позивом уграђене next
функције.
>>> прости = [2, 3, 5, 7]
>>> type(прости)
<class 'list'>
>>> итератор = iter(прости)
>>> type(итератор)
<class 'list_iterator'>
>>> next(итератор)
2
>>> next(итератор)
3
>>> next(итератор)
5
Пајтон сигнализира да нема више преосталих вредности подижући StopIteration
изузетак након next
позива. Овај изузетак се може обрадити користећи се try
наредбом.
>>> next(итератор)
7
>>> next(итератор)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> try:
... next(итератор)
... except StopIteration:
... print('Нема више вредности!')
Нема више вредности!
Итератор одржава локално стање да представи свој положај унутар секвенце. Сваки пут када се next
позове, тај положај напредује. Два одвојена итератора могу пратити два различита положаја унутар исте секвенце. Међутим, два имена за исти итератор делиће исти положај јер деле и исту вредност.
>>> о = range(3, 13)
>>> п = iter(о) # први итератор кроз о
>>> next(п)
3
>>> next(п)
4
>>> р = iter(о) # други итератор кроз о
>>> next(р)
3
>>> next(р)
4
>>> с = р # Резервно име за други итератор
>>> next(с)
5
>>> next(с)
6
Напредовање другог итератора не утиче на први. Пошто је последња вредност враћена од стране првог итератора била 4, његов положај је остао да врати 5 у наредној итерацији. С друге стране, други итератор је у положају да врати 7 у наредној итерацији.
>>> next(п)
5
>>> next(р)
7
Позивање уграђене iter
функције над итератором вратиће тај итератор, а не његову копију. Ово понашање је укључено у Пајтон, тако да програмер може позвати iter
с неком вредношћу како би добио итератор без потребе да брине да ли је она већ итератор или садржалац.
>>> т = iter(р) # Још једно резервно име за други итератор
>>> next(т)
8
>>> next(с)
9
>>> next(р)
10
Корисност итератора произилази из чињенице да секвенца података која је основа за итератор и која заправо лежи испод итератора можда неће бити експлицитно представљена у меморији рачунара. Итератор пружа механизам за разматрање по редоследу сваке вредности у секвенци, али сви ти чланови не морају бити ускладиштени истовремено. Уместо тога, када се од итератора затражи следећи члан, тај члан се може израчунати на захтев, уместо да се преузме из већ постојећег меморијског извора.
Опсези могу лењо да израчунају чланове секвенце пошто је представљена секвенца уједначена, а било који члан унутар опсега је лако израчунати из почетне и крајње границе опсега. Итератори омогућавају лења генерисања много шире класе секвенцијалних скупова података јер не треба да подрже приступ произвољним члановима секвенце која се налази у основи итератора. Уместо тога, од итератора се тражи да израчунају само први следећи члан секвенце, редоследом, сваки пут када се затражи други члан. Иако није тако флексибилан као случајни приступ (односно приступ произвољним члановима секвенце у било ком редоследу), секвенцијални приступ секвенцијалним подацима је често довољан за примене у обради података.
Итерирајуће вредности¶
Свака вредност која може да произведе итераторе назива се итерирајућа вредност. У Пајтону, итерирајућа вредност је све што се може проследити уграђеној iter
функцији. Итерирајуће вредности укључују секвенце као што су ниске и поворке, као и друге садржаоце као што су скупови и речници. Итератори су такође и сами итерирајуће вредности јер се могу проследити iter
функцији.
Чак и неуређене структуре података, попут речника у Пајтону 3.5 и старијим верзијама, морају дефинисати редослед над својим садржајем када производе итераторе. Речници и скупови раније нису били уређени јер програмер није имао контролу над редоследом итерација, али Пајтон у својој спецификацији гарантује одређена својства њиховог редоследа.
>>> р = {'један': 1, 'два': 2, 'три': 3}
>>> р
{'један': 1, 'два': 2, 'три': 3}
>>> к = iter(р)
>>> next(к)
'један'
>>> next(к)
'два'
>>> в = iter(р.values())
>>> next(в)
1
>>> next(в)
2
Уколико се структура речника промени услед додавања или уклањања одређеног кључа, тада сви постојећи итератори над тим речником постају неважећи, а будући итератори могу имати измењен редослед њиховог садржаја у односу на претходне. С друге стране, промена вредности постојећег кључа не доводи до престанка важења итератора или промене редоследа у њиховом садржају.
>>> р.pop('два')
2
>>> next(к)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: dictionary changed size during iteration
Наредба for
се може користити за пролазак кроз садржај ма ког итератора или итерирајуће вредности.
>>> о = range(3, 6)
>>> и = iter(о)
>>> next(и)
3
>>> for x in и:
... print(x)
4
5
>>> list(и)
[]
>>> for x in о:
... print(x)
3
4
5
Уграђени итератори¶
Неколико уграђених функција прима итерирајуће вредности као аргументе и враћа итераторе. Ове функције се широко користе за лењу обраду секвенци.
Функција map
је лења: њен позив не врши рачунање неопходно за израчунавање чланова њеног резултата. Уместо тога, ствара се објекат итератора који може вратити резултате ако траже преко next
функције. Ова чињеница може се уочити у следећем примеру у ком се print
позив одлаже све док се одговарајући члан не затражи од удвостручен
-ог итератора.
>>> def удвостручиИиспиши(x):
... print('***', x, '=>', 2*x, '***')
... return 2*x
>>> о = range(3, 7)
>>> удвостручен = map(удвостручиИиспиши, о) # удвостручиИиспиши још није позвана
>>> next(удвостручен) # удвостручиИиспиши је позвана једанпут
*** 3 => 6 ***
6
>>> next(удвостручен) # удвостручиИиспиши је позвана поново
*** 4 => 8 ***
8
>>> list(удвостручен) # удвостручиИиспиши је позвана још двапут
*** 5 => 10 ***
*** 6 => 12 ***
[10, 12]
Уграђена функција filter
враћа итератор кроз неки подскуп итерирајуће вредности која јој је прослеђена као аргумент. С друге стране уграђена функција zip
враћа итератор кроз поворку вредности која комбинује по једну вредност из сваке од више прослеђених јој итерирајућих вредности. Такође и уграђена функција reversed
враћа итератор кроз улазну секвенцу, само у обрнутом редоследу од изворног.
For наредбе/петље¶
Наредба for
у Пајтону делује на итераторима. Објекти су итерирајуће вредности (сучеље) ако имају __iter__
методу која враћа итератор. Објекти итерирајућих вредности могу бити вредност у коју се вреднује <израз>
у заглављу for
наредбе:
for <име> in <израз>:
<пакет>
Да би извршио for
наредбу, Пајтон вреднује <израз>
у заглављу, који мора да враћа итерирајућу вредност. Затим се на ту вредност примењује iter
функција. Све док се не подигне StopIteration
изузетак, Пајтон изнова у више наврата позива next
над тим итератором и резултат повезује на <име>
у for
наредби. Затим извршава <пакет>
.
>>> бројеви = [1, 2, 3]
>>> for члан in бројеви:
... print(члан)
1
2
3
У горњем примеру, наредба for
имплицитно позива функцију iter(бројеви)
, која враћа итератор кроз садржај. Наредба for
затим позива next
над тим итератором и сваки пут додељује враћену вредност променљивој члан
. Овај процес се наставља све док итератор не подигне StopIteration
изузетак, у ком тренутку се завршава извршавање for
наредбе/петље.
Опскрбљени знањем итератора, може се имплементирати правило извршавања for
наредбе преко while
наредбе, наредбе доделе, и try
наредбе.
>>> чланови = iter(бројеви)
>>> try:
... while True:
... члан = next(чланови)
... print(члан)
... except StopIteration:
... pass
1
2
3
Горе, итератор враћен позивањем iter
на бројеви
везан је на име чланови
тако да се може редом испитивати за сваки члан редоследом. Клаузула за обраду StopIteration
изузетка не ради ништа изузев што руковођење изузетком пружа управљачки механизам за излазак из while
петље.
Сучеље итератора¶
Сучеље итератора у Пајтону дефинисано је користећи се методом под називом __next__
која враћа следећи члан неке секвенце која се налази у основи итератора. Као одговор на позивање __next__
методе, итератор може извршити произвољно израчунавање како би дохватио или израчунао следећи члан секвенце. Позиви __next__
методе врше мутирајућу промену на итератору: они мењају положај итератора. Стога ће вишеструки позиви __next__
методи по редоследу враћати чланове секвенце који леже у основи итератора. Пајтон обавештава да је дошло до краја основне секвенце подизањем StopIteration
изузетка приликом позива на __next__
.
Класа СловаИтер
из наставка итерира кроз низ слова који се налази у њеној основи почевши од слова почетак
па све до слова крај
, не укључујући га. Атрибут инстанце следећеСлово
чува следеће слово које треба вратити. Метода __next__
враћа ово слово и користи га за израчунавање нове вредности атрибута следећеСлово
.
>>> class СловаИтер:
... """Итератор кроз слова по азбучном редоследу."""
... def __init__(self, почетак='а', крај='е'):
... self.следећеСлово = почетак
... self.крај = крај
... def __next__(self):
... if self.следећеСлово == self.крај:
... raise StopIteration
... слово = self.следећеСлово
... self.следећеСлово = chr(ord(слово)+1)
... return слово
Користећи ову класу, можемо приступити словима у секвенци користећи __next__
методу или уграђену next
функцију која позива методу __next__
свог аргумента.
>>> словаИтер = СловаИтер()
>>> словаИтер.__next__()
'а'
>>> словаИтер.__next__()
'б'
>>> next(словаИтер)
'в'
>>> next(словаИтер)
'г'
>>> словаИтер.__next__()
'д'
>>> словаИтер.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 12, in next
StopIteration
Итератори су променљиви: током напредовања они прате положај у некој секвенци вредности која се налази у њиховој основи. Када се дође до краја, итератор је искоришћен и потрошен. Кроз инстанцу класе СловаИтер
може се проћи само једном. Након што њена метода __next__()
први пут подигне StopIteration
изузетак, она то наставља да чини и приликом сваког следећег позива. Типично, итератори се не ресетују већ се уместо тога стварају нове инстанце за започињање нових итерација.
Итератори такође омогућавају представљање бесконачних секвенци импелемнтацијом __next__
методе која никада не подиже StopIteration
изузетак. На пример, класа Позитивни
дата у наставку пролази кроз бесконачни низ позитивних целих бројева. Уграђена функција next
у Пајтону позива методу __next__
свог аргумента.
>>> class Позитивни:
... def __init__(self):
... self.следећиПозитиван = 1;
... def __next__(self):
... резултат = self.следећиПозитиван
... self.следећиПозитиван += 1
... return резултат
>>> п = Позитивни()
>>> next(п)
1
>>> next(п)
2
>>> next(п)
3
Сучеље итерирајућих вредности¶
Објекат је итерабилан ако враћа итератор када се позове његова __iter__
метода. Итерирајуће вредности представљају колекције података и пружају сталну представу која може произвести више од једног итератора.
На пример, инстанца класе Слова
у наставку представља секвенцу узастопних слова. Сваки пут када се позове њена __iter__
метода гради се нова инстанца СловаИтер
која омогућава секвенцијални приступ садржају секвенце.
>>> class Слова:
... def __init__(self, почетак='а', крај='е'):
... self.почетак = почетак
... self.крај = крај
... def __iter__(self):
... return СловаИтер(self.почетак, self.крај)
Уграђена функција iter
позива __iter__
методу свог аргумента. У низу израза у наставку, два итератора изведена из исте итерабилне секвенце независно дају слова у низу.
>>> ВдоМ = Слова('в', 'м')
>>> првиИтератор = ВдоМ.__iter__()
>>> next(првиИтератор)
'в'
>>> next(првиИтератор)
'г'
>>> другиИтератор = iter(ВдоМ)
>>> другиИтератор.__next__()
'в'
>>> првиИтератор.__next__()
'д'
>>> првиИтератор.__next__()
'е'
>>> другиИтератор.__next__()
'г'
>>> другиИтератор.__next__()
'д'
Итерирајућа инстанца класе Слова
названа ВдоМ
и инстанце итератора СловаИтер
под називима првиИтератор
и другиИтератор
се разликују по томе што се инстанца класе Слова
не мења, док се инстанце итератора мењају са сваким позивом на уграђене функције next
(или еквивалентно, са сваким позивањем методе __next__
). Итератор прати напредак кроз секвенцијалне податке, док итерирајућа вредност представља саме податке.
Многе уграђене функције у Пајтону примају итерабилне аргументе и враћају итераторе. На пример, функција map
прима функцију и итерирајућу вредност, а враћа итератор кроз резултате примене функције из аргумента на сваки члан у итерабилном аргументу.
>>> велика = map(lambda x: x.upper(), ВдоМ)
>>> next(велика)
'В'
>>> велика.__next__()
'Г'
>>> next(велика)
'Д'
Генератори и yield наредбе¶
Горе наведени објекти Слова
и Позитиви
захтевају да се у објект уведе ново поље налик на self.тренутно
како би се испратио напредак кроз секвенцу. У једноставним секвенцама, попут малопређашње приказаних, то се може лако учинити. Међутим, код сложених секвенци, методи __next__
може бити прилично тешко да сачува своје тренутно место у одређеном прорачуну. Генератори омогућавају да се дефинишу сложеније итерације над произвољним секвенцама, па чак и оним бесконачним, користећи се својствима Пајтоновог интерпретатора.
Генератор је итератор који је повратна вредност посебне класе функција под називом генераторска функција. Генераторске функције се разликују од обичних функција по томе што уместо наредбе return
у свом телу користе наредбу yield
како би вратиле поједине чланове секвенце.
Генератори не користе атрибуте објекта да би пратили свој напредак кроз секвенцу. Уместо тога, они управљају извршавањем генераторске функције, која се извршава све док се не дође до следеће yield
наредбе сваки пут када је next
позвана над генератором. На пример, доња функција генераторСлова
враћа генератор кроз слова а, б, в, и на крају г.
>>> def генераторСлова():
... тренутно = 'а'
... while тренутно <= 'г':
... yield тренутно
... тренутно = chr(ord(тренутно)+1)
>>> for слово in генераторСлова():
... print(слово)
а
б
в
г
Наредба yield
указује на то да је дефинисана генераторска функција, а не обична функција. Приликом позива, генераторска функција не враћа одређену вредност из секвенце, већ generator
(који је врста итератора) који може да врати добијене вредности из секвенце. Позивањем next
над генератором наставља извршавање генераторске функције одакле год је претходно стала па све док се не изврши следећа yield
наредба.
Приликом првог next
позива, програм извршава наредбе унутар тела функције генераторСлова
све док не наиђе на yield
наредбу. Затим се зауставља и враћа вредност променљиве тренутно
. Наредбе yield
не уништавају новостворено окружење већ га чувају за касније. Приликом следећег next
позива, извршавање се наставља тамо где је и заустављено. Вредности повезане за име тренутно
и ма које друго повезано име у опсегу генераторСлова
чувају се и током наредних next
позива.
Кроз генератор се може пролазити и ручним позивањем next()
:
>>> слова = генераторСлова()
>>> type(слова)
<class 'generator'>
>>> next(слова)
'а'
>>> next(слова)
'б'
>>> next(слова)
'в'
>>> next(слова)
'г'
>>> next(слова)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Генератор не започиње са извршавањем било које наредбе из тела своје генераторске функције све док се први пут не позове next
. Генератор подиже StopIteration
изузетак кад год његова генераторска функција врати нешто (било преко return
наредбе, било None
када јој понестане наредби унутар тела).
Прављење итерирајућих вредности преко yield¶
У Пајтону, итератори праве само један пролаз кроз чланове основне секвенце. Након тог проласка, итератор ће наставити да подиже StopIteration
изузетак када се позове уграђена функција next
или __next__
метода. Многе апликације захтевају понављање чланова секвенце више пута. На пример, мора се много пута проћи кроз секвенцу како би се побројали сви могући парови чланова исте.
>>> def свиПарови(секвенца):
... for члан1 in секвенца:
... for члан2 in секвенца:
... yield (члан1, члан2)
>>> list(свиПарови([1, 2, 3]))
[(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3), (3, 1), (3, 2), (3, 3)]
Секвенце саме по себи нису итератори, већ су итерабилни објекти. Сучеље итерирајуће вредности, односно итерабилног објекта у Пајтону се састоји од једне поруке, __iter__
, која враћа итератор. Уграђени типови секвенци у Пајтону враћају нове инстанце итератора када се позове њихова __iter__
метода. Уколико итерабилни објекат врати свежу инстанцу итератора сваки пут када се __iter__
позове, тада се кроз њега може итерирати више пута.
Нове итерабилне класе могу се дефинисати имплементацијом сучеља итерирајућих вредности. На пример, итерабилна класа СловаПрекоYield
из наставка враћа нови итератор кроз слова сваки пут када се __iter__
позове.
>>> class СловаПрекоYield:
... def __init__(self, почетак='а', крај='д'):
... self.почетак = почетак
... self.крај = крај
... def __iter__(self):
... следећеСлово = self.почетак
... while следећеСлово < self.крај:
... yield следећеСлово
... следећеСлово = chr(ord(следећеСлово)+1)
Метода __iter__
је генераторска функција: враћа објекат генератора који даје слова од 'а'
до 'г'
, а затим се зауставља. Сваки пут када се позове ова метода, нови генератор започиње свеж пролаз кроз секвенцијалне податке.
>>> слова = СловаПрекоYield()
>>> list(свиПарови(слова))[:5]
[('а', 'а'), ('а', 'б'), ('а', 'в'), ('а', 'г'), ('б', 'а')]
Пајтон токови¶
Токови нуде још један начин имплицитног представљања секвенцијалних података. Ток је лењо израчуната уланчана листа. Попут класе Листа
из другог поглавља, инстанца Ток
одговара на захтеве за свој први
члан и остатак
тока. Као и код класе Листа
, остатак
класе Ток
је сам по себи такође Ток
. За разлику класе Листа
, остатак
класе Ток
израчунава се само када се погледа, уместо да се унапред ускладишти у меморији рачунара. Односно, остатак
тока се израчунава лењо.
Да би се постигло ово лењо вредновање, ток чува функцију која израчунава остатак тока. Кад год се ова функција позове, њена враћена вредност се кешира као део тока у атрибуту званом _остатак
, именованим подвлаком која треба да укаже на то да му не треба приступати директно.
Доступни атрибут остатак
је метода својства која враћа остатак тока, рачунајући га ако је потребно. Са оваквим дизајном, ток похрањује како израчунати остатак тока, уместо да остатак увек експлицитно складишти.
Ради прегледности ево најпре поновљене класе која дефинише уланчану листу, која је додуше нешто измењена у односу на ону која је представљена у другом поглављу.
>>> class Листа:
... """Уланчана листа."""
... празно = ()
... def __init__(self, први, остатак=празно):
... self.први = први
... self.остатак = остатак
... def __getitem__(self, i):
... if i == 0:
... return self.први
... else:
... return self.остатак[i-1]
... def __len__(self):
... return 1 + len(self.остатак)
... def __repr__(self):
... if self.остатак:
... нискаОстатка = ', ' + repr(self.остатак)
... else:
... нискаОстатка = ''
... return 'Листа({0}{1})'.format(self.први, нискаОстатка)
Затим ево и класе ток, која даје могућност лењог израчунавања остатка, само онда када је потребан.
>>> class Ток:
... """Лењо израчуната уланчана листа."""
... class празно:
... def __repr__(self):
... return 'Ток.празно'
... празно = празно()
... def __init__(self, први, израчунајОстатак=lambda: празно):
... assert callable(израчунајОстатак), 'израчунајОстатак мора бити callable.'
... self.први = први
... self._compute_rest = израчунајОстатак
... @property
... def остатак(self):
... """Враћа остатак тока, израчунавајући га ако је неопходно."""
... if self._compute_rest is not None:
... self._остатак = self._compute_rest()
... self._израчунајОстатак = None
... return self._остатак
... def __repr__(self):
... return 'Ток({0}, <...>)'.format(repr(self.први))
Уланчана листа се дефинише помоћу угнежђеног израза. На пример, можемо се створити Листа
која представља чланове 1 па 5 на следећи начин:
>>> л = Листа(1, Листа(2+3, Листа(9)))
Исто тако, може се створити и Ток
који представља исту серију. Ток
заправо не израчунава други члан 5 све док остатак тока не буде затражен. Овај ефекат постиже се стварањем анонимних функција.
>>> т = Ток(1, lambda: Ток(2+3, lambda: Ток(9)))
Овде је 1 први члан тока, а lambda
израз који следи враћа функцију за израчунавање остатка тока.
Приступ члановима уланчане листе л
и тока т
одвија се слично. Међутим, док је 5 ускладиштено у л
, код т
се израчунава на захтев преко додавања, први пут када је затражено.
>>> л.први
1
>>> т.први
1
>>> л.остатак.први
5
>>> т.остатак.први
5
>>> л.остатак
Листа(5, Листа(9))
>>> т.остатак
Ток(5, <...>)
Док је остатак
од л
двочлана уланчана листа, остатак
од т
укључује функцију за израчунавање остатка, а чињеница да ће можда вратити празан ток још увек није откривена.
Када је направљена инстанца Ток
, поље self._остатак
је None
, што значи да остатак Ток
-а још увек није израчунат. Када се атрибут остатак
затражи помоћу тачканог израза, позива се метода својства остатак
која покреће израчунавање са self._остатак = self._compute_rest()
. Због механизма кеширања унутар класе Ток
, функција compute_rest
се позива само једном, а затим одбацује.
Основна својства функције compute_rest
су да не узима никакве аргументе и да враћа Ток
или Ток.празно
.
Лења вредновања дају могућност представљања бесконачних секвенцијалних скупова података токове. На пример, могу се представити растуће целобројне вредности, почевши од било ког броја који се усвоји као први
.
>>> def целобројниТок(први):
... def израчунајОстатак():
... return целобројниТок(први+1)
... return Ток(први, израчунајОстатак)
>>> позитивни = целобројниТок(1)
>>> позитивни
Ток(1, <...>)
>>> позитивни.први
1
Када се целобројниТок
позове по први пут, враћа ток чији је први
прва целобројна вредност у секвенци. Међутим, целобројниТок
је заправо рекурзивна функција, јер израчунајОстатак
од овог тока позива целобројниТок
поново, са увећаним аргументом. Каже се да је целобројниТок
лењ јер се рекурзивни целобројниТок
позив извршава само кад се затражи остатак
целобројног тока.
>>> позитивни.први
1
>>> позитивни.остатак.први
2
>>> позитивни.остатак.остатак
Ток(3, <...>)
Исте функције вишег реда које манипулишу секвенцама – map
и filter
– такође се примењују на токове, мада се њихове имплементације морају мењати како би лењо примењивале своје функције аргумената. Функција мапТок
примењује функцију пресликавања кроз читав ток, тиме стварајући нови ток. Локално дефинисана функција израчунајОстатак
осигурава да ће се функција пресликати на остатак тока кад год се израчуна остатак.
>>> def mapТок(фја, т):
... if т is Ток.празно:
... return т
... def израчунајОстатак():
... return mapТок(фја, т.остатак)
... return Ток(фја(т.први), израчунајОстатак)
Ток се може филтрирати дефинисањем функције израчунајОстатак
која примењује функцију филтера на остатак тока. Уколико функција филтера одбије први члан тока, остатак се израчунава одмах. Будући да је filterТок
рекурзивна функција, остатак се може израчунати више пута све док се не пронађе важећи први
члан.
>>> def filterТок(фја, т):
... if т is Ток.празно:
... return т
... def израчунајОстатак():
... return filterТок(фја, т.остатак)
... if фја(т.први):
... return Ток(т.први, израчунајОстатак)
... else:
... return израчунајОстатак()
Функције mapТок
и filterТок
показују заједнички образац у обради тока: локално дефинисана функција израчунајОстатак
рекурзивно примењује функцију обраде на остатак тока кад год се остатак израчунава.
Да би се прегледао садржај тока, може се извршити претварање до првих k
чланова у Пајтонов низ, односно уграђени тип list
.
>>> def првихk(т, k):
... низk = []
... while т is not Ток.празно and k > 0:
... низk.append(т.први)
... т, k = т.остатак, k-1
... return низk
Ова погодна функција омогућава верификацију имплементације mapТок
једноставним примером који квадрира природне бројеве од 3 до 7.
>>> т = целобројниТок(3)
>>> т
Ток(3, <...>)
>>> м = mapТок(lambda x: x*x, т)
>>> м
Ток(9, <...>)
>>> првихk(м, 5)
[9, 16, 25, 36, 49]
Користећи функцију filterТок
може се дефинисати ток простих бројева помоћу такозваног Ератостеновог сита, које филтрира ток целих бројева како би уклонио све бројеве који су умношци његовог првог члана. Узастопним филтрирањем са сваким простим бројем, сви сложени бројеви уклањају се из тока.
>>> def прости(природниТок):
... def недељив(x):
... return x % природниТок.први != 0
... def израчунајОстатак():
... return прости(filterТок(недељив, природниТок.остатак))
... return Ток(природниТок.први, израчунајОстатак)
Једноставним скраћивањем тока прости
, може се побројати било који префикс простих бројева.
>>> простиБројеви = прости(целобројниТок(2))
>>> првихk(простиБројеви, 12)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]
Токови се разликују од итератора тако што се могу више пута проследити чистим функцијама и сваки пут донети исти резултат. Ток простих бројева се не „троши” претварањем у низ. Односно, први
члан тока простиБројеви
је и даље 2 чак и након претварања префикса тока у низ.
>>> простиБројеви.први
2
Као што уланчане листе пружају једноставну имплементацију апстракције секвенце, токови пружају једноставну, функционалну, рекурзивну структуру података која имплементира лењо вредновање употребом функција вишег реда.