Some Aspects of Generic Programming

Một phần của tài liệu Python scripting for computational science (Trang 453 - 457)

C++ programmers often findgeneric programming attractive. This is a spe- cial programming style, supported bytemplates in C++, which helps to pa- rameterize the code. A problem can often be solved by both object-oriented and generic programming, but normally the version based on generic pro- gramming is computationally more efficient since templates perform a pa- rameterization known at compile time, whereas object-oriented programming leaves the parameterization to run time. With generic programming it is also easier to separate algorithms and data structures than in object-oriented programming, often implying that the code becomes more reusable.

It is instructive to see how Python supports the style of generic program- ming, without any template construct. This will demonstrate the ease and power of dynamically typed languages, especially when compared to C++.

The material in this section assumes that the reader is familiar with C++, templates, and generic programming.

Templates are mainly used in two occasions: to parameterize arguments and return values in functions and to parameterize class dependence. In Python there is no need to parameterize function arguments and return val- ues, as neither of these variables are explicitly typed. Consider a function for computing a matrix-vector producty=Ax. The C++ code for carrying out this task could be implemented as follows17:

template <class Vec, class Mat>

void matvec(const Vec& x, const Mat& A, Vec& y) {

...

y = ...

}

17 The resultyis passed as argument to avoid internal allocation ofyand copying in areturnstatement.

8.9. Iterators 433 Thematvec function can be called with all sorts of matrix and vector types as long as the statements in the body ofmatvecmake sense with these types.

The specific types used in the calls must be known at compile time, and the compiler will generate different versions of thematveccode depending on the types involved.

The similar Python code will typically treat the result y as a return value18:

def matvec(x, A):

y = ...

return y

Since the type of x,A, and yare not specified explicitly, the function works for all types that can be accepted by the statements inside the function.

Parameterization of classes through templates is slightly more involved.

Consider a class A that may have a data member of type X, Y, or Z (these types are implemented as classes). In object-oriented programming we would typically deriveX,Y, orZfrom a common base class, sayB, and then work with aBpointer inA. At run time one can bind this pointer to any object of type X,Y, orZ. This means that the compiler has no idea about what theBpointer actually points to and can therefore make no special optimizations. With templates in C++, one would parameterize class A in terms of a template (say)T:

<template typename T>

class A { ...

T data;

...

};

At compile time, the type of Tmust be known. Writing A<Grid>in the code forces the compiler to generate a version of class A with T replaced by the specific type Grid. Special features ofGrid (e.g., small inline functions) can be actively used by the compiler to generate more efficient code. Macros in C can be used to simulate the same effect.

The Python equivalent to the C++ class A would be to provide a class name Tas argument to the constructor, e.g.,

class A:

def __init__(self, T):

self.data = T()

A statementa = A(X) then instantiates an instancea of classA, with an at- tribute data holding a reference to an object of type X. Since there is no compilation to machine code, there is neither any efficiency gain. This alter- native is equally efficient,

18 The resultyis allocated inside the function, but all arrays and lists in Python are represented by references, so when we returny, we only copy a reference out of the function. Some C++ libraries also work with references in this way.

class A:

def __init__(self, T):

self.data = T a = A(X())

Instead of sending the class name Xto A’s constructor, we send an instance of class X, i.e., we instantiate the right object outside rather than inside the constructor.

The Standard Template Library (STL) in C++ has set standards for generic programming [32]. Typically, algorithms and data structures are sep- arated, in contrast to the object-oriented programming style where data and algorithms are normally tightly coupled within an object. A particular fea- ture of STL is the standard for iterating over data structures: traditionalfor loops with an index counter and subscripting operations are replaced byfor loops involving iterators. Suppose we have a classAcontaining a sequence of data. ClassAis often equipped with a local class,A::Iterator, for pointing to elements in the sequence of data. For instance,Amay implement a vector in terms of a plain C array, andA::Iteratoris then simply aT*pointer, where Tis the type of each element in the vector. ClassAoffers two methods,begin and end, which return an iterator for the beginning and one item past the end of the data structure, respectively. The iterator contains an operator++

function, which updates the iterator to point to the next element in the se- quence. Applying a function fto each element in an object a of typeA can be implemented as

A::Iterator i;

for (i = a.begin(); i != a.end(); ++i) {

*i = f(*i); // apply function f to every element }

A copy function will typically be parameterized by the type of the iterators, e.g.,

template< typename In_iter, typename Out_iter >

Out_iter copy(In_iter first, In_iter last, Out_iter result) { while (first != last) {

*result = *first;

result++; first++;

}

return result;

}

A sample code callingcopylooks like

copy(somedata.begin(), somedata.end(), copied_data.begin());

Python iterators support, to a large extent, this style of programming:

the begin function corresponds to__iter__, the StopIteration exception is a counterpart to the end function, and next corresponds to the operator++

8.9. Iterators 435 function. The iterator object in C++ is used as a kind of generalized pointer to the elements, while the iterator object in Python only provides a next method and can therefore coincide with the object holding the whole data sequence (i.e., the Python iterator object does not need to return an object of its own type from next). At first sight, Python iterators imply that the start and stop elements in the forloop must be fixed. However, a class can let__iter__return different objects depending on the state, cf. the different iterators in classGrid2Dit in Chapter 8.9.2.

On the other hand, implementing our two previous iteration examples from C++ using Python iterators is not straightforward. Both examples in- volvein-place modifications of a data structure. Afor loop like

for item in data:

<process item>

do not allow modification ofdata by modifyingitem (item is a reference to an element indata, but assigning a new value toitemjust makesitem refer another object, cf. page 87). In Python we would typically write

for i in range(len(a)):

a[i] = f(a[i])

# or

a = [f(x) for x in a]

import numpy

def copy(a, a_slice=None):

if isinstance(a, numpy.ndarray):

# slicing in NumPy arrays does not copy data if slice is None: return a.copy()

else: return a.copy()[a_slice]

elif isinstance(a, (list, tuple)):

if slice is None: return a[:]

else: return a[a_slice]

elif isinstance(a, dict):

return a.copy() # a_slice has no meaning b = copy(a)

b = copy(a, slice(1,-1,1)) # copies a[1:-1]

Note that copying a standard list/tuple and a NumPy array applies different syntax so we test on a’s type. Copying just a slice of a can be done by specifying aa_sliceargument. The value of this argument is asliceobject or a tuple ofsliceobjects, see page 391 for information onsliceobjects.

The bottom line is that one can mimic generic programming in Python, because class names are handled as any other variables. However, with iter- ators there is a major difference between Python and C++ if the loop is to be used to perform in-place modifications of data structures.

As a final remark, we mention that the difference between generic and object-oriented programming in Python is much smaller than in C++ because Python variables are not declared with a specific type.

Một phần của tài liệu Python scripting for computational science (Trang 453 - 457)

Tải bản đầy đủ (PDF)

(769 trang)