天天看點

Decorator in Pyhton

  轉自:http://programmingbits.pythonblogs.com/27_programmingbits/archive/50_function_decorators.html

  找了一篇關于Decorator的文章,雖然有點小問題,但在如何應用的方面寫的比較好。具體的小問題我會在文章中間用紅色字标注一下。

The Python programming language has an interesting syntactic feature called a decorator. Let's use an example in order to explain how and why you would want to use a Python decorator.

NOTE: All Python code examples presented here are based in Python 3.0. Full source code: function_decorators.py

Suppose we want to compute the Fibonacci numbers using a recursive function1. The following function definition does the trick:

def fib(n):
    if n in (0, 1):
        return n
    else:
        return fib(n - 1) + fib(n - 2)      

We can now test the function with different input values: 

>>> fib(0)
0
>>> fib(1)
1
>>> fib(4)
3
>>> fib(10)
55
>>> fib(30)
832040
>>> fib(35)
9227465
      

If you've actually typed these examples in a Python shell, you've probably noticed that obtaining a result takes longer for bigger inputs. In order to make it run faster, we can use a technique called memoization. A memoized function stores in a cache the results corresponding to some set of specific inputs. Later calls, with previously computed inputs, return the results stored in the cache, thus avoiding their recalculation. This means that the primary cost of a call with certain parameters is taken care of during the first call made to the function with those same parameters.

Instead of modifiying the fib function directly, it's better to write a reusable memoizing function:

def memoize(f):
    cache = {}
    def helper(x):
        if x not in cache:            
            cache[x] = f(x)
        return cache[x]
    return helper      

The memoize function wraps another function, called helper, which is going to provide additional functionality to the function received through parameter f. The helper function is actually a lexical closure, that is, a function object that remembers the variable bindings that were in its scope when it was created. In this case, helper remembers variables f and cache, which happen to hold a function object and a dictionary, respectively. The last statement in memoize just returns to its caller the helper lexical closure.

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Type the following code, and you'll notice right away a speed increase when calling fib. The speedup can be appreciated even in the very first call because the memoization immediately boosts all the recursive calls inside fib's definition.

>>> fib = memoize(fib)
>>> fib(50)
12586269025
      

對于這個測試,作者的了解出現了一點偏差,對于 fib(50) 這個調用,實際上在memoize的helper裡可以看到,它調用的是傳給memoize的參數f,在這裡就是上面def的fib函數,在這個函數内部的遞歸過程中與cache是沒有任何關系的,是以他這裡說第一次算 fib(50) 速度會快是不對的。

實際對cache提高效率的測試應該是:

>>> fib = memorize(fib)

>>> fib(50)

12586269025

>>> fib(50)

12586269025

可以發現,第一個fib(50)會計算很長時間,但第二個fib(50)是立即就會算出來的,因為第一次算出fib(50)後,cache中會增加fib(50)這個結果,而第二次再求的話,會直接從cache中取出,是以不耗時。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

The assignment in the first line can be read as: the fib function is being decorated by the memoize function. Because this is such a common programming idiom in Python, there's a special decorator syntax to simplify its use. This syntax was originally inspired by Java's annotations: just above the definition of the function that is to be decorated, place an at sign (@) followed by the name of the decorator function.

In other words, this Python syntax:

@some_decorator
def some_function():
    # function body...      

is equivalent to:

def some_function():
    # function body...
some_function = some_decorator(some_function)      

Most should agree that the @ notation is more readable and less error prone.

In order to use the Python decorator with our Fibonacci + memoization example, we would have to rewrite as follows:

def memoize(f):
    # Same code as before...
 
@memoize 
def fib(n):
    # Same code as before...      

The memoize function shows the general structure that a typical function decorator should have: it receives a function f as its input parameter, and it returns another function that attaches some additional responsibilities to f. 

Another interesting thing about Python decorators is that you can chain two or more together. Let's extend our example in order to incorporate a tracing decorator that will display a message before the decorated function gets called and also when it returns.

def memoize(f):
    # Same code as before...
 
def trace(f):
    def helper(x):
        call_str = "{0}({1})".format(f.__name__, x)
        print("Calling {0} ...".format(call_str))
        result = f(x)
        print("... returning from {0} = {1}".format(
              call_str, result))
        return result
    return helper
 
@memoize
@trace
def fib(n):
    # Same code as before...      

Notice the use @memoize and @trace just before the definition of fib. Now, when invoking fib, first the memoize decorator gets called, then the trace decorator, and finally the original fib code. Check this example:

>>> fib(5)
Calling fib(5) ...
Calling fib(4) ...
Calling fib(3) ...
Calling fib(2) ...
Calling fib(1) ...
... returning from fib(1) = 1
Calling fib(0) ...
... returning from fib(0) = 0
... returning from fib(2) = 1
... returning from fib(3) = 2
... returning from fib(4) = 3
... returning from fib(5) = 5
5
      

Python has three built-in functions that are intended to be used as function decorators:

  • classmethod: used to indicate that the decorated method is a class method, similar to those in Smalltalk or Ruby.
  • staticmethod: used to indicate that the decorated method is a static method, like those in C++, C# or Java.
  • property: used to decorate methods that will be used to get, set and delete object properties. 

It's also worth noting that Python 3.0 not only allows you to decorate functions, but also complete classes. Hopefully, I'll take some time to write about these specific kinds of decorators in a a future post.