Composite design

The common mathematical notation for a composite function looks as follows:

 

The idea is that we can define a new function, , that combines two other functions,  and .

Python's multiple-line definition of a composition function can be done through the following code:

@f_deco
def g(x):
    something  

The resulting function can be essentially equivalent to  . The f_deco() decorator must define and return the composite function by merging an internal definition of f() with the provided g()

The implementation details show that Python actually provides a slightly more complex kind of composition. The structure of a wrapper makes it helpful to think of Python decorator composition as follows:

A decorator applied to some application function, , will include a wrapper function, , that has two parts. One portion of the wrapper,  , applies to the arguments of the wrapped function, , and the other portion,  , applies to the result of the wrapped function.

Here's a more concrete idea, shown as a  something_wrapper() decorator definition:

@wraps(argument_function)
def something_wrapper(*args, **kw):
    # The "before" part, w_α, applied to *args or **kw
    result = argument_function(*args, **kw)
    # The "after" part, w_β, applied to the result
return after_result

This shows the places to inject additional processing before and after the original function. This emphasizes an important distinction between the abstract concept of functional composition and the Python implementation: it's possible that a decorator can create either , or , or a more complex . The syntax of decoration doesn't fully describe which kind of composition will be created.

The real value of decorators stems from the way any Python statement can be used in the wrapping function. A decorator can use  if or for statements to transform a function into something used conditionally or iteratively. In the next section, the examples will leverage the try statement to perform an operation with a standard recovery from bad data. There are many clever things that can be done within this general framework.

A great deal of functional programming follows the  design pattern. Defining a composite from two smaller functions isn't always helpful. In some cases, it can be more informative to keep the two functions separate. In other cases, however, we may want to create a composite function to summarize processing.

It's very easy to create composites of the common higher-order functions, such as map(), filter(), and reduce(). Because these functions are simple, a composite function is often easy to describe, and helps make the programming more expressive.

For example, an application may include map(f, map(g, x)). It may be more clear to create a composite function and use a map(f_g, x) expression to describe applying a composite to a collection. Using f_g = lambda x: f(g(x)) can often help explain a complex application as a composition of simpler functions.

It's important to note that there's no real performance advantage to either technique. The map() function is lazy: with two map() functions, one item will be taken from x, processed by the g() function, and then processed by the f() function. With a single map() function, an item will be taken from x and then processed by the f_g() composite function.

In Chapter 14, The PyMonad Library, we'll look at an alternative approach to this problem of creating composite functions from individual curried functions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset