zoukankan      html  css  js  c++  java
  • Python装饰器

    原文:Decorators and Functional Python

    译者:youngsterxyf

    装饰器是Python的一大特色。除了在语言中的原本用处,还帮助我们以一种有趣的方式(函数式)进行思考。

    我打算自底向上解释装饰器如何工作。首先解释几个话题以帮助理解装饰器。然后,深入一点探索几个简单的装饰器以及它们如何工作。最后,讨论一些更高级的使用装饰器的方式,比如:传递可选参数给装饰器或者串接几个装饰器。

    首先以我能想到的最简单的方式来定义Python函数是什么。基于该定义,我们可以类似的简单方式来定义装饰器。

    函数是一个完成特定任务的可复用代码块。

    好的,那么装饰器又是什么呢?

    装饰器是一个修改其他函数的函数。

    现在在装饰器的定义上进行详述,先解释一些先决条件。

    函数是一等对象

    Python中,所有东西都是对象。这意味着可以通过名字引用函数,以及像其他对象那样传递。例如:

    def traveling_function():
    	print "Here I am!"
    
    function_dict = {
    	"func": traveling_function
    }
    
    trav_func = function_dict['func']
    trav_func()
    # >> Here I am!

    traveling_function 被赋值给 function_dict 字典中键 func 的值,仍旧可以正常调用。

    一等函数允许高阶函数

    我们可以像其他对象那样传递函数。可以将函数作为值传递给字典,放在列表中,或者作为对象的属性进行赋值。那为什么不能作为参数传给另一个函数呢?当然可以!如果一个函数接受另一个函数作为其参数或者返回另一个函数,则称之为高阶函数。

    def self_absorbed_function():
    	return "I'm an amazing function!"
    
    def printer(func):
    	print "The function passed to me says: " + func()
    
    # Call `printer` and give it `self_absorbed_function` as an argument
    printer(self_absorbed_function)
    # >>> The function passed to me says: I'm an amazing function!

    现在你也看到函数可以作为参数传给另一个函数,而且传给函数的函数还可以调用。这允许我们创建一些有意思的函数,例如装饰器。

    装饰器基础

    本质上,装饰器就是一个以另一个函数为参数的函数。大多数情况下,它们会返回所包装函数的一个修改版本。来看个我们能想到的最简单的装饰器—同一性(identity)装饰器,或许对我们理解装饰器的工作原理有所帮助。

    def identity_decorator(func):
        def wrapper():
            func()
        return wrapper
    
    def a_function():
        print "I'm a normal function."
    
    # `decorated_function` 是 `identity_function` 返回的函数,也就是嵌套函数 `wrapper`
    decorated_function = identity_function(a_function)
    
    # 如下调用 `identity_function` 返回的函数
    decorated_function()
    # >>> I'm a normal function

    这里, identity_decorator 根本没有修改它包装的函数,只是简单地返回一个函数(wrapper),这个函数在被调用之时,会去调用原来作为 identity_decorator 参数的函数。这是个没有用处的装饰器!

    关于 identity_decorator 的有趣之处是 wrapper 能够访问变量 func ,即使 func 并非是它的参数。这归因于闭包。

    闭包

    闭包是一个花哨的术语,意为声明一个函数时,该函数会维持一个指向声明所处词法环境的引用。

    上例中定义的函数 wrapper 能够在其局部作用域(local scope)中访问 func。这意味着在 wrapper (返回并赋值给变量 decorated_function )的整个生命周期内,它都可以访问 func 变量。一旦 identity_decorator返回,那么访问 func 的唯一方式就是通过 decorated_function 。 func 只作为一个变量存在于 decorated_function 作用域环境的内部。

    一个简单的装饰器

    现在我们来创建一个确实有点用的装饰器。这个装饰器所做的就是记录它所修改的函数被调用了多少次。

    def logging_decorator(func):
        def wrapper():
            wrapper.count += 1
            print "The function I modify has been called {0} time(s)".format(wrapper.count)
            func()
        wrapper.count = 0
    return wrapper def a_function(): print "I'm a normal function." modified_function = logging_decorator(a_function) modified_function() # >>> The function I modify has been called 1 time(s). # >>> I'm a normal function. modified_function() # >>> The function I modify has been called 2 time(s). # >>> I'm a normal function.

    我们说装饰器会修改函数,这样来想对理解也是有帮助的。但如例子所见, logging_decorator 返回的是一个类似于a_function 的新函数,只是多了一个日志特性。

    上例中, logging_decorator 不仅接受一个函数作为参数,并且返回一个函数, wrapper 。每次logging_decorator 返回的函数得到调用,它就对 wrapper.count 的值加1,打印出来,然后调用logging_decorator 包装的函数。

    你也许正疑惑为什么我们的计数器是 wrapper 的一个属性而不是一个普通的变量。难道 wrapper 的闭包环境不是让我们访问在其局部作用域中声明的任意变量么?是的,但有个问题。Python中,闭包允许对其函数作用域链中任一变量的进行任意读操作,但只允许对可变对象(列表、字典、等等)进行写操作。整数在Python中是非可变对象,因此我们不能修改 wrapper 内部整型变量的值。相反,我们将计数器作为 wrapper 的一个属性—一个可变对象,因此可以随我们自己增大它的值

    如果改成普通变量,会报错:UnboundLocalError: local variable 'count' referenced before assignment。这里可以参考:

    http://www.cnblogs.com/youxin/p/3730610.html

    装饰器语法

    在前一个例子中,我们看到可以将一个函数作为参数传给装饰器,从而使用装饰器函数对该函数进行包装。然而,Python还有一个语法模式使得这一切更加直观,更容易阅读,一旦你熟悉了装饰器。

    # In the previous example, we used our decorator function by passing the
    # function we wanted to modify to it, and assigning the result to a variable
    def some_function():
        print "I'm happiest when decorated."
    
    # Here we will make the assigned variable the same name as the wrapped function
    some_function = logging_decorator(some_function)

    # We can achieve the exact same thing with this syntax:
    
    @logging_decorator
    def some_function():
        print "I'm happiest when decorated"

    使用装饰器语法,鸟瞰其中发生的事情:

    1. 解释器到达被装饰的函数,编译 some_function,并将其命名为 ‘some_function’。
    2. 然后将该函数传递给装饰行中指定的装饰器函数( logging_function )。
    3. 装饰器函数(通常是用来包装原函数的另一个函数)的返回值取代原来的函数(some_function ),绑定到变量名some_function 。

    将这些步骤记住,让我们来更清晰地解释 identity_decorator 。

    def identity_decorator(func):
        # Everything here happens when the decorator LOADS and is passed
        # the function as decribed in step 2 above
        def wrapper():
            # Things here happen each time the final wrapped function gets CALLED
            func()
        return wrapper

    希望那些注释有助于理解。每次调用被包装的函数,仅执行装饰器返回的函数中的指令。返回函数之外的指令仅执行一次—上述步骤2中描述的:装饰器首次接收到传递给它的待包装函数之时。

    在观察更多的有意思的装饰器之前,我想再解释一样东西。

    *args与**kwargs

    以前你也许有时会把这两者相混淆了。让我们一次性地讨论它们。

    • 通过在形参列表中使用 *args 语法,python函数能够接收可变数量的位置参数(positional arguments)。 *args 会将所有没有关键字的参数放入一个参数元组中,在函数里可以访问元组中的参数。相反,将 *args 用于函数调用时的实参列表之时,它会将参数元组展开成一系列的位置参数。

    def function_with_many_arguments(*args):
        print args
    
    # `args` within the function will be a tuple of any arguments we pass
    # which can be used within the function like any other tuple
    function_with_many_arguments('hello', 123, True)
    # >>> ('hello', 123, True)

    def function_with_3_parameters(num, boolean, string):
        print "num is " + str(num)
        print "boolean is " + str(boolean)
        print "string is " + string
    
    arg_list = [1, False, 'decorators']
    
    # arg_list will be expanded into 3 positional arguments by the `*` symbol
    function_with_3_parameters(*arg_list)
    # >>> num is 1
    # >>> boolean is False
    # >>> string is decorators

    重述一遍:在形参列表中, *args会将一系列的参数压缩进一个名为’args’的元组,而在实参列表中, *args 会将一个可迭代的参数数据结构展开为一系列的位置实参应用于函数

    如你所见在实参展开的例子中, * 符号可与’args’之外的名字一起使用。当压缩/展开一般的参数列表,使用 *args 的形式仅仅是一种惯例。

    • **kwargs 与 *args 的行为类似,但用于关键字参数而非位置参数。如果在函数的形参列表中使用 **kwargs ,它会收集函数收到的所有额外关键字参数,放入一个字典中。如果用于函数的实参列表,它会将一个字典展开为一系列的关键字参数。

    def funtion_with_many_keyword_args(**kwargs):
        print kwargs
    
    function_with_many_keyword_args(a='apples', b='bananas', c='cantalopes')
    # >> {'a':'apples', 'b':'bananas', 'c':'cantalopes'}

    def multiply_name(count=0, name=''):
        print name * count
    
    arg_dict = {'count': 3, 'name': 'Brian'}
    
    multiply_name(**arg_dict)
    # >> BrianBrianBrian

    既然你理解了 *args 与 **kwargs 的工作原理,那么我们就继续研究一个你会发现很有用的装饰器。

    缓存制表(Memoization)

    缓存制表是避免潜在的昂贵的重复计算的一种方法,通过缓存函数每次执行的结果来实现。这样,下一次函数以相同的参数执行,就可以从缓存中获取返回结果,不需要再次计算结果。

    from functools import wraps
    
    def memoize(func):
        cache = {}
    
        @wraps(func)
        def wrapper(*args):
            if args not in cache:
                cache[args] = func(*args)
            return cache[args]
        return wrapper
    
    @memoize
    def an_expensive_function(arg1, arg2, arg3):
        ...

    你可能注意到了示例代码中一个奇怪的 @wraps 装饰器。在完整地讨论 memoize 之前我将简要地解释这个装饰器。

    • 使用装饰器的一个副作用是被包装的函数失去了本来有的 __name__ , __doc__ , 以及 __module__ 属性。 wraps函数是一个包装另一个装饰器返回的函数的装饰器,将那三个属性的值恢复为函数未装饰之时的值。例如: 如果不使用 wraps 装饰器, an_expensive_function 的名字(通过 an_expensive_function.__name__ 可以看到)将是 ‘wrapper’ 。

    我认为 memoize 是一个很好的装饰器用例。它服务于一个很多函数都需要的目的,通过将它创建为一个通用装饰器,我们可以将它的功能应用于任一能够从其中获益的函数。这就避免了在多种不同的场合重复实现这个功能。因为不需要重复自己,所以我们的代码更容易维护,并且更容易阅读和理解。只要读一个单词你就能立刻理解函数使用了缓存制表。

    需要提醒的是:缓存制表仅适用于纯函数。也就是说给定一个特定的参数设置,函数确定总会产生相同的结果。如果函数依赖于不作为参数传递的全局变量、I/O、或者其它任意可能影响返回值的东西,缓存制表会产生令人迷惑的结果!并且,一个纯函数不会有任何副作用。因此,如果你的函数会增大一个计数器,或者调用另一个对象的方法,或者其它任意不在函数的返回结果中表示的东西,当结果是从缓存中返回时,副作用操作并不会得到执行。

    类的装饰器

    最初,我们说装饰器是一个修改另一个函数的函数,但其实它们可以用于修改类或者方法。对类进行装饰并不常见,但某些情况下作为元类(metaclass)的一个替代,类的装饰器是一个有用的工具。

    foo = ['important', 'foo', 'stuff']
    
    def add_foo(klass):
        klass.foo = foo
        return klass
    
    
    @add_foo
    class Person(object):
        pass
    
    brian = Person()
    
    print brian.foo
    # >> ['important', 'foo', 'stuff']

    现在,类 Person 的任一对象都有一个超级重要的 foo 属性!注意,因为我们装饰的是一个类,所以装饰器返回的不是一个函数,而是一个类。更新一下装饰器的定义:

    装饰器是一个修改函数、或方法、或类的函数。

    装饰器类

    事实证明我早先对你隐瞒了一些其它事情。不仅装饰器可以装饰一个类,并且装饰器也可以是一个类!对于装饰器的唯一要求就是它的返回值必须可调用(callable)。这意味着装饰器必须实现 __call__ 魔术方法,当你调用一个对象时,会隐式调用这个方法。函数当然是隐式设置这个方法的。我们重新将 identity_decorator 创建为一个类来看看它是如何工作的。

    class IdentityDecorator(object):
        def __init__(self, func):
            self.func = func
    
        def __call__(self):
            self.func()
    
    
    @IdentityDecorator
    def a_function():
        print "I'm a normal function."
    
    a_function()
    # >> I'm a normal function.

    如下是上例中发生的事情:

    • 当 IdentityDecorator 装饰 a_function 时,它的行为就和装饰器函数一样。这个代码片段等价于上例中的装饰器语法: a_function = IdentityDecorator(a_function) 。调用(实例化)该装饰器类时,需将其装饰的函数作为一个实参传递给它。

    • 实例化 IdentityDecorator 之时,会以被装饰的函数作为实参调用初始化函数 __init__ 。本例中,初始化函数所做的事情就是将被装饰函数赋值给一个属性,这样之后就可以通过其它方法进行调用。

    • 最后,调用 a_function (实际上是返回的包装了 a_function 的 IdentityDecorator 对象)之时,会调用对象的 __call__ 方法。这仅是一个同一性装饰器,所以方法只是简单地调用了该类所装饰的函数。

    再次更新一下我们对装饰器的定义!

    装饰器是一个修改函数、方法或者类的可调用对象。

    带参数的装饰器

    有时,需要根据不同的情况改变装饰器的行为。你可以通过传参来做到这一点。

    from functools import wraps
    
    def argumentative_decorator(gift):
        def func_wrapper(func):
            @wraps(func)
            def returned_wrapper(*arg, **kwargs):
                 print "I don't like this " + gift + "you gave me!"
                 return func(gift, *args, **kwargs)
            return returned_wrapper
        return func_wrapper
    
    @argumentative_decorator("sweater")
    def grateful_function(gift):
        print "I love the " + gift + "!Thank you!"
    
    grateful_function()
    # >> I don't like this sweater you gave me!
    # >> I love the sweater! Thank you!

    我们来看看如果不使用装饰器语法这个装饰器函数是如何工作的:

    # If we tried to invoke without an argument:
    grateful_function = argumentative_function(grateful_function)
    
    # But when given an argument, the pattern changes to:
    grateful_function = argumentative_decorator("sweater")(grateful_function)

    需要注意的地方是:当给定参数,首先仅以那些参数调用装饰器—被包装的函数并不在参数中。装饰器调用返回后,装饰器要包装的函数被传递给装饰器初始调用返回的函数(本例中,为 argumentative_decorator("sweater") 的返回值)。

    逐步地:

    1. 解释器到达被装饰函数之处,编译 grateful_function ,并将其绑定到名字’grateful_function’。
    2. 传递参数”sweater”调用 argumentative_decorator ,返回 func_wrapper 。
    3. 以 grateful_function 为参调用 func_wrapper ,返回 returned_wrapper 。
    4. 最后, returned_wrapper 取代原来的函数 grateful_function ,并绑定到名字’grateful_function’ 。

    我想这一过程相比没有装饰器参数理解起来更难一点,但是如果你花些时间将其理解通透,我希望是有意义的。

    带可选参数的装饰器

    有多种方式让装饰器接受可选参数。根据你是想使用位置参数、关键字参数还是两者皆是,需要使用稍微不同的模式。如下我将展示一种接受一个可选关键字参数的方式:

    from functools import wraps
    
    GLOBAL_NAME = "Brian"
    
    def print_name(function=None, name=GLOBAL_NAME):
        def actual_decorator(function):
            @wraps(function)
            def returned_func(*args, **kwargs):
                print "My name is " + name
                return function(*args, **kwargs)
            return returned_func
    
        if not function:    # User passed in a name argument
            def waiting_for_func(function):
                return actual_decorator(function)
            return waiting_for_func
    
        else:
            return actual_decorator(function)
    
    @print_name
    def a_function():
        print "I like the name!"
    
    @print_name(name='Matt')
    def another_function():
        print "Hey, that's new!"
    
    a_function()
    # >> My name is Brian
    # >> I like that name!
    
    another_function()
    # >> My name is Matt
    # >> Hey, that's new!

    如果我们传递关键字参数 name 给 print_name ,那么它的行为就与前一个例子中的 argumentative_decorator 相似。即,首先以 name 为参调用 print_name 。然后,将待包装的函数传递给首次调用返回的函数。

    如果我们没有提供 name 实参, print_name 的行为就与前面我们看到的不带参数的装饰器一样。装饰器仅以待包装的函数作为唯一的参数进行调用。

    print_name 支持两种可能性。它会检查是否收到作为参数的被包装函数。如果没有,则返回函数 waiting_for_func,该函数可以被包装函数作为参数进行调用。如果收到被包装函数作为参数,则跳过中间步骤,直接调用actual_decorator 。

    串接装饰器

    现在来探索一下今天要讲的最后一个装饰器的特性:串接。你可以在任意给定的函数之上堆叠使用多个装饰器, 这种构建函数的方式与使用多重继承构建类相类似。不过最好不要疯狂使用这种特性。

    @print_name('Sam')
    @logging_decorator
    def some_function():
        print "I'm the wrapped function!"
    
    some_function()
    # >> My name is Sam
    # >> The function I modify has been called 1 time(s).
    # >> I'm the wrapped function!

    当你串接使用装饰器时,它们堆叠的顺序是自底向上的。将被包装的函数 some_function 经编译后传递给它之上的第一个装饰器( logging_decorator )。然后第一个装饰器的返回值被传递给第二个装饰器。依此逐个应用链上每个装饰器。

    因为我们使用的两个装饰器都是 print 一个值,然后执行传递给它们的函数,这意味着当调用被包装函数时,链中的最后一个装饰器 print_name 打印输出中的第一行。

    总结

    我认为装饰器最大的好处之一在于让你能够从更高的抽象层次进行思考。假如你开始阅读一个函数定义,看到有一个memoize 装饰器,你立刻就能明白你正在看的是一个使用缓存制表的函数。如果缓存制表的代码包含在函数体内,就会需要额外的脑力进行解析,并且会有引入误解的可能。使用装饰器也允许代码复用,从而节省时间、简化调试,并且使得重构更加容易。

    玩玩装饰器也是一种很好的学习函数式概念(如高阶函数与闭包)的方式。

    我希望本文阅读起来很愉快,并且内容翔实。

    转自:http://youngsterxyf.github.io/2013/01/04/Decorators-and-Functional-Python/

    python装饰器解释:

    今天来讨论一下装饰器。装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

    1. 装饰器入门

    1.1. 需求是怎么来的?

    装饰器的定义很是抽象,我们来看一个小例子。

    1
    2
    3
    4
    def foo():
        print 'in foo()'
     
    foo()

    这是一个很无聊的函数没错。但是突然有一个更无聊的人,我们称呼他为B君,说我想看看执行这个函数用了多长时间,好吧,那么我们可以这样做:

    1
    2
    3
    4
    5
    6
    7
    8
    import time
    def foo():
        start = time.clock()
        print 'in foo()'
        end = time.clock()
        print 'used:', end - start
     
    foo()

    很好,功能看起来无懈可击。可是蛋疼的B君此刻突然不想看这个函数了,他对另一个叫foo2的函数产生了更浓厚的兴趣。

    怎么办呢?如果把以上新增加的代码复制到foo2里,这就犯了大忌了~复制什么的难道不是最讨厌了么!而且,如果B君继续看了其他的函数呢?

    1.2. 以不变应万变,是变也

    还记得吗,函数在Python中是一等公民,那么我们可以考虑重新定义一个函数timeit,将foo的引用传递给他,然后在timeit中调用foo并进行计时,这样,我们就达到了不改动foo定义的目的,而且,不论B君看了多少个函数,我们都不用去修改函数定义了!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import time
     
    def foo():
        print 'in foo()'
     
    def timeit(func):
        start = time.clock()
        func()
        end =time.clock()
        print 'used:', end - start
     
    timeit(foo)

    看起来逻辑上并没有问题,一切都很美好并且运作正常!……等等,我们似乎修改了调用部分的代码。原本我们是这样调用的:foo(),修改以后变成了:timeit(foo)。这样的话,如果foo在N处都被调用了,你就不得不去修改这N处的代码。或者更极端的,考虑其中某处调用的代码无法修改这个情况,比如:这个函数是你交给别人使用的。

    1.3. 最大限度地少改动!

    既然如此,我们就来想想办法不修改调用的代码;如果不修改调用代码,也就意味着调用foo()需要产生调用timeit(foo)的效果。我们可以想到将timeit赋值给foo,但是timeit似乎带有一个参数……想办法把参数统一吧!如果timeit(foo)不是直接产生调用效果,而是返回一个与foo参数列表一致的函数的话……就很好办了,将timeit(foo)的返回值赋值给foo,然后,调用foo()的代码完全不用修改!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #-*- coding: UTF-8 -*-
    import time
     
    def foo():
        print 'in foo()'
     
    # 定义一个计时器,传入一个,并返回另一个附加了计时功能的方法
    def timeit(func):
         
        # 定义一个内嵌的包装函数,给传入的函数加上计时功能的包装
        def wrapper():
            start = time.clock()
            func()
            end =time.clock()
            print 'used:', end - start
         
        # 将包装后的函数返回
        return wrapper
     
    foo = timeit(foo)
    foo()

    这样,一个简易的计时器就做好了!我们只需要在定义foo以后调用foo之前,加上foo = timeit(foo),就可以达到计时的目的,这也就是装饰器的概念,看起来像是foo被timeit装饰了。在在这个例子中,函数进入和退出时需要计时,这被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。与传统编程习惯的从上往下执行方式相比较而言,像是在函数执行的流程中横向地插入了一段逻辑。在特定的业务领域里,能减少大量重复代码。面向切面编程还有相当多的术语,这里就不多做介绍,感兴趣的话可以去找找相关的资料。

    这个例子仅用于演示,并没有考虑foo带有参数和有返回值的情况,完善它的重任就交给你了 :)

    2. Python的额外支持

    2.1. 语法糖

    上面这段代码看起来似乎已经不能再精简了,Python于是提供了一个语法糖来降低字符输入量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import time
     
    def timeit(func):
        def wrapper():
            start = time.clock()
            func()
            end =time.clock()
            print 'used:', end - start
        return wrapper
     
    @timeit
    def foo():
        print 'in foo()'
     
    foo()

    重点关注第11行的@timeit,在定义上加上这一行与另外写foo = timeit(foo)完全等价,千万不要以为@有另外的魔力。除了字符输入少了一些,还有一个额外的好处:这样看上去更有装饰器的感觉。

    2.2. 内置的装饰器

    内置的装饰器有三个,分别是staticmethod、classmethod和property,作用分别是把类中定义的实例方法变成静态方法、类方法和类属性。由于模块里可以定义函数,所以静态方法和类方法的用处并不是太多,除非你想要完全的面向对象编程。而属性也不是不可或缺的,Java没有属性也一样活得很滋润。从我个人的Python经验来看,我没有使用过property,使用staticmethod和classmethod的频率也非常低。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Rabbit(object):
         
        def __init__(self, name):
            self._name = name
         
        @staticmethod
        def newRabbit(name):
            return Rabbit(name)
         
        @classmethod
        def newRabbit2(cls):
            return Rabbit('')
         
        @property
        def name(self):
            return self._name

    这里定义的属性是一个只读属性,如果需要可写,则需要再定义一个setter:

    1
    2
    3
    @name.setter
    def name(self, name):
        self._name = name

    2.3. functools模块

    functools模块提供了两个装饰器。这个模块是Python 2.5后新增的,一般来说大家用的应该都高于这个版本。但我平时的工作环境是2.4 T-T

    2.3.1. wraps(wrapped[, assigned][, updated]): 
    这是一个很有用的装饰器。看过前一篇反射的朋友应该知道,函数是有几个特殊属性比如函数名,在被装饰后,上例中的函数名foo会变成包装函数的名字wrapper,如果你希望使用反射,可能会导致意外的结果。这个装饰器可以解决这个问题,它能将装饰过的函数的特殊属性保留。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import time
    import functools
     
    def timeit(func):
        @functools.wraps(func)
        def wrapper():
            start = time.clock()
            func()
            end =time.clock()
            print 'used:', end - start
        return wrapper
     
    @timeit
    def foo():
        print 'in foo()'
     
    foo()
    print foo.__name__

    首先注意第5行,如果注释这一行,foo.__name__将是'wrapper'。另外相信你也注意到了,这个装饰器竟然带有一个参数。实际上,他还有另外两个可选的参数,assigned中的属性名将使用赋值的方式替换,而updated中的属性名将使用update的方式合并,你可以通过查看functools的源代码获得它们的默认值。对于这个装饰器,相当于wrapper = functools.wraps(func)(wrapper)。

    2.3.2. total_ordering(cls): 
    这个装饰器在特定的场合有一定用处,但是它是在Python 2.7后新增的。它的作用是为实现了至少__lt__、__le__、__gt__、__ge__其中一个的类加上其他的比较方法,这是一个类装饰器。如果觉得不好理解,不妨仔细看看这个装饰器的源代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    53  def total_ordering(cls):
    54      """Class decorator that fills in missing ordering methods"""
    55      convert = {
    56          '__lt__': [('__gt__', lambda self, other: other < self),
    57                     ('__le__', lambda self, other: not other < self),
    58                     ('__ge__', lambda self, other: not self < other)],
    59          '__le__': [('__ge__', lambda self, other: other <= self),
    60                     ('__lt__', lambda self, other: not other <= self),
    61                     ('__gt__', lambda self, other: not self <= other)],
    62          '__gt__': [('__lt__', lambda self, other: other > self),
    63                     ('__ge__', lambda self, other: not other > self),
    64                     ('__le__', lambda self, other: not self > other)],
    65          '__ge__': [('__le__', lambda self, other: other >= self),
    66                     ('__gt__', lambda self, other: not other >= self),
    67                     ('__lt__', lambda self, other: not self >= other)]
    68      }
    69      roots = set(dir(cls)) & set(convert)
    70      if not roots:
    71          raise ValueError('must define at least one ordering operation: < > <= >=')
    72      root = max(roots)       # prefer __lt__ to __le__ to __gt__ to __ge__
    73      for opname, opfunc in convert[root]:
    74          if opname not in roots:
    75              opfunc.__name__ = opname
    76              opfunc.__doc__ = getattr(int, opname).__doc__
    77              setattr(cls, opname, opfunc)
    78      return cls

    本文到这里就全部结束了,有空的话我会整理一个用于检查参数类型的装饰器的源代码放上来,算是一个应用吧 :

    转自:http://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html#2817508

    更多:http://www.codecho.com/understanding-python-decorators/

    http://doc.42qu.com/python/python-closures-and-decorators.html

    http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html

  • 相关阅读:
    先做人,再做事
    当ligerui的grid出现固定列与非固定列不在同一水平线上时,改怎么处理
    权限设计的idea
    ligerUI问题
    在程序出现问题,当找不到错误时,第一时间用try ,catch包括起来
    当页面是本地页面时,通过ajax访问tomcat里的action,传递的参数在action里并不能识别
    好句子
    js Uncaught TypeError: undefined is not a function
    Photoshop学习笔记(一)
    microsoft project 出现不能保存为xls文件时可以按照如下方法解决
  • 原文地址:https://www.cnblogs.com/youxin/p/3383059.html
Copyright © 2011-2022 走看看