We can iterate over sequences, sets and dictionaries in multiple ways. Coming from another language you might find it natural to do the iteration using a counter as follows:
P=[3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
#Find sum of all elements in P (demo only: we already have a 'sum' function)
s=0
for i in range(len(P)):
s+=P[i]
print 'Sum is',s
However, one Pythonic way of iterating over this list is:
s=0
for i in P:
s+=i
print 'Sum is',s
We can also iterate over dictionaries as:
d=dict(zip(['ek','do','teen'],[1,2,3]))
print 'The dictionary is:',d
print 'The following will print keys and values in the dictionary'
for k in d:
print k,d[k]
print 'This will also print keys and values in the dictionary'
for (k,v) in d.items():
print k,v
Note that, for dictionaries and sets, the order of iteration is arbitrary.
for
loop really work?¶Things we can iterate over like this are called iterables. Technically, iterable objects must implement either the iterator protocol (e.g., lists which have a __iter__
function that returns an iterator) or the sequence protocol (e.g., strings which provide a __getitem__(index)
function). Iterable objects can iterated over using iterators. An iterator is an object that allows iterations over it as it implements a next
function which, on demand, returns the next element. You can create an iterator by using the iter
function as shown below:
s='nishta dildar nishta'
print 'Does \'s\' have __iter__ function:','__iter__' in dir(s)
print 'Does \'s\' have __getitem__ function:','__getitem__' in dir(s)
iter_s=iter(s)
print 'type of iter_s is',type(iter_s)
print 'using the next() function:',iter_s.next()
print 'Again:',iter_s.next()
print 'Yet again:',iter_s.next()
The for loop, implicitly, calls iter()
on the to-loop-over sequence, and uses next()
calls on the result. What the for loop really needs is an iterator. You can also make your own iterators (and generators!). However, this is a little advanced for now.
Major reasons for using iterators are:
xrange
generates a lazy sequence which produces a single element at a time. This allows for lower memory usage and is faster in comparison to using range
which produces a list. For more details see: Python: range() vs. xrange() or this stockoverflow question. Python dictionaries also provide lazy iterators: iteritems()
, iterkeys()
and itervalues()
for iterms, keys and values, respectively.What's the big deal about mutable and immutable objects? They offer certain advantages: (usually) fixed and unchanged storage requirements, thread safety (since no one can change the object, all threads see the same thing), ease in handling multiple copies etc.
However, there is more to it.
If an object is immutable then its value based hash can be used as a key in a dictionary because there is no threat of it changing after creation. This allows O(1) time access to any element in a dictionary instead of searching and comparing-by-value which will take O(n) time. All immutable objects in Python are hashable. All hashable objects come with a __hash__()
function which returns the hash of that object. You can use the hash(obj)
function to find out the hash value of any object. If two objects have the same value then they must have the same hash but the converse, though very likely, isn't always true (try hash(-1)
and hash(-2)
to understand this!). To read more about this see: how python implements hash, python dictionary implementation and why mutable objects cannot be dictionary keys.