Section 11.3. Creating Functions


11.3. Creating Functions

11.3.1. def Statement

Functions are created using the def statement, with a syntax like the following:

def function_name(arguments):     "function_documentation_string"     function_body_suite


The header line consists of the def keyword, the function name, and a set of arguments (if any). The remainder of the def clause consists of an optional but highly recommended documentation string and the required function body suite. We have seen many function declarations throughout this text, and here is another:

def helloSomeone(who):   'returns a salutory string customized with the input'   return "Hello " + str(who)


11.3.2. Declaration versus Definition

Some programming languages differentiate between function declarations and function definitions. A function declaration consists of providing the parser with the function name, and the names (and traditionally the types) of its arguments, without necessarily giving any lines of code for the function, which is usually referred to as the function definition.

In languages where there is a distinction, it is usually because the function definition may belong in a physically different location in the code from the function declaration. Python does not make a distinction between the two, as a function clause is made up of a declarative header line immediately followed by its defining suite.

11.3.3. Forward References

Like some other high-level languages, Python does not permit you to reference or call a function before it has been declared. We can try a few examples to illustrate this:

def foo():     print 'in foo()'     bar()


If we were to call foo() here, it would fail because bar() has not been declared yet:

>>> foo() in foo() Traceback (innermost last):   File "<stdin>", line 1, in ?   File "<stdin>", line 3, in foo NameError: bar


We will now define bar(), placing its declaration before foo() 's declaration:

def bar():     print 'in bar()' def foo():    print 'in foo()'    bar()


Now we can safely call foo() with no problems:

>>> foo() in foo() in bar()


In fact, we can even declare foo() before bar():

def foo():     print 'in foo()'     bar() def bar():     print 'in bar()'


Amazingly enough, this code still works fine with no forward referencing problems:

>>> foo() in foo() in bar()


This piece of code is fine because even though a call to bar() (from foo() ) appears before bar() 's definition, foo() itself is not called before bar() is declared. In other words, we declared foo(), then bar(), and then called foo(), but by that time, bar() existed already, so the call succeeds.

Notice that the output of foo() succeeded before the error came about. NameError is the exception that is always raised when any uninitialized identifiers are accessed.

11.3.4. Function Attributes

We will briefly discuss namespaces later on in this chapter, especially their relationship to variable scope. There will be a more in-depth treatment of namespaces in the next chapter; however, here we want to point out a basic feature of Python namespaces.

You get a free one with every Python module, class, and function. You can have a variable named x in modules foo and bar, but can use them in your current application upon importing both modules. So even though the same variable name is used in both modules, you are safe because the dotted attribute notation implies a separate namespace for both, i.e., there is no naming conflict in this snippet of code:

import foo, bar print foo.x + bar.x


Function attributes are another area of Python to use the dotted-attribute notation and have a namespace. (More on namespaces later on in this chapter as well as Chapter 12 on Python modules.)

def foo():     'foo() -- properly created doc string' def bar():     pass bar.__doc__ = 'Oops, forgot the doc str above' bar.version = 0.1


In foo() above, we create our documentation string as normal, e.g., the first unassigned string after the function declaration. When declaring bar(), we left everything out and just used the dotted-attribute notation to add its doc string as well as another attribute. We can then access the attributes freely. Below is an example with the interactive interpreter. (As you may already be aware, using the built-in function help() gives more of a pretty-printing format than just using the vanilla print of the __doc__ attribute, but you can use either one you wish.)

>>> help(foo) Help on function foo in module __main__: foo()     foo() -- properly created doc string >>> print bar.version 0.1 >>> print foo.__doc__ foo() -- properly created doc string >>> print bar.__doc__ Oops, forgot the doc str above


Notice how we can define the documentation string outside of the function declaration. Yet we can still access it at runtime just like normal. One thing that you cannot do, however, is get access to the attributes in the function declaration. In other words, there is no such thing as a "self" inside a function declaration so that you can make an assignment like __dict__['version'] = 0.1. The reason for this is because the function object has not even been created yet, but afterward you have the function object and can add to its dictionary in the way we described above ... another free namespace!

Function attributes were added to Python in 2.1, and you can read more about them in PEP 232.

11.3.5. Inner or Nested Functions

It is perfectly legitimate to create function (object)s inside other functions. That is the definition of an inner or nested function. Because Python now supports statically nested scoping (introduced in 2.1 but standard as of 2.2), inner functions are actually useful now. It made no sense for older versions of Python, which only supported the global and one local scope. So how does one create a nested function?

The (obvious) way to create an inner function is to define a function from within an outer function's definition (using the def keyword), as in:

def foo():     def bar():         print 'bar() called'     print 'foo() called'     bar() foo() bar()


If we stick this code in a module, say inner.py, and run it, we get the following output:

foo() called bar() called Traceback (most recent call last):   File "inner.py", line 11, in ?     bar() NameError: name 'bar' is not defined


One interesting aspect of inner functions is that they are wholly contained inside the outer function's scope (the places where you can access an object; more on scope later on in this chapter). If there are no outside references to bar(), it cannot be called from anywhere else except inside the outer function, hence the reason for the exception you see at the end of execution in the above code snippet.

Another way of creating a function object while inside a(nother) function is by using the lambda statement. We will cover this later on in Section 11.7.1.

Inner functions turn into something special called closures if the definition of an inner function contains a reference to an object defined in an outer function. (It can even be beyond the immediately enclosing outer function too.) We will learn more about closures coming up in Section 11.8.4. In the next section, we will introduce decorators, but the example application also includes a preview of a closure.

11.3.6. *Function (and Method) Decorators

The main motivation behind decorators came from Python object-oriented programming (OOP). Decorators are just "overlays" on top of function calls. These overlays are just additional calls that are applied when a function or method is declared.

The syntax for decorators uses a leading "at-sign" ( @ ) followed by the decorator function name and optional arguments. The line following the decorator declaration is the function being decorated, along with its optional arguments. It looks something like this:

@decorator(dec_opt_args) def func2Bdecorated(func_opt_args):     :


So how (and why) did this syntax come into being? What was the inspiration behind decorators? Well, when static and class methods were added to Python in 2.2, the idiom required to realize them was clumsy, confusing, and makes code less readable, i.e.,

class MyClass(object):     def staticFoo():        :     staticFoo = staticmethod(staticFoo)        :


(It was clearly stated for that release that this was not the final syntax anyway.) Within this class declaration, we define a method named staticFoo(). Now since this is intended to become a static method, we leave out the self argument, which is required for standard class methods, as you will see in Chapter 12. The staticmethod() built-in function is then used to "convert" the function into a static method, but note how "sloppy" it looks with def staticFoo() followed by staticFoo = staticmethod (staticFoo). With decorators, you can now replace that piece of code with the following:

class MyClass(object):     @staticmethod     def staticFoo():               :


Furthermore, decorators can be "stacked" like function calls, so here is a more general example with multiple decorators:

@deco2 @deco1 def func(arg1, arg2, ...): pass


This is equivalent to creating a composite function:

def func(arg1, arg2, ...): pass func = deco2(deco1(func))


Function composition in math is defined like this: (g · f)(x) = g(f(x)). For consistency in Python:

@g @f def foo():     :


... is the same as foo = g(f(foo)).

Decorators With and Without Arguments

Yes the syntax is slightly mind-bending at first, but once you are comfortable with it, the only twist on top of that is when you use decorators with arguments. Without arguments, a decorator like:

@deco def foo(): pass


... is pretty straightforward:

foo = deco(foo)


Function composition without arguments (as seen above) follows. However, a decorator decomaker() with arguments:

@decomaker(deco_args) def foo(): pass


... needs to itself return a decorator that takes the function as an argument. In other words, decomaker() does something with deco_args and returns a function object that is a decorator that takes foo as its argument. To put it simply:

foo = decomaker(deco_args)(foo)


Here is an example featuring multiple decorators in which one takes an argument:

@deco1(deco_arg) @deco2 def func(): pass


This is equivalent to:

func = deco1(deco_arg)(deco2(func))


We hope that if you understand these examples here, things will become much clearer. We present a more useful yet still simple script below where the decorator does not take an argument. Example 11.8 is an intermediate script with a decorator that does take an argument.

So What Are Decorators?

We know that decorators are really functions now. We also know that they take function objects. But what will they do with those functions? Generally, when you wrap a function, you eventually call it. The nice thing is that we can do that whenever it is appropriate for our wrapper. We can run some preliminary code before executing the function or some cleanup code afterward, like postmortem analysis. It is up to the programmer. So when you see a decorator function, be prepared to find some code in it, and somewhere embedded within its definition, a call or at least some reference, to the target function.

This feature essentially introduces the concept that Java developers call AOP, or aspect-oriented programming. You can place code in your decorators for concerns that cut across your application. For example, you can use decorators to:

  • Introduce logging

  • Insert timing logic (aka instrumentation) for monitoring performance

  • Add transactional capabilities to functions

The ability to support decorators is very important for creating enterprise applications in Python. You will see that the bullet points above correspond quite closely to our example below as well as Example 11.2.

Decorator Example

We have an extremely simple example below, but it should get you started in really understanding how decorators work. This example "decorates" a (useless) function by displaying the time that it was executed. It is a "timestamp decoration" similar to the timestamp server that we discuss in Chapter 16.

Example 11.2. Example of Using a Function Decorator (deco.py)

This demonstration of a decorator (and closures) shows that it is merely a "wrapper" with which to "decorate" (or overlay) a function, returning the altered function object and reassigning it to the original identifier, forever losing access to the original function object.

1  #!/usr/bin/env python 2 3  from time import ctime, sleep 4 5  def tsfunc(func): 6      def wrappedFunc(): 7          print '[%s] %s() called' % ( 8              ctime(), func.__name__) 9          return func() 10   return wrappedFunc 11 12 @tsfunc 13 def foo(): 14     pass 15 16 foo() 17 sleep(4) 18 19 for i in range(2): 20    sleep(1) 21    foo()

Running this script, we get the following output:

[Sun Mar 19 22:50:28 2006] foo() called [Sun Mar 19 22:50:33 2006] foo() called [Sun Mar 19 22:50:34 2006] foo() called


Line-by-Line Explanation
Lines 510

Following the startup and module import lines, the tsfunc() function is a decorator that displays a timestamp (to standard output) of when a function is called. It defines an inner function wrappedFunc(), which adds the timestamp and calls the target function. The return value of the decorator is the "wrapped" function.

Lines 1221

We define function foo() with an empty body (which does nothing) and decorate it with tsfunc(). We then call it once as a proof-of-concept, wait four seconds, then call it twice more, pausing one second before each invocation.

As a result, after it has been called once, the second time it is called should be five (4 + 1) seconds after the first call, and the third time around should only be one second after that. This corresponds perfectly to the program output seen above.

You can read more about decorators in the Python Language Reference, the "What's New in Python 2.4" document, and the defining PEP 318.



Core Python Programming
Core Python Programming (2nd Edition)
ISBN: 0132269937
EAN: 2147483647
Year: 2004
Pages: 334
Authors: Wesley J Chun

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net