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 function generates 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 evaluated simplifies 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) |