In (new versions of) Python, iterator syntax is really just a very simple protocol. It's surprisingly simple, but I've recently found out how flexible that makes it. New iterators replace the lame, patchy, previous implementation, which had you use the random access indexing method __getitem__ (properly intended for the aΓ] syntax) to dupe the for loop, without ever really supporting random access.

An iterable object (which should really be called a preiterator, IMHO) is one which supports the __iter__ method. All this does is return an iterator. This is important. In one typical scenario, the iterable object is some sort of container, and the iterator (probably created transiently for the purposes of a for loop) just points into it. In another (e.g. xrange) the preiterator stores the parameters of some simple enumeration (say the start, stop, and skip of an arithmetic sequence), and the iterator implements some algorithm to traverse the specified values. This is sometimes a great improvement on generating the whole list...

An iterator, now, is just an object with methods __iter__ and next. On __iter__, the object is supposed to return itself. Although strange, this sometimes makes sense. This makes an unstepped iterator somewhat like an iterable object. More on this below. Besides, anything uniterable is un__iter__able (not to be confused with inutterable). The next method does all the work (such as it is): it returns the next value (so, really, it returns a value and steps). On a list, say, this could work by returning the value pointed to by some internal pointer, and then incrementing it. If next is called past the terminating element, the iterator raises a StopIteration exception.

You can use for to loop over any iterator, as it just does __iter__, and then nexts the iterator until it catches a StopIteration. So it's just
for x in MyIterableObject: whatever(x). Similarly, if you want to handle iterators yourself (you'd normally just hold on to preiterators, but maybe you want to fiddle with your own for-like loop, or write stuff to perform operations like concating and multiplying (pre)iterators) you just try nexting your iterators whenever they're needed, and when they raise StopIteration, you move on (for example, to the next iterator, if you're concatenating).

Want to restart an iterator? Just take the __iter__ from the preiterator which gave you it...

If you've somehow lost the original preiterator (probaby because you're a nitwit and didn't heed advice to iter your iterator only at the very moment you need to start iterating) you can still substitute your unstepped iterator, if you need to pass something to a function which starts by taking the __iter__ result...

One clever way to create iterators is using python's new generators. To my mind, these are really only useful if you're using an iteration from some external (e.g. C++) function or class wrapped in python, or if you wrote some complicated iteration loop in a function before iterators came along, and you want to start using an iterator.