Tuesday, May 5, 2009

Python|Decorators Don't Have to be (that) Scary

Python Decorators Don't Have to be (that) Scary - Siafoo

Python Decorators Don't Have to be (that) Scary

By David Updated 9 months ago (30 Jul 2008 at 02:37 PM) history recent activity
Abstract: Decorators modify functions. Beginning with the basics, learn how to use decorators in a variety of ways. Execute code when a function is parsed or called. Conditionally call functions and transform inputs and outputs. Write customizable decorators that accept arbitrary arguments. And, if necessary, easily make sure your decorated function has the same signature as the original.
Languages Python

Decorators. The shear mention of them brings fear to even the seasoned Python programmer.

Okay, maybe not. But decorators, at least in this author's opinion, have a weird syntax and inevitably complicated implementations that are especially foreign (I think) to many who have gotten used to the simplicity of Python.

1   The Basics

Decorators modify functions. More specifically, a decorator is a function that transforms another function.

When you use a decorator, Python passes the decorated function -- we'll call this the target function -- to the decorator function, and replaces it with the result. Without decorators, it would look something like this:

# 's
 1def decorator_function(target):
2 # Do something with the target function
3 target.attribute = 1
4 return target
5
6def target(a,b):
7 return a + b
8
9# This is what the decorator actually does
10target = decorator_function(target)

This code has the exact same functionality, but uses decorators. Note that I can name my decorator function whatever I want. Here, I've chosen 'decorator_function':

# 's
1def decorator_function(target):
2 # Do something with the target function
3 target.attribute = 1
4 return target
5
6# Here is the decorator, with the syntax '@function_name'
7@decorator_function
8def target(a,b):
9 return a + b

As you can see, you need to put the decorator function's name, prefaced with a @, on the line before the target function definition. Python internally will transform the target by applying the decorator to it and replacing it with the returned value.

Both of the above examples will have the same results:

# 's
1>>> target(1,2)
23
3>>> target.attribute
41

1.1   Does a decorator function have to return a function?

No. The decorator function can return absolutely anything, and Python will replace the target function with that return value. For example, you could do something like this:

# 's
 1def decorator_evil(target):
2 return False
3
4@decorator_evil
5def target(a,b):
6 return a + b
7
8>>> target
9False
10
11>>> target(1,2)
12TypeError: 'bool' object is not callable

This is really not something you want to be doing on a regular basis though -- I'm pretty sure that a basic design principal is to not have functions randomly turning into other sorts of things. It makes good sense to at least return some sort of callable.

2   Run-Time Transformations

"But," I hear you saying, "I thought decorators did more than that. I want to do things at run-time, like conditionally calling the function and transforming the arguments and return value."

Can decorators do these things? Yes. Is that really something 'more' than we talked about above? Not really. It's important here not to get bogged down in the details -- you already know all there is to know about decorators. To do one of these more complex things, we're really just adding some plain old Python to the mix.

2.1   The Wrapper Function

Remember, your decorator function can return an arbitrary function. We'll call it the wrapper function, for reasons which will become clear in a second. The trick here is to define the wrapper function inside the decorator function, giving it access to the decorator function's variable scope, including the target function.

# 's
 1def decorator(target):
2
3 def wrapper():
4 print 'Calling function "%s"' % target.__name__
5 return target()
6
7 # Since the wrapper is replacing the target function, assigning an attribute to the target function won't do anything.
8 # We need to assign it to the *wrapper function*.
9 wrapper.attribute = 1
10 return wrapper
11
12@decorator
13def target():
14 print 'I am the target function'
15
16>>> target()
17Calling function "target"
18I am the target function
19
20>>> target.attribute
211

As you can see, the wrapper function can do whatever it wants to the target function, including the simple case of returning the target's return value. But what happens to any arguments passed to the target function?

2.2   Getting the Arguments

Since the returned wrapper function replaces the target function, the wrapper function will receive the arguments intended for the target function. Assuming you want your decorator to work for any target function, your wrapper function then should accept arbitrary non-keyword arguments and arbitrary keyword arguments, add, remove, or modify arguments if necessary, and pass the arguments to the target function.

# 's
 1def decorator(target):
2
3 def wrapper(*args, **kwargs):
4 kwargs.update({'debug': True}) # Edit the keyword arguments -- here, enable debug mode no matter what
5 print 'Calling function "%s" with arguments %s and keyword arguments %s' % (target.__name__, args, kwargs)
6 return target(*args, **kwargs)
7
8 wrapper.attribute = 1
9 return wrapper
10
11@decorator
12def target(a, b, debug=False):
13 if debug: print '[Debug] I am the target function'
14 return a+b
15
16>>> target(1,2)
17Calling function "target" with arguments (1, 2) and keyword arguments {'debug': True}
18[Debug] I am the target function
193
20
21>>> target.attribute
221

Note

You can also apply a decorator to a class method. If your decorator is always going to be used this way, and you need access to the current instance, your wrapper function can assume the first argument is always self:

# 's
1def wrapper(self, *args, **kwargs):
2 # Do something with 'self'
3 print self
4 return target(self, *args, **kwargs)

2.3   Summing It Up

So, we have a wrapper function that accepts arbitrary arguments defined inside our decorator function. The wrapper function can call the target function if and when it wants, get the result, do something with it, and return whatever it wants.

Say I want certain function calls to require positive confirmation before they are executed, and then stringify the result of the function before returning it. Note that the built-in function raw_input prints a message and then waits for a response from stdin.

# 's
 1def decorator(target):  # Python passes the target function to the decorator
2
3 def wrapper(*args, **kwargs):
4
5 choice = raw_input('Are you sure you want to call the function "%s"? ' % target.__name__)
6
7 if choice and choice[0].lower() == 'y':
8 # If input starts with a 'y', call the function with the arguments
9 result = target(*args, **kwargs)
10 return str(result)
11
12 else:
13 print 'Call to %s cancelled' % target.__name__
14
15 return wrapper
16
17@decorator
18def target(a,b):
19 return a+b
20
21>>> test.target(1,2)
22Are you sure you want to call the function "target"? n
23Call to target cancelled
24
25>>> test.target(1,2)
26Are you sure you want to call the function "target"? y
27'3'

3   Dynamic Decorators

Sometimes you might want to customize behavior by passing arbitrary options to your decorator function. A cursory look at decorator syntax suggests there's no way to do that. You could just abandon the decorator idea altogether, but you certainly don't have to.

The solution is define your decorator function inside another function -- call it the options function. Right before the target function definition, where you would normally list the decorator function (prepended with an @), call this options function (prepended with an @) instead. The options function then returns your decorator function, which Python will use as the passes the target function to as before.

3.1   Passing Options to the Decorator

Your options function can accept any arguments you want it to. Since the decorator function is defined inside the options function, the decorator function has access to any of the arguments passed to the options function.

# 's
 1def options(value):
2
3 def decorator(target):
4 # Do something with the target function
5 target.attribute = value
6 return target
7 return decorator
8
9@options('value')
10def target(a,b):
11 return a + b
12
13>>> target(1,2)
143
15
16>>> target.attribute
17'value'

As you can see, nothing here about the decorator syntax itself has changed. Our decorator function is just in a dynamic scope instead of a static one.

3.2   Run-Time Tranformations

You can do Run-time Transformations by returning a wrapper function from your decorator function, just like before. For better or worse, though, there now must be three levels of functions:

# 's
 1def options(debug_level):
2
3 def decorator(target):
4
5 def wrapper(*args, **kwargs):
6 kwargs.update({'debug_level': debug_level}) # Edit the keyword arguments
7 # here, set debug level to whatever specified in the options
8
9 print 'Calling function "%s" with arguments %s and keyword arguments %s' % (target.__name__, args, kwargs)
10 return target(*args, **kwargs)
11
12 return wrapper
13
14 return decorator
15
16@options(5)
17def target(a, b, debug_level=0):
18 if debug_level: print '[Debug Level %s] I am the target function' % debug_level
19 return a+b
20
21>>> target(1,2)
22Calling function "target" with arguments (1, 2) and keyword arguments {'debug_level': 5}
23[Debug Level 5] I am the target function
243

4   Caveat: Function Signatures

Phew. Understand everything you can do with decorators now? Good :). However, there is one drawback that must be mentioned.

The function returned from the decorator function -- usually a wrapper function -- replaces the target function completely. Any later introspection into what appears to be the target function will actually be into the wrapper function.

Most of the time, this is okay. Generally you just call a function with some options. Your program doesn't check to see what the function's __name__ or what arguments it accepts. So usually this problem won't be a problem.

However sometimes you care if the function you are calling supports a certain option, supports arbitrary options, or, perhaps, what its __name__ is. Or maybe you are interested in one if the function's attributes. If you look at a function that has been decorated, you will actually be looking at the wrapper function.

In the example below, note that the getargspec function of the inspect module gets the names and default values of a function's arguments.

# 's
 1# This function is the same as the function 'target', except for the name
2def standalone_function(a,b):
3 return a+b
4
5def decorator(target):
6
7 def wrapper(*args, **kwargs):
8 return target()
9
10 return wrapper
11
12@decorator
13def target(a,b):
14 return a+b
15
16>>> from inspect import getargspec
17
18>>> standalone_function.__name__
19'standalone_function'
20
21>>> getargspec(standalone_function)
22(['a', 'b'], None, None, None)
23
24>>> target.__name__
25'wrapper'
26
27>>> getargspec(target)
28([], 'args', 'kwargs', None)

As you can see, the wrapper function reports that it accepts different arguments than the original target function Its call signature has changed.

4.1   A Solution

This is not an easy problem to solve. The update_wrapper method of the functools module provides a partial solution, copying the __name__ and other attributes from one function to another. But it does not solve what might be the largest problem of all: the changed call signature.

The decorator function of the decorator module provides the best solution: it can wrap your wrapper function in a dynamically-evaluated function with the correct arguments, restoring the original call signature. Similar to the update_wrapper function, it can also update your wrapper function with the __name__ and other attributes from the target function.

Note

For the remainder of this section, when I speak of the decorator function, I mean the one from this module, not one of the decorator functions that we've been using to transform our target functions.

Another way to create decorators

Unfortunately, though, the decorator function wasn't written with this use in mind. Instead it was written to turn standalone wrapper functions into full-fledged decorators, without having to worry about the function nesting described in Run-Time Transformations, above.

While this technique is often useful, it is much less customizable. Everything must be done at run-time, each time the function is executed. You cannot do any work when the target function is defined, including assigning the target or wrapper functions attributes or passing options to the decorator.

Also, in this author's opinion it is a bit of a black box; I'd rather know what my decorators are doing even if it is a little messier.

But we can make it work for us to solve this problem.

Ideally you would just call decorator(wrapper) and be done with it. However, things are never as simple as we'd like. As described above, the decorator function wraps the function passed to it -- our wrapper function -- in a dynamic function to fix the signature. But we still have a few problems:

Problem #1:
The dynamic function calls our wrapper function with (func, *args, **kwargs)
Solution #1:
Make our wrapper function accept (func, *args, **kwargs) instead of just (*args, **kwargs).
Problem #2:
The dynamic function is then wrapped in another function that expects to be used as an actual decorator -- it expects to be called with the target function, and will return the wrapper function.
Solution #2:
Call decorator's return value with the target function to get back to the dynamic function, which has the right signature.

This technique is a bit of a hack, and is a bit hard to explain, but it is easy to implement and works well.

This is the same example as before, but now with the decorator function (and a name change so things don't get too confusing):

# 's
 1from decorator import decorator
2
3def my_decorator(target):
4
5 def wrapper(target, *args, **kwargs): # the target function has been prepended to the list of arguments
6 return target(*args, **kwargs)
7
8 # We are calling the returned value with the target function to get a 'proper' wrapper function back
9 return decorator(wrapper)(target)
10
11
12@my_decorator
13def target(a,b):
14 return a+b
15
16>>> from inspect import getargspec
17
18>>> target.__name__
19'target'
20
21>>> getargspec(target)
22(['a', 'b'], None, None, None)

5   Putting it All Together

Sometimes, you really need a customizable decorator that does work both at parse-time and run-time, and has the signature of the original target function.

Here's an example that ties everything together. Expanding on the example from earlier, say you want certain function calls to require positive confirmation before they are executed, and you want to be able to customize the confirmation string for each target function. Furthermore, for some reason [1], you need the decorated function's signature to match the target function.

Here we go:

# 's
 1from decorator import decorator
2
3# The 'options' function. Recieves options and returns a decorator.
4def confirm(text):
5 '''
6 Pass a string to be sent as a confirmation message. Returns a decorator.
7 '''
8
9 # The actual decorator. Recieves the target function.
10 def my_decorator(target):
11 # Anything not in the wrapper function is done when the target function is initially parsed
12
13 # This is okay because the decorator function will copy the attribute to the wrapper function
14 target.attribute = 1
15
16 # The wrapper function. Replaces the target function and receives its arguments
17 def wrapper(target, *args, **kwargs):
18 # You could do something with the args or kwargs here
19
20 choice = raw_input(text)
21
22 if choice and choice[0].lower() == 'y':
23 # If input starts with a 'y', call the function with the arguments
24 result = target(*args, **kwargs)
25 # You could do something with the result here
26 return result
27
28 else:
29 print 'Call to %s cancelled' % target.__name__
30
31 # Fix the wrapper's call signature
32 return decorator(wrapper)(target)
33
34 return my_decorator
35
36@confirm('Are you sure you want to add these numbers? ')
37def target(a,b):
38 return a+b
39
40>>> Are you sure you want to add these numbers? yes
413
42
43>>> target.attribute
441

Hey, what do you know, it actually works.

6   Conclusion

As always, if you have a better way to do anything mentioned here, or if I've left anything out, leave a comment or feel free edit this article to fix the problem.

7   References

Michele Simionato's 'decorator' Module

Python Tips, Tricks, and Hacks - Decorators

[1]For example, Pylons checking your method's signature to decide what arguments to pass it.

Python| decorator 学习

Python decorator 学习 - Windows Live

November 09

Python decorator 学习

Python 程序一向简洁易读。不过也有例外,这两天 decorator 的语法就让我晕了一阵。不带参数时还好些,带参数的 decorator 定义要三层函数嵌套,网上的例子也比较少,自己想的时候是吃了点苦头。不过真正搞明白了也发现就那么回事。恩,记下此例以备忘:
 
目标描述:
写一个 decorator,使函数调用时能够打印被调用的次数和传给他的参数;且这两个功能由传给 decorator 的两个参数作为开关。
 
第一步:为目标函数func做第一层封装。
def newfunc(*kwds,**dic):
    #do something before the call of func
    if dbg_count:
        cnt[0] += 1
        print "Function %s() calling count= %s" % (func.__name__, cnt[0])
    if dbg_arg:
        print "Args:\t",kwds,dic
    val = func(*kwds,**dic)
    #do something after the call of func
    #log.write( str(val) + os.linesep )
    return val

*kwds 和 **dic 是要传给函数 func 的参数,我们在 newfunc 中把他们截获,做一点准备工作后再调用 func 。dbg_count 和 dbg_arg 是 decorator 接收的两个参数,分别作为是否打印对应信息的开关。cnt 是只有一个元素的 list , 用它的 0 号元素储存函数的调用次数,后面再具体分析。
 
第二步:为 newfunc 做一层封装,将他作为替代 func 的函数返回。
def loadf(func):
    cnt = [0]
    def newfunc(*kwds,**dic):
        #do something before the call of func
        if dbg_count:
            cnt[0] += 1
            print "Function %s() calling count= %s" % (func.__name__, cnt[0])
        if dbg_arg:
            print "Args:\t",kwds,dic
        val = func(*kwds,**dic)
        #do something after the call of func
        #log.write( str(val) + os.linesep )
        return val
    return newfunc

与第一步相比只增加了三行,完成两件事:(1)返回刚刚定义的 newfunc 函数,作为 func 的替代;(2)初始化函数调用计数器 cnt 。第一件事比较好理解,返回 newfunc 函数后,如果不考虑那两个控制参数,loadf 已经可以当成一个不含参数的 decorator 来使用了。关于第二件事,这里实际上 cnt[0] 是作为函数 newfunc 的一个静态变量来使用的。作为 c/c++ 程序员,当遇到给函数添加调用计数器时自然想到用静态变量来储存,但在 Python 中没有明确的 静态变量 声明语法,该如何保存上次函数调用后的一些值呢——嵌套函数,这是 Python 中的方法 (或许有人会说yield 或 Generator,那个虽然我也试过,但用作静态变量时很不自然,暂不讨论)。外层函数中定义的 list或dict 变量,在内层函数中一直有效且多次调用内层函数时不会重复初始化,完全就是静态变量的翻版嘛...这里还有个疑问,既然只用保存一个量,为何不用一个 int 型的 cnt ,而要麻烦的动用 list 呢。嘛,懒得写了,试过你就知道了。
 
第三步:最后一层封装,用来接收那两个控制参数。下面是最终版

def dbg_config(dbg_count=True, dbg_arg=True):
    def loadf(func):
        cnt = [0]
        def newfunc(*kwds,**dic):
            #do something before the call of real function
            if dbg_count:
                cnt[0] += 1
                print "Function %s() calling count= %s" % (func.__name__, cnt[0])
            if dbg_arg:
                print "Args:\t",kwds,dic
            val = func(*kwds,**dic)
            #do something after the call of real function
            #log.write( str(val) + os.linesep )
            return val
        return newfunc
    return loadf

做的事情很简单,接收参数,最后返回 loadf 。来分析一下如何起作用的:
@dbg_config(True,True)
def func(a,b):pass
 
调用 func(1,2) 时,展开后就是
dbg_config(True,True)(func)(1,2)
由于 dbg_config(True,True) 返回的是 loadf ,于是上面继续展开,得到
loadf(func)(1,2)
loadf(func) 又返回 newfunc ,变成
newfunc(1,2)
就把对 func 的调用,通过两层封装,变成了对 newfunc 的调用。newfunc 做点补充工作,最后在内部调用了 func
 
上面,由于 decorator 要接收参数,所以得三层封装。若不带参数时只用两层即可。
 
最后看下使用情况:
>>> @dbg_config(dbg_arg=False)
... def foo():pass
...
>>> foo()
Function foo() calling count= 1
>>> foo()
Function foo() calling count= 2
>>> foo()
Function foo() calling count= 3
通过将 dbg_arg 赋值为 False,关闭打印参数的功能
 
>>> @dbg_config()
... def add(a,b,c):
...     return a+b+c
...
>>> print add(1,2,3)
Function add() calling count= 1
Args: (1, 2, 3) {}
6

>>> print add(3,c=2,b=5)
Function add() calling count= 2
Args: (3,) {'c': 2, 'b': 5}
10
两个功能都打开(默认),打印了调用次数,也打印了参数,最后输出运行结果。
 
应该都比较清楚了。写 decorator 的时候有个 functools 模块好像有工具可以用;另外 yield 从 2.4 版开始可以作为表达式而不是一个语句来使用,灵活性大增,可以搞些复杂的用法了,以后有空再来分析吧。

Python|学习decorator的使用

[Python学习]decorator的使用 - limodou的学习记录 - DonewsBlog

在我以前介绍 Python 2.4 特性的Blog中已经介绍过了decorator了,不过,那时是照猫画虎,现在再仔细描述一下它的使用。

关于decorator的详细介绍在 Python 2.4中的What's new中已经有介绍,大家可以看一下。

如何调用decorator

基本上调用decorator有两种形式

第一种:

@A
def f ():
    ...

这种形式是decorator不带参数的写法。最终 Python 会处理为:

f = A(f)

还可以扩展成:

@A
@B
@C
def f ():
    ...

最终 Python 会处理为:

f = A(B(C(f)))

注:文档上写的是@A @B @C的形式,但实际上是不行的,要写成多行。而且执行顺序是按函数调用顺序来的,先最下面的C,然后是B,然后是A。因此,如果decorator有顺序话,一定要注意:先要执行的放在最下面,最后执行的放在最上面。(应该不存在这种倒序的关系)

第二种:

@A(args)
def f ():
    ...

这种形式是decorator带参数的写法。那么 Python 会处理为:

def f(): ...
_deco = A(args)
f = _deco(f)

可以看出, Python 会先执行A(args)得到一个decorator函数,然后再按与第一种一样的方式进行处理。

decorator函数的定义

每一个decorator都对应有相应的函数,它要对后面的函数进行处理,要么返回原来的函数对象,要么返回一个新的函数对象。请注意,decorator只用来处理函数和类方法。

第一种:

针对于第一种调用形式

def A(func):
    #处理func
    #如func.attr='decorated'
    return func
@A
def f(args):pass

上面是对func处理后,仍返回原函数对象。这个decorator函数的参数为要处理的函数。如果要返回一个新的函数,可以为:

def A(func):
    def new_func(args):
        #做一些额外的工作
        return func(args) #调用原函数继续进行处理
    return new_func
@A
def f(args):pass

要注意 new_func的定义形式要与待处理的函数相同,因此还可以写得通用一些,如:

def A(func):
    def new_func(*args, **argkw):
        #做一些额外的工作
        return func(*args, **argkw) #调用原函数继续进行处理
    return new_func
@A
def f(args):pass

可以看出,在A中定义了新的函数,然后A返回这个新的函数。在新函数中,先处理一些事情,比如对参数进行检查,或做一些其它的工作,然后再调原始的函数进行处理。这种模式可以看成,在调用函数前,通过使用decorator技术,可以在调用函数之前进行了一些处理。如果你想在调用函数之后进行一些处理,或者再进一步,在调用函数之后,根据函数的返回值进行一些处理可以写成这样:

def A(func):
    def new_func(*args, **argkw):
        result = func(*args, **argkw) #调用原函数继续进行处理
        if result:
            #做一些额外的工作
            return new_result
        else:
            return result
    return new_func
@A
def f(args):pass

第二种:

针对第二种调用形式

在文档上说,如果你的decorator在调用时使用了参数,那么你的decorator函数只会使用这些参数进行调用,因此你需要返回一个新的decorator函数,这样就与第一种形式一致了。

def A(arg):
    def _A(func):
        def new_func(args):
            #做一些额外的工作
            return func(args)
        return new_func
    return _A
@A(arg)
def f(args):pass

可以看出A(arg)返回了一个新的 decorator _A。

decorator的应用场景

不过我也一直在想,到底decorator的魔力是什么?适合在哪些场合呢?是否我需要使用它呢?

decorator的魔力就是它可以对所修饰的函数进行加工。那么这种加工是在不改变原来函数代码的情况下进行的。有点象我知道那么一点点的AOP(面向方面编程)的想法。

它适合的场合我能想到的列举出下:

  1. 象文档中所说,最初是为了使调用staticmethod和classmethod这样的方法更方便
  2. 在某些函数执行前做一些工作,如web开发中,许多函数在调用前需要先检查一下用户是否已经登录,然后才能调用
  3. 在某此函数执行后做一些工作,如调用完毕后,根据返回状态写日志
  4. 做参数检查

可能还有许多,你可以自由发挥想象

那么我需要用它吗?

我想那要看你了。不过,我想在某些情况下,使用decorator可以增加程序的灵活性,减少耦合度。比如前面所说的用户登录检查。的确可以写一个通用的登录检查函数,然后在每个函数中进行调用。但这样会造成函数不够灵活,而且增加了与其它函数之间的结合程度。如果用户登录检查功能有所修改,比如返回值的判断发生了变化,有可能每个用到它的函数都要修改。而使用decorator不会造成这一问题。同时使用decorator的语法也使得代码简单,清晰(一但你熟悉它的语法的话)。当然你不使用它是可以的。不过,这种函数之间相互结合的方式,更符合搭积木的要求,它可以把函数功能进一步分解,使得功能足够简单和单一。然后再通过decorator的机制灵活的把相关的函数串成一个串,这么一想,还真是不错。比如下面:

@A
@B
def account(args):pass

假设这是一个记帐处理函数,account只管记帐。但一个真正的记帐还有一些判断和处理,比如:B检查帐户状态,A记日志。这样的效果其实是先检查B、通过在A中的处理可以先执行account,然后再进行记日志的处理。象搭积木一样很方便,改起来也容易。甚至可以把account也写成decorator,而下面执行的函数是一个空函数。然后再通过配置文件等方法,将decorator的组合保存起来,就基本实现功能的组装化。是不是非常理想。

Python 带给人的创造力真是无穷啊!

Python| tips: 什么是*args和**kwargs?

Python tips: 什么是*args和**kwargs? - 天生我材必有用,千金散尽还复来 - JavaEye技术网站

http://www.cnblogs.com/fengmk2/archive/2008/04/21/1163766.html

Python tips: 什么是*args和**kwargs?

先来看个例子:

def foo(*args, **kwargs):     print 'args = ', args     print 'kwargs = ', kwargs     print '---------------------------------------'  if __name__ == '__main__':     foo(1,2,3,4)     foo(a=1,b=2,c=3)     foo(1,2,3,4, a=1,b=2,c=3)     foo('a', 1, None, a=1, b='2', c=3)
输出结果如下:

args =  (1, 2, 3, 4)
kwargs =  {}
---------------------------------------
args =  ()
kwargs =  {'a': 1, 'c': 3, 'b': 2}
---------------------------------------
args =  (1, 2, 3, 4)
kwargs =  {'a': 1, 'c': 3, 'b': 2}
---------------------------------------
args =  ('a', 1, None)
kwargs =  {'a': 1, 'c': 3, 'b': '2'}
---------------------------------------

可以看到,这两个是python中的可变参数。*args表示任何多个无名参数,它是一个tuple;**kwargs表示关键字参数,它是一个dict。并且同时使用*args和**kwargs时,必须*args参数列要在**kwargs前,像foo(a=1, b='2', c=3, a', 1, None, )这样调用的话,会提示语法错误“SyntaxError: non-keyword arg after keyword arg”。

 

呵呵,知道*args和**kwargs是什么了吧。还有一个很漂亮的用法,就是创建字典:

    def kw_dict(**kwargs):         return kwargs     print kw_dict(a=1,b=2,c=3) == {'a':1, 'b':2, 'c':3}

其实python中就带有dict类,使用dict(a=1,b=2,c=3)即可创建一个字典了。

 

“人生苦短,我用python。”

Python|学习python decorator模块

学习python decorator模块 - hfeeqi - JavaEye技术网站

2007-04-15

学习python decorator模块

关键字: python decorator metaclass
本文简介
decorator模块是 Michele Simionato 为简化python的decorator的使用难度而开发的,使用它,您可以更加容易的使用decorator机制写出可读性、可维护性更好的代码。
本文大部分翻译自下面这篇文档: www.phyast.pitt.edu/~micheles/python/documentation.html , 之中或会加入自己的理解或注释

decorator 模块


作者: Michele Simionato
E-mail: michele.simionato@gmail.com
版本: 2.0.1
下载: http://www.phyast.pitt.edu/~micheles/python/decorator-2.0.1.zip
网络安装: easy_install decorator
License: Python license

简介

Python 2.4 的 decorators 是一个展现语法糖的有趣的例子: 原理上, 它们的引入没有改变任何东西,因为它们没有提供任何新的在原语言里面不存在的功能; 实践中, 它们的引入能够显著的改善我们的Python代码结构. 我相信这种改变是出于好意的, 基于以下理由,decorators是一些非常好的想法:
但是,到现在为止(译者注: 到Python 2.5 里面已经有所改善),正确的定制一个decorators需要一些经验, 它比我们想像的要难于使用。例如,典型的decorators的实现涉及到嵌套(nested)函数,而我们都知道:flat 比 nested 好。

decorator的目的是为普通开发人员简化dccorators的使用,并且通过一些有用的例子来推广decorators的使用,例如:memoize,tracing,redirecting_stdout, locked等等。

模块的核心是一个叫做decorator的decorator工厂函数。所有在这里讨论的简单的decorators解决方案都是基于decorator模块的。通过执行如下命令后产生的 _main.py 文件中包含了这些所有的这些代码:
> python doctester.py documentation.txt
同时,该命令还会运行所有的作为测试用例存在的例子。

定义

从技术上来讲, 任何可以被调用的、带一个参数的Python对象都可以被当作是一个decorator。 然而,这个定义因为过于宽泛而显得并无真正得用处。为方便起见,我们可以把decorator分为两类:
  • 保留签名的(signature-preserving)decorators,例如:将一个函数A作为可调用对象(decorators)的输入并返回一个函数B,函数A和B的签名是相同的;
  • 改变签名的(signature-changing)decorators, 例如:将一个函数A作为可调用对象(decorators)的输入并返回一个函数B,函数A和B的签名是不相同的,或者该decorator的返回值就不是一个可调用对象。

Signature-changing decorators有它们的用处,例如:内部类 staticmethod classmethod 就属于这一类,因为它们接受函数作为输入并返回一个descriptor对象,这个对象不是函数,也不是可调用对象。

但是,signature-preserving decorators 则更加通用,更加容易理解,尤其是这一类decorators能够被组合起来使用,而其他decorators通常是不能够组合使用的(如staticmethodclassmethod)。

从零开始写一个signature-preserving decorator 不是那么浅显的,尤其是当你想实现一个正确的能够接受任意签名的decorator时,一个简单的例子将阐明这个问题。

问题的描述

假设你想跟踪一个函数的执行:这是一个常见的使用decorator的例子,很多地方你都能看到类似的代码

python 代码
  1. try:  
  2.     from functools import update_wrapper  
  3. except ImportError# using Python version < 2.5  
  4.     def decorator_trace(f):  
  5.         def newf(*args, **kw):  
  6.            print "calling %s with args %s, %s" % (f.__name__, args, kw)  
  7.            return f(*args, **kw)  
  8.         newf.__name__ = f.__name__  
  9.         newf.__dict__.update(f.__dict__)  
  10.         newf.__doc__ = f.__doc__  
  11.         newf.__module__ = f.__module__  
  12.         return newf  
  13. else# using Python 2.5+  
  14.     def decorator_trace(f):  
  15.         def newf(*args, **kw):  
  16.             print "calling %s with args %s, %s" % (f.__name__, args, kw)  
  17.             return f(*args, **kw)  
  18.         return update_wrapper(newf, f)  

(译者注:上面的代码中虽然有修改结果对象的自省信息(或元信息),但是修改得不全面,如其签名信息就没有修改为与被修饰对象保持一致)
上面代码是想实现一个能够接受一般签名的decorator,不幸的是,该实现并没有定义为一个signature-preserving的decorator,因为大体上说 decorator_trace 返回了一个与被修饰函数不同的签名。
思考下面的例子:

>>> @decorator_trace
... def f1(x):
... pass

这里原先的函数只接受一个参数,而修饰后的函数却接受任意的参数和关键字参数。
>>> from inspect import getargspec
>>> print getargspec(f1)
([], 'args', 'kw', None)

这就意味这自省工具如:pydoc将会给出关于函数f1的错误的签名信息,这是一个美丽的错误:pydoc将告诉你该函数能够接受一个通用的签名:*args, **kw,但是当你使用超过一个的参数去调用该函数时,你将会得到如下错误:
>>> f1(0, 1)
Traceback (most recent call last):
...
TypeError: f1() takes exactly 1 argument (2 given)


解决方案

这个方案提供了一个通用的 decorators  工厂,它对应用程序员隐藏了实现 signature-preserving  的 decorator 的复杂性。该工厂允许在不使用嵌套函数或嵌套类的情况下定义decorators, 下面是一个告诉你怎样定义 decorator_trace 的简单例子。
首先,导入decorator模块:
>>> from decorator import decorator
然后定义一个辅助函数f,该函数具有如下signature:(f, *args, **kw),该函数只是简单的做如下调用f(args, kw):
python 代码
 
  1. def trace(f, *args, **kw):  
  2.     print "calling %s with args %s, %s" % (f.func_name, args, kw)  
  3.     return f(*args, **kw)  

decorator 模块能够把帮助函数转变成一个 signature-preserving 对象,例如:一个接受一个函数作为输入的可调用对象,并返回一个被修饰过的函数,该函数具有与原函数一致的签名,这样,你就能够这样写:
>>> @decorator(trace)
... def f1(x):
... pass
很容易就验证函数 f1 正常工作了
>>> f1(0)
calling f1 with args (0,), {}
并且它具有正确的签名:
>>> print getargspec(f1)
(['x'], None, None, None)
同样的,该decorator对象对具有其他签名的函数也是有效的:
>>> @decorator(trace)
... def f(x, y=1, z=2, *args, **kw):
... pass
>>> f(0, 3)
calling f with args (0, 3, 2), {}
>>> print getargspec(f)

甚至包括下面这些具有奇特签名的函数也能够正常工作:
>>> @decorator(trace)
... def exotic_signature((x, y)=(1,2)): return x+y

>>> print getargspec(exotic_signature)
([['x', 'y']], None, None, ((1, 2),))
>>> exotic_signature()
calling exotic_signature with args ((1, 2),), {}
3

(['x', 'y', 'z'], 'args', 'kw', (1, 2))

decorator is a Decorator


工厂函数decorator本身就可以被当作是一个 signature-changing 的decorator, 就和 classmethod 和 staticmethod 一样,不同的是这两个内部类返回的是一般的不可调用的对象,而decorator返回的是 signature-preserving 的decorators, 例如带单参数的函数。这样,你能够这样写:
>>> @decorator
... def tracing(f, *args, **kw):
... print "calling %s with args %s, %s" % (f.func_name, args, kw)
... return f(*args, **kw)
 这中惯用法实际上是把 tracing 重定义为一个 decorator , 我们能够很容易的检测到tracing的签名被更改了:
>>> print getargspec(tracing)
(['f'], None, None, None)
这样,tracing能够被当作一个decorator使用,下面的代码能够工作:
>>> @tracing
... def func(): pass
>>> func()
calling func with args (), {}
BTW,你还能够对 lambda 函数应用该 decorator:
>>> tracing(lambda : None)()
calling <lambda> with args (), {}</lambda>
下面开始讨论decorators的用法。

缓存化(memoize)


这里讨论的decorator实现了memoize模式,它可以把函数调用结果存储在一个字典对象中,下次使用相同参数调用该函数时,就可以直接从该字典对象里面获取结果而无需重新计算。
memoize 代码
 
  1. from decorator import *  
  2.   
  3. def getattr_(obj, name, default_thunk):  
  4.     "Similar to .setdefault in dictionaries."  
  5.     try:  
  6.         return getattr(obj, name)  
  7.     except AttributeError:  
  8.         default = default_thunk()  
  9.         setattr(obj, name, default)  
  10.         return default  
  11.   
  12. @decorator  
  13. def memoize(func, *args):  
  14.     dic = getattr_(func, "memoize_dic", dict)  
  15.     # memoize_dic is created at the first call  
  16.     if args in dic:  
  17.         return dic[args]  
  18.     else:  
  19.         result = func(*args)  
  20.         dic[args] = result  
  21.         return result  
下面时使用测试:
>>> @memoize
... def heavy_computation():
... time.sleep(2)
... return "done"
>>> print heavy_computation() # the first time it will take 2 seconds
done
>>> print heavy_computation() # the second time it will be instantaneous
done
作为练习, 您可以尝试不借助于decorator工厂来正确的实现memoize。
注意:这个memoize实现只有当函数没有关键字参数时才能够正常工作,因为实际上不可能正确的缓存具有可变参数的函数。您可以放弃这个需求,允许有关键字参数存在,但是,当有关键字参数被传入时,其结果是不能够被缓存的。具体例子请参照http://www.python.org/moin/PythonDecoratorLibrary

锁(locked)


不想再翻这一小节了!这一小节本来已经翻译了,但是由于系统故障导致被丢失了,而且因为Python2.5中已经实现了with语句,因此这一小节的内容也就显得不是那么重要了。
代码附上:
locked的实现
 
  1. import threading  
  2.   
  3. @decorator  
  4. def locked(func, *args, **kw):  
  5.     lock = getattr_(func, "lock", threading.Lock)  
  6.     lock.acquire()  
  7.     try:  
  8.         result = func(*args, **kw)  
  9.     finally:  
  10.         lock.release()  
  11.     return result  

锁的使用
 
  1. import time  
  2.   
  3. datalist = [] # for simplicity the written data are stored into a list.  
  4.   
  5. @locked  
  6. def write(data):  
  7.     "Writing to a sigle-access resource"  
  8.     time.sleep(1)  
  9.     datalist.append(data)  

延时化和线程化(delayed and threaded)