| Understanding Iterables |
|
an iterable is any object capable of returning its members one at a time can be iterated over in a loop an object is considered iterable if it meets one of the following conditions
dictionaries naturally iterate over their keys Python provides ways to iterate over values or key-value pairs of the dictionary as well |
| Decoding Iterators |
|
iterators enable the process of iteration are objects that track the current position during iteration and proceed to the next element when prompted to be classified as an iterator an object must implement
including functions range, filter, map, and enumerate iterators vary in their operation some generating values on the fly and others applying a function to each item in an iterable |
| Implementing Iterators and Iterables |
|
custom iterable with __getitem__
all iterable objects have inherent iterator capabilitieswhen iterating over index-based iterable objects the process is based on the object's length Dictionaries use their keys iterable for iteration functioning similarly to other index-based iterable objects a simple iterable object that allows for index-based access to its elements class NumberIterable:
def __init__(self, *numbers):
self.numbers = numbers
def __getitem__(self, idx: int) -> int:
if idx < len(self.numbers):
return self.numbers[idx]
raise IndexError("list index out of range")
numbers = NumberIterable(1, 2, 3, 4, 5)
for number in numbers:
print(number)
can also create iterators from iterables by using the iter function
iterator = iter([1, 2, 3, 4, 5])
print(iterator)
for i in iterator:
print(i)
building an iterator with __next__
class Range:
def __init__(self, start: int, end: int, step: int = 1):
self.start = start
self.end = end
self.step = step
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current > self.end:
raise StopIteration
current = self.current
self.current += self.step
return current
my_range = Range(0, 10)
first = next(my_range)
print(f"first: {first}")
second = next(my_range)
print(f"second: {second}")
for item in my_range:
print(item)
the Range class mimics built-in range functiongenerates numbers within a specified range by implementing the __iter__ and __next__ methods the type can yield the generated numbers This does not depend on an external iterable, instead generating them from an internal state the next function can then be used to manually progress through an iterator allows for the provision of a default value to be returned in case the iterator is depleted |
| The Power of Generators |
|
generators offer a streamlined way to create iterators by using the yield keyword, functions are converted into generators the underlying process is the same each iteration calls the generator's __next__ method and returns the result def my_generator(n):
# initialize counter
value = 0
# loop until counter is less than n
while value < n:
# produce the current value of the counter
yield value
# increment the counter
value += 1
# iterate over the generator object produced by my_generator
for value in my_generator(3):
# print each value produced by generator
print(value)
generators are valuable for their ability to be lazily evaluatedsimplifies the creation of iterators and iterables achieve this by relinquishing control back to their caller upon reaching the yield keyword effectively pauses execution until prompted to continue |
| Comprehending Comprehensions |
|
another approach to generating and utilizing iterators and iterables is through the use of comprehension expressions these expressions have a syntax that closely resembles the objects they generate, with the exception of generators List Comprehension
even_numbers = [i for i in filter(lambda x: x % 2 == 0, range(10))] print(even_numbers)produced an iterable (even_numbers) by linking iterators together comprehension expressions leverage the capability of iterators to be chained in a clear and readable manner syntax below produces a tuple generator_expression = (i for i in filter(lambda x: x % 2 == 0, range(10))) print(generator_expression) # unpack the tuple print(*generator_expression) |