The payoff of the Iterator pattern is that work happens only when items are actually pulled. A well-built Cursor never touches the backend until you ask for the first item, and it fetches exactly the pages it needs, no more.
PagedSource counts every call in self.fetches, so we can prove it. Add take(n) to Cursor (same idea as ListCursor.take) so we can consume a bounded number of items, then watch the counter: constructing a Cursor should fetch zero pages, taking one item should fetch one, and reaching across a page boundary should fetch exactly two.
class ListCursor:
def __init__(self, items):
self._items = list(items)
self._index = 0
def __iter__(self):
return self
def __next__(self):
if self._index >= len(self._items):
raise StopIteration
value = self._items[self._index]
self._index += 1
return value
def take(self, n):
result = []
for _ in range(n):
try:
result.append(next(self))
except StopIteration:
break
return result
class PagedSource:
def __init__(self, items, page_size):
self.items = list(items)
self.page_size = page_size
self.fetches = 0
def page(self, index):
self.fetches += 1
start = index * self.page_size
return self.items[start:start + self.page_size]
class Cursor:
def __init__(self, source):
self.source = source
self._page_index = 0
self._buffer = []
self._pos = 0
self._done = False
def __iter__(self):
return self
def __next__(self):
while self._pos >= len(self._buffer):
if self._done:
raise StopIteration
page = self.source.page(self._page_index)
self._page_index += 1
if not page:
self._done = True
raise StopIteration
self._buffer = page
self._pos = 0
value = self._buffer[self._pos]
self._pos += 1
return value
def take(self, n):
"""Pull up to n items from the paged cursor without fetching extra pages."""
# TODO: the same bounded pull as ListCursor.take, only now every value
# comes through the paging __next__ above. Ask for at most n items,
# gather them into a list, and stop early once the cursor reports it is
# exhausted. Never look ahead: source.fetches must rise only when the
# buffer is genuinely empty and an item has actually been requested.
pass
The editor and the test runner need a wide screen. Open this page on a desktop browser and your progress will be waiting.
The payoff of the Iterator pattern is that work happens only when items are actually pulled. A well-built Cursor never touches the backend until you ask for the first item, and it fetches exactly the pages it needs, no more.
PagedSource counts every call in self.fetches, so we can prove it. Add take(n) to Cursor (same idea as ListCursor.take) so we can consume a bounded number of items, then watch the counter: constructing a Cursor should fetch zero pages, taking one item should fetch one, and reaching across a page boundary should fetch exactly two.
Press Run Tests or ββ© to check your solution.