## 1.4 Composability 

As you work on a functional-style program, you’ll write a number of functions with varying inputs and outputs. Some of these functions will be unavoidably specialized to a particular application, but others will be useful in a wide variety of programs. For example, a function that takes a directory path and returns all the XML files in the directory, or a function that takes a filename and returns its contents, can be applied to many different situations. 

Over time you’ll form a personal library of utilities. Often you’ll assemble new programs by arranging existing functions in a new configuration and writing a few functions specialized for the current task. 

## 2 Iterators 

I’ll start by looking at a Python language feature that’s an important foundation for writing functional-style programs: iterators. 

An iterator is an object representing a stream of data; this object returns the data one element at a time. A Python iterator must support a method called$_{__next__()}$ that takes no arguments and always returns the next element of the stream. If there are no more elements in the stream,$_{__next__()}$ must raise the$_{StopIteration}$exception. Iterators don’t have to be finite, though; it’s perfectly reasonable to write an iterator that produces an infinite stream of data. 

The built-in$_{iter()}$ function takes an arbitrary object and tries to return an iterator that will return the object’s contents or elements, raising$_{TypeError}$ if the object doesn’t support iteration. Several of Python’s built-in data types support iteration, the most common being lists and dictionaries. An object is called iterable if you can get an iterator for it. 

You can experiment with the iteration interface manually: 

>>> L =[1,2,3] >>> it =iter(L) >>> it <...iterator object at ...> >>> it.__next__ () # same as next(it) 1 >>> next(it) 2 >>> next(it) 3 >>> next(it) Traceback (most recent call last): File "<stdin>" , line1, in <module> StopIteration >>> 

Python expects iterable objects in several different contexts, the most important being the$_{for}$ statement. In the statement$_{for X in Y}$ , Y must be an iterator or some object for which$_{iter()}$ can create an iterator. These two statements are equivalent: 

for i in iter(obj): print(i) for i in obj: print(i) 

Iterators can be materialized as lists or tuples by using the$_{list()}$ or$_{tuple()}$ constructor functions: 

>>> L =[1,2,3] >>> iterator =iter(L) >>> t =tuple(iterator) >>> t (1, 2, 3) 

