Iterations over containers are what digital computers do much better than human beings and they make it possible to find a specific entry from an encyclopedia in a fraction of second. Arguably, a key to write any useful program is providing good supports for iterations and it is of utmost importance for a language is to grok(thoroughly understand and absorb) iterations, which Python seems to have succeeded to come off as a great example of.
Before getting into details of Python's features let's first see another example well known for its elegant support for iterations over containers, The C++ Standard Template Library(STL). When it appeared it was welcome by many programmers because it clearly defined four orthogonal components, which are algorithms, containers, functions and iterators, and made it possible to handle arbitrary data structures with only a small set of APIs.
int main()
{
std::vector<int> v = {7, 5, 16, 8};
for (vector<int>::iterator iter = v.begin(); iter != v.end(); iter++)
std::cout << (*iter) << '\\n';
}
// or in C++11 you can do simply
// for(int n : v) {
// std::cout << n << '\\n';
// }
}
In the example above, the vector was iterated over using an iterator. You can do the same thing with other container types in STL such as stack
, set
and map
. STL offers almost identical interfaces for different containers so that you don't have to memorize container specific methods or functions. Just imagine how complicated it would be to access each value of a string-keyed map data structure without STL. Compared to iterating over a container using integer indexes, which probably the most common iteration method still, having an iterator, separate from the original container, is applicable in more general situations where the container may not have any ordering and, therefore, cannot be accessed with indexes starting from 0. Python's iter()
is powerful in the same sense.
for num in myset:
print(num)
This interface can be used for any other container types in Python whether it's a list, tuple, dict or set. Even generators can utilize the interface if you know what they are. This is possible because you can always get an iterator from any kind of container using iter()
. Let's see how to manually iterate over a container using iter()
to understand the internals of for loop, list comprehension and any other iterations in Python.
myset = {2,3,1}
it = iter(myset)
print(next(it))
print(next(it))
print(next(it))
Many python programmer may know what iter()
is and you may not need to use it on daily bases. But it can be useful in some occasions. Here is a coroutine example using iter()
from itertools import cycle
def Items(nums, alphas):
nit = iter(cycle(nums))
ait = iter(cycle(alphas))
cur = yield
while True:
if cur == 'num':
cur = yield next(nit)
elif cur == 'alpha':
cur = yield next(ait)
else:
cur = yield None
items = Items(range(4), 'ab')
next(items)
print(items.send('num'), items.send('num'),
items.send('alpha'), items.send('alpha'),
items.send('num'), items.send('num'))
# output
0 1 a b 2 3
I prefer to use CapWords names for generator and coroutine functions and lower case names for actual generators and coroutines just like how you name classes and instances. The rationale is that one with a CapWords name is what holds the code to be run and one with lower case name is what actually runs the code and store the state.
The pattern observed in the above example can be useful in OS or networking applications. Using iter()
also can be useful when you want to avoid nested blocks as much as possible, for example, when you debug a program within an interactive debugger.
Back to the point, even if it's true that python's iter()
is cool as I asserted, many languages have been adopting similar features and you may question what is so great about python's in our time. Well, what I'm most appreciating about iter()
is not just that it is one way to iterate over any container, but that it is the only way to iterate over any container in the language. This kind of uniformity is rarely observed in other languages.