Possibilities with Sequences, sets and dictionaries

Iterate

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:

In [1]:
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
Sum is 1058

However, one Pythonic way of iterating over this list is:

In [2]:
s=0
for i in P:
    s+=i
print 'Sum is',s
Sum is 1058

We can also iterate over dictionaries as:

In [3]:
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
The dictionary is: {'teen': 3, 'do': 2, 'ek': 1}
The following will print keys and values in the dictionary
teen 3
do 2
ek 1
This will also print keys and values in the dictionary
teen 3
do 2
ek 1

Note that, for dictionaries and sets, the order of iteration is arbitrary.

How does the Python 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:

In [4]:
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()
Does 's' have __iter__ function: False
Does 's' have __getitem__ function: True
type of iter_s is <type 'iterator'>
using the next() function: n
Again: i
Yet again: s

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.

Why iterators?

Major reasons for using iterators are:

Why Mutable and Immutable objects?

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.

(c) Dr. Fayyaz ul Amir Afsar Minhas, DCIS, PIEAS, Pakistan.