Iterators
Iterators are objects which can be iterated over (looped through, etc.). Strings, lists, dictionaries, generators, and any other type which provides an interface for iteration (see Classes) can be converted to iterators, and effectively are converted automatically in many scenarios.
string = "abcdef"
for ch in string:
print(ch, end='')
In the above case, the string variable is acting as an iterator.
Generators
Generators "generate" a new value every time they're run, and so function as iterators. This can be helpful when holding an entire list of values in memory is not feasible.
def infinity():
num = 0
while True:
yield num
num += 1
for i in infinity():
if i <= 500000:
print(i, end='\r')
else:
break
This will print every number from 0 to 500000, with each line replacing the last. infinity() is effectively an infinite set.
Obviously, instead of generating numbers from 1-500000, a function could return a list of values containing that large range of numbers; something like:
nums = list(range(0, 500000))
But this requires storing each number individually in memory, which for larger sets of data can become problematic. range() itself acts a bit like a generator, although in reality it is of its own class of objects.
nums = range(0, 500000)
This means that working with a range() object is almost always better than working with a static list of numbers.
nums = (num**2 for num in range(10))
Generator expressions are created in the same way as List Comprehensions, but they create generators instead of lists.
print(next(nums))
print(next(nums))
25
36
Any iterator object (or object which has implemented __next__()) can use next() to travel through each value; to explicitly transform a sequence into an iterator, use iter(). This is essentially what happens during a for-loop, but done implicitly.
Map
Mappers can be used to automatically perform operations on iterable objects and return a new copy, without actually having to manually loop through them. map() returns a map object, is both iterable and an iterator, but is not a sequence.
stuff = ['a', 'b', 'c']
ascii_stuff = map(ord, stuff) # Convert everything to its ascii value
print(*ascii_stuff)
97 98 99
Additionally, map objects can be transformed into lists or other kinds of sequences:
ascii_stuff = list(map(ord, stuff))
print(ascii_stuff)
[97, 98, 99]
When combined with lambda functions, this can be used to do the same thing as comprehensions, but with different syntax.
string = "mississippi"
capital_string = "".join([ch.upper() for ch in string])
print(capital_string)
MISSISSIPPI
string = "missouri"
capital_string = "".join(map(lambda ch: ch.upper(), string))
print(capital_string)
MISSOURI
Of course, the previous two examples are overkill in the sense that the same results could be achieved by doing string.upper() by itself— but if for some you reason wanted to change each letter into its uppercase version one by one, maps and comprehensions are both viable methods.
Filter
string = "aardvark"
string_no_vowels = "".join(filter(lambda ch: ch not in "aeiou", string))
print(string_no_vowels)
rdvrk
filter() passes each value in an iterable through a function— if that function returns True, it keeps that data in the iterable— if False, it deletes that data from the iterable.
It can be handled more or less identically to map(), but with a different purpose.
Mastery of iteration concepts in Python is not exactly paramount to effectively use the language, but for many developers, mapping functions and generators will likely provide more preferable ways to perform a plethora of tasks.