Online Book Reader

Home Category

Beautiful Code [234]

By Root 5215 0
data pointer. Therefore, if every N-dimensional array in NumPy were contiguous, discussing iterators would be rather uninteresting.

The beauty of the iterator abstraction is that it allows us to think about processing and manipulating noncontiguous arrays with the same ease as contiguous arrays. Noncontiguous arrays arise in NumPy because an array can be created that is a "view" of some other contiguous memory area. This new array may not itself be contiguous.

For example, consider a three-dimensional array, a, that is contiguous in memory. With NumPy, you can create another array consisting of a subset of this larger array using Python's slicing notation. Thus, the statement b=a[::2, 3:, 1::3] returns another NumPy array consisting of every other element in the first dimension, all elements starting at the fourth element (with zero-based indexing) in the second dimension, and every third element starting at the second element in the third dimension. This new array is not a copy of the memory at those locations; it is a view of the original array and shares memory with it. But this new array cannot be represented as a contiguous chunk of memory.

A two-dimensional illustration should further drive home the point. Figure 19-1 shows a contiguous, two-dimensional, 4 x 5 array with memory locations labeled from 1 through 20. Above the representation of the 4 x 5 array is a linear representation of the memory for the array as the computer might see it. If a represents the full memory block, b=a[1:3, 1:4] represents the shaded region (memory locations 7, 8, 9, 12, 13, and 14). As emphasized in the linear representation, these memory locations are not contiguous.

Figure 19-1. A two-dimensional array slice and its linear representation in memory

NumPy's general memory model for an N-dimensional array supports the creation of these kinds of noncontiguous views of arrays. It is made possible by attaching to the array a sequence of integers that represent the values for the "striding" through each dimension.

The stride value for a particular dimension specifies how many bytes must be skipped to get from one element of the array to another along the associated dimension, or axis. This stride value can even be negative, indicating that the next element in the array is obtained by moving backward in memory. The extra complication of the (potentially) arbitrary striding means that constructing an iterator to handle the generic case is more difficult.

Multidimensional Iterators in NumPy > NumPy Iterator Origins

19.3. NumPy Iterator Origins

Loops that traverse all the elements of an array in compiled code are an essential feature that NumPy offers the Python programmer. The use of an iterator makes it relatively easy to write these loops in a straightforward and readable way that works for the most general (arbitrarily strided) arrays supported by NumPy. The iterator abstraction is an example in my mind of beautiful code because it allows simple expression of a simple idea, even though the underlying implementation details might actually be complicated. This kind of beautiful code does not just drop into existence, but it is often the result of repeated attempts to solve a set of similar problems until a general solution crystallizes.

My first attempt at writing a general-purpose, N-dimensional looping construct occurred around 1997 when I was trying to write code for both an N-dimensional convolution and a general-purpose arraymap that would perform a Python function on every element of an N-dimensional array.

The solution I arrived at then (though not formalized as an iterator) was to keep track of a C array of integers as indices for the N-dimensional array. Iteration meant incrementing this N-index counter with special code to wrap the counter back to zero and increment the next counter by 1 when the index reached the size of the array in a particular dimension.

While writing NumPy eight years later, I became more aware of the concept of, and use of, iterator objects in Python. I thus considered adapting the arraymap

Return Main Page Previous Page Next Page

®Online Book Reader