DekGenius.com
[ Team LiB ] Previous Section Next Section

14.7 Function Design Concepts

When you start using functions, you're faced with choices about how to glue components together—for instance, how to decompose a task into functions (cohesion), how functions should communicate (coupling), and so on. Some of this falls into the category of structured analysis and design. Here are a few general hints for Python beginners:

  • Coupling: use arguments for inputs and return for outputs. Generally, you should strive to make a function independent of things outside of it. Arguments and return statements are often the best ways to isolate external dependencies.

  • Coupling: use global variables only when truly necessary. Global variables (i.e., names in the enclosing module) are usually a poor way for functions to communicate. They can create dependencies and timing issues that make programs difficult to debug and change.

  • Coupling: don't change mutable arguments unless the caller expects it. Functions can also change parts of mutable objects passed in. But as with global variables, this implies lots of coupling between the caller and callee, which can make a function too specific and brittle.

  • Cohesion: each function should have a single, unified purpose. When designed well, each of your functions should do one thing—something you can summarize in a simple declarative sentence. If that sentence is very broad (e.g., "this function implements my whole program"), or contains lots of conjunctions (e.g., "this function gives employee raises and submits a pizza order"), you might want to think about splitting it into separate and simpler functions. Otherwise, there is no way to reuse the code behind the steps mixed together in such a function.

  • Size: each function should be relatively small. This naturally follows from the cohesion goal, but if your functions start spanning multiple pages on your display, it's probably time to split. Especially given that Python code is so concise to begin with, a function that grows long or deeply nested is often a symptom of design problems. Keep it simple, and keep it short.

Figure 14-1 summarizes the ways functions can talk to the outside world; inputs may come from items on the left side, and results may be sent out in any of the forms on the right. Some function designers usually only use arguments for inputs and return statements for outputs.

Figure 14-1. Function execution environment
figs/lpy2_1401.gif

There are plenty of exceptions, including Python's OOP support—as you'll see in Part VI, Python classes depend on changing a passed-in mutable object. Class functions set attributes of an automatically passed-in argument called self, to change per-object state information (e.g., self.name='bob'). Moreover, if classes are not used, global variables are often the best way for functions in modules to retain state between calls. Such side effects aren't dangerous if they're expected.

14.7.1 Functions Are Objects: Indirect Calls

Because Python functions are objects at runtime, you can write programs that process them generically. Function objects can be assigned, passed to other functions, stored in data structures, and so on, as if they were simple numbers or strings. We've seen some of these uses in earlier examples. Function objects happen to export a special operation—they can be called by listing arguments in parentheses after a function expression. But functions belong to the same general category as other objects.

For instance, there's really nothing special about the name used in a def statement: it's just a variable assigned in the current scope, as if it had appeared on the left of an = sign. After a def runs, the function name is a reference to an object; you can reassign that object to other names and call it through any reference—not just the original name:

>>> def echo(message):           # Echo assigned to a function object.
...     print message
...
>>> x = echo                     # Now x references it too.
>>> x('Hello world!')            # Call the object by adding (  ).
Hello world!

Since arguments are passed by assigning objects, it's just as easy to pass functions to other functions, as arguments; the callee may then call the passed-in function just by adding arguments in parentheses:

>>> def indirect(func, arg):
...     func(arg)                         # Call the object by adding (  ).
...
>>> indirect(echo, 'Hello jello!')        # Pass function to a function.
Hello jello!

You can even stuff function objects into data structures, as though they were integers or strings. Since Python compound types can contain any sort of object, there's no special case here either:

>>> schedule = [ (echo, 'Spam!'), (echo, 'Ham!') ]
>>> for (func, arg) in schedule:
...     func(arg)
...
Spam!
Ham!

This code simply steps through the schedule list, calling the echo function with one argument each time through. Python's lack of type declarations makes for an incredibly flexible programming language. Notice the tuple unpacking assignment in the for loop header, introduced in Chapter 8.

    [ Team LiB ] Previous Section Next Section