python装饰器

作者: 洛克黄瓜 | 来源:发表于2018-11-05 22:04 被阅读2次

    部分细节自己改了点,也加了点自己例子,基本上属于转载。
    转载出处:https://my.oschina.net/leejun2005/blog/477614

    Python的装饰器的英文名叫Decorator,基本上适用的场景就是“装修”:不涉及主流程业务,用于鉴权、审计等副业。

    1.函数

    在python中,函数通过def关键字、函数名和可选的参数列表定义。通过return关键字返回值。我们举例来说明如何定义和调用一个简单的函数:

    def foo():
        return 1
    foo()
    1
    

    方法体(当然多行也是一样的)是必须的,通过缩进来表示,在方法名的后面加上双括号()就能够调用函数(括号里根据情况放入实参)

    2.作用域

    在python中,函数会创建一个新的作用域。python开发者可能会说函数有自己的命名空间,差不多一个意思。这意味着在函数内部碰到一个变量的时候函数会优先在自己的命名空间里面去寻找。让我们写一个简单的函数看一下 本地作用域全局作用域有什么不同:

    a_string = "This is a global variable"
    def foo():
        print locals()
    
    print globals()
    {..., 'a_string': 'This is a global variable'}
    
    foo() # 2
    {}
    

    内置的函数globals返回一个包含所有python解释器知道的变量名称的字典(为了干净和洗的白白的,我省略了python自行创建的一些变量)。在#2我调用了函数 foo 把函数内部本地作用域里面的内容打印出来。我们能够看到,函数foo有自己独立的命名空间,虽然暂时命名空间里面什么都还没有。

    3.变量解析规则

    在python的作用域规则里面,优先在局部作用域里查找,找不到再去找全局作用域。

    a_string = "This is a global variable"
    def foo():
        print a_string # 1
    foo()
    This is a global variable
    

    在#1处,python解释器会尝试查找变量a_string,当然在函数的本地作用域里面是找不到的,所以接着会去上层的作用域里面去查找。
    但是另一方面,假如我们在函数内部给全局变量赋值,结果却和我们想的不一样:

    a_string = "This is a global variable"
    def foo():
        a_string = "test" # 1
        print locals()
    foo()
    {'a_string': 'test'}
    a_string # 2
    'This is a global variable'
    
    lst = []
    def foo1():
        lst.append(233)
        print locals()
    foo1() # 3
    {}
    lst # 4
    [233]
    
    • 我们能够看到,全局变量能够被访问到(如果是可变数据类型(像list,dict这些)甚至能够被更改)但是赋值不行。
    • 在函数内部的#1处,我们实际上新创建了一个局部变量,隐藏全局作用域中的同名变量。我们可以通过打印出局部命名空间中的内容得出这个结论。我们也能看到在#2处打印出来的变量a_string的值并没有改变。
    • 在#3处调用函数会改动全局变量lst。因为lst没有赋值操作,局部作用域也是空的。
      额外多说点,如果在函数内就是想要对全局变量直接赋值的话,可以在函数内注明使用的是全局变量:
    a_string = "This is a global variable"
    def foo():
        global a_string
        a_string = "this is a local variable" # 1
        print locals()
    foo()
    {}
    a_string # 2
    'this is a local variable'
    

    4.变量生存周期

    值得注意的一个点是,变量不仅是生存在一个个的命名空间内,他们都有自己的生存周期,请看下面这个例子:

    def foo():
        x = 1
    foo()
    print x # 1
    #Traceback (most recent call last):
    #NameError: name 'x' is not defined
    

    1处发生的错误不仅仅是因为作用域规则导致的(尽管这是抛出了NameError的错误的原因)它还和python以及其它很多编程语言中函数调用实现的机制有关。在这个地方这个执行时间点并没有什么有效的语法让我们能够获取变量x的值,因为它这个时候压根不存在!

    函数foo的命名空间随着函数调用开始而开始,结束而销毁。

    5.函数参数

    python允许我们向函数传递参数,参数会变成本地变量存在于函数内部。

    def foo(x):
        print locals()
    foo(1)
    {'x': 1}
    

    在Python里有很多的方式来定义和传递参数,完整版可以查看 python官方文档。我们这里简略的说明一下:函数的参数可以是必须的位置参数或者是可选的命名,默认参数。

    def foo(x, y=0): # 1
        return x - y
    foo(3, 1) # 2
    2
    foo(3) # 3
    3
    foo() # 4
    #Traceback (most recent call last):
    #TypeError: foo() takes at least 1 argument (0 given)
    foo(y=1, x=3) # 5
    2
    

    在#1处我们定义了函数foo,它有一个位置参数x和一个命名参数y。在#2处我们能够通过常规的方式来调用函数,尽管有一个命名参数,但参数依然可以通过位置传递给函数。在调用函数的时候,对于命名参数y我们也可以完全不管就像#3处所示的一样。如果命名参数没有接收到任何值的话,python会自动使用声明的默认值也就是0。需要注意的是我们不能省略第一个位置参数x, 否则的话就会像#4处所示发生错误。
    函数的参数可以有名称和位置。这意味着在函数的定义和调用的时候会稍稍在理解上有点儿不同。我们可以给只定义了位置参数的函数传递命名参数(实参),反之亦然!如果觉得不够可以查看官方文档

    6. 嵌套函数

    Python允许创建嵌套函数。这意味着我们可以在函数里面定义函数而且现有的作用域和变量生存周期依旧适用。

    def outer():
        x = 1
        def inner():
            print x # 1
        return inner() # 2
    outer()
    1
    

    这个例子有一点儿复杂,但是看起来也还行。想一想在#1发生了什么:python解释器需找一个叫x的本地变量,查找失败之后会继续在上层的作用域里面寻找,这个上层的作用域定义在另外一个函数里面。对函数outer来说,变量x是一个本地变量,但是如先前提到的一样,函数inner可以访问封闭的作用域(至少可以读和修改)。在#2处,我们调用函数inner,非常重要的一点是,inner也仅仅是一个遵循python变量解析规则的变量名,python解释器会优先在outer的作用域里面对变量名inner查找匹配的变量.

    7. 函数是python世界里的一级类对象

    一等公民,不想Java那样函数都是放在类里的

    显而易见,在python里函数和其他东西一样都是对象。(此处应该大声歌唱)啊!包含变量的函数,你也并不是那么特殊!

    issubclass(int, object) # all objects in Python inherit from a common baseclass
    #True
    def foo():
        pass
    foo.__class__ # 1
    #<type 'function'>
    issubclass(foo.__class__, object)
    #True
    

    你也许从没有想过,你定义的函数居然会有属性。没办法,函数在python里面就是对象,和其他的东西一样,也许这样描述会太学院派太官方了点:在python里,函数只是一些普通的值而已和其他的值一毛一样。这就是说你尅一把函数想参数一样传递给其他的函数或者说从函数了里面返回函数!如果你从来没有这么想过,那看看下面这个例子:

    def add(x, y):
        return x + y
    def sub(x, y):
        return x - y
    def apply(func, x, y): # 1
        return func(x, y) # 2
    apply(add, 2, 1) # 3
    3
    apply(sub, 2, 1)
    1
    

    你们也许看到过这样的行为:“python把频繁要用的操作变成函数作为参数进行使用,像通过传递一个函数给内置排序函数的key参数从而来自定义排序规则。那把函数当做返回值回事这样的情况呢:

    def outer():
        def inner():
            print "Inside inner"
        return inner # 1
    foo = outer() #2
    foo
    #<function inner at 0x...>
    foo()
    #Inside inner
    

    这个例子看起来也许会更加的奇怪。在#1处我把恰好是函数标识符的变量inner作为返回值返回出来。这并没有什么特殊的语法:”把函数inner返回出来,否则它根本不可能会被调用到。“还记得变量的生存周期吗?每次函数outer被调用的时候,函数inner都会被重新定义,如果它不被当做变量返回的话,每次执行过后它将不复存在。

    在#2处我们捕获住返回值 – 函数inner,将它存在一个新的变量foo里。我们能够看到,当对变量foo进行求值,它确实包含函数inner,而且我们能够对他进行调用。初次看起来可能会觉得有点奇怪,但是理解起来并不困难是吧。坚持住,因为奇怪的转折马上就要来了

    8. 闭包(被函数记住的封闭作用域)

    我们先不急着定义什么是闭包,先来看看一段代码,仅仅是把上一个例子简单的调整了一下:

    def outer():
        x = 1
        def inner():
            print x # 1
        return inner
    foo = outer()
    foo.func_closure
    #(<cell at 0x...: int object at 0x...>,)
    

    在上一个例子中我们了解到,inner作为一个函数被outer返回,保存在一个变量foo,并且我们能够对它进行调用foo()。不过它会正常的运行吗?我们先来看看作用域规则。

    所有的东西都在python的作用域规则下进行工作:“x是函数outer里的一个局部变量。当函数inner在#1处打印x的时候,python解释器会在inner内部查找相应的变量,当然会找不到,所以接着会到封闭作用域里面查找,并且会找到匹配。

    但是从变量的生存周期来看,该怎么理解呢?我们的变量x是函数outer的一个本地变量,这意味着只有当函数outer正在运行的时候才会存在。根据我们已知的python运行模式,我们没法在函数outer返回之后继续调用函数inner,在函数inner被调用的时候,变量x早已不复存在,可能会发生一个运行时错误。

    万万没想到,返回的函数inner居然能够正常工作。Python支持一个叫做函数闭包的特性,用人话来讲就是,嵌套定义在非全局作用域里面的函数能够记住它在被定义的时候它所处的封闭命名空间。这能够通过查看函数的func_closure属性得出结论,这个属性里面包含封闭作用域里面的值(只会包含被捕捉到的值,比如x,如果在outer里面还定义了其他的值,封闭作用域里面是不会有的)
    记住,每次函数outer被调用的时候,函数inner都会被重新定义。现在变量x的值不会变化,所以每次返回的函数inner会是同样的逻辑,假如我们稍微改动一下呢?

    def outer(x):
        def inner():
            print x # 1
        return inner
    print1 = outer(1)
    print2 = outer(2)
    print1()
    1
    print2()
    2
    

    从这个例子中你能够看到闭包 – 被函数记住的封闭作用域 – 能够被用来创建自定义的函数,本质上来说是一个硬编码的参数。事实上我们并不是传递参数1或者2给函数inner,我们实际上是创建了能够打印各种数字的各种自定义版本。

    闭包单独拿出来就是一个非常强大的功能, 在某些方面,你也许会把它当做一个类似于面向对象的技术:outer像是给inner服务的构造器,x像一个私有变量。使用闭包的方式也有很多:你如果熟悉python内置排序方法的参数key,你说不定已经写过一个lambda方法在排序一个列表的列表的时候基于第二个元素而不是第一个。现在你说不定也可以写一个itemgetter方法,接收一个索引值来返回一个完美的函数,传递给排序函数的参数key。

    不过,我们现在不会用闭包做这么low的事(⊙o⊙)…!相反,让我们再爽一次,写一个高大上的装饰器!

    9. 装饰器

    装饰器其实就是一个闭包,把一个函数当做参数然后返回一个替代版函数。我们一步步从简到繁来瞅瞅:

    def outer(some_func):
        def inner():
            print "before some_func"
            ret = some_func() # 1
            return ret + 1
        return inner
    def foo():
        return 1
    decorated = outer(foo) # 2
    decorated()
    #before some_func
    #2
    

    仔细看看上面这个装饰器的例子。们定义了一个函数outer,它只有一个some_func的参数,在他里面我们定义了一个嵌套的函数inner。inner会打印一串字符串,然后调用some_func,在#1处得到它的返回值。在outer每次调用的时候some_func的值可能会不一样,但是不管some_func的之如何,我们都会调用它。最后,inner返回some_func() + 1的值 – 我们通过调用在#2处存储在变量decorated里面的函数能够看到被打印出来的字符串以及返回值2,而不是期望中调用函数foo得到的返回值1。

    我们可以认为变量decorated是函数foo的一个装饰版本,一个加强版本。事实上如果打算写一个有用的装饰器的话,我们可能会想愿意用装饰版本完全取代原先的函数foo,这样我们总是会得到我们的”加强版“foo。想要达到这个效果,完全不需要学习新的语法,简单地赋值给变量foo就行了:

    foo = outer(foo)
    foo # doctest: +ELLIPSIS
    #<function inner at 0x...>
    

    现在,任何怎么调用都不会牵扯到原先的函数foo,都会得到新的装饰版本的foo。

    假设有如下函数:

    from datetime import datetime
    def now():
        print str(datetime.now())
    f = now
    f()
    #'2018-11-11 14:06:07.600773'
    

    现在假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

    本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:

    def log(func):
        def wrapper(*args, **kwargs):
            print 'call {}'.format(func.__name__)
            ret = func(*args, **kwargs)
            return ret
        return wrapper
    def pp(a):
        print a
    pp = log(pp)
    pp(2)
    #call pp
    #2
    

    观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。

    10.使用 @ 标识符将装饰器应用到函数

    在上一节的例子里我们是将原本的方法用装饰后的方法代替:

    pp = log(pp)
    

    Python2.4支持使用标识符@将装饰器应用在函数上,只需要在函数的定义前加上@和装饰器的名称。

    @log
    def pp(a):
        print a
    

    需要明白的是,这样的做法和先前简单的用包装方法替代原有方法是一模一样的, python只是加了一些语法糖让装饰的行为更加的直接明确和优雅一点。
    多个decorator怎么处理呢?

    @decorator_one
    @decorator_two
    def func():
        pass
    

    相当于:

    func = decorator_one(decorator_two(func))
    

    本身带参数的decorator:

    @decorator(arg1, arg2)
    def func():
        pass
    

    相当于

    func = decorator(arg1,arg2)(func)
    

    这意味着decorator(arg1, arg2)这个函数需要返回一个“真正的decorator”。

    11. *args and **kwargs

    decorator(arg1, arg2)是一个有用的装饰器,但是由于硬编码的原因它只能应用在一类具体的方法上,这类方法接收两个参数,传递给闭包捕获的函数。如果我们想实现一个能够应用在任何方法上的装饰器要怎么做呢?再比如,如果我们要实现一个能应用在任何方法上的类似于计数器的装饰器,不需要改变原有方法的任何逻辑。这意味着装饰器能够接受拥有任何签名的函数作为自己的被装饰方法,同时能够用传递给它的参数对被装饰的方法进行调用。
    非常巧合的是Python正好有支持这个特性的语法。可以阅读 Python Tutorial 获取更多的细节。当定义函数的时候使用了,意味着那些通过位置传递的参数将会被放在带有前缀的变量中,
    arg在函数内是元组类型。 所以:

    def one(*args):
        print args # 1
    one()
    #()
    one(1, 2, 3)
    #(1, 2, 3)
    def two(x, y, *args): # 2
        print x, y, args
    two('a', 'b', 'c')
    #a b ('c',)
    
    def add(x, y):
        return x + y
    lst = [1,2]
    add(lst[0], lst[1]) # 1
    3
    add(*lst) # 2
    3
    

    接下来提到的会稍多更复杂一点,代表着键值对的参数字典,和*所代表的意义相差无几,也很简单对不对:

    def foo(**kwargs):
        print kwargs
    foo()
    #{}
    foo(x=1, y=2)
    #{'y': 2, 'x': 1}
    

    当我们定义一个函数的时候,我们能够用kwargs来表明,所有未被捕获的关键字参数都应该存储在kwargs的字典中。如前所诉,args、kwargs并不是python语法的一部分,但在定义函数的时候,使用这样的变量名算是一个不成文的约定。和一样,我们同样可以在定义或者调用函数的时候使用*。

    dct = {'x': 1, 'y': 2}
    def bar(x, y):
        return x + y
    bar(**dct)
    #3
    

    12. 更通用的装饰器

    有了这招新的技能,我们随随便便就可以写一个能够记录下传递给函数参数的装饰器了。先来个简单地把日志输出到界面的例子

    def logger(func):
        def inner(*args, **kwargs): #1
            print "Arguments were: %s, %s" % (args, kwargs)
            return func(*args, **kwargs) #2
        return inner
    

    请注意我们的函数inner,它能够接受任意数量和类型的参数并把它们传递给被包装的方法,这让我们能够用这个装饰器来装饰任何方法

    @logger
    def foo1(x, y=1):
        return x * y
    @logger
    def foo2():
        return 2
    foo1(5, 4)
    #Arguments were: (5, 4), {}
    #20
    foo1(1)
    #Arguments were: (1,), {}
    #1
    foo2()
    #Arguments were: (), {}
    #2
    

    13. 带参数的装饰器

    如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:

    def log(text):
        def decorator(func):
            def wrapper(*args, **kwargs):
                print '{} {}'.format(text, func.__name__)
                ret = func(*args, **kwargs)
                return ret
            return wrapper
        return decorator
    

    这个3层嵌套的decorator用法如下:

    @log('execute')
    def now():
        print '2013-12-25'
    
    now()
    #execute now():
    #2013-12-25
    

    和两层嵌套的decorator相比,3层嵌套的效果是这样的:

    now = log('execute')(now)
    

    来剖析上面的语句,首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。

    14.装饰器的副作用

    以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有name等属性,但你去看经过decorator装饰之后的函数,它们的name已经从原来的'now'变成了'wrapper':

    >>> now.__name__
    'wrapper'
    

    因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的name等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

    def log(func):
        def wrapper(*args, **kw):
            print 'call %s():' % func.__name__
            return func(*args, **kw)
        wrapper.__name__ = func.__name__
        return wrapper
    

    其实不需要编写wrapper.name = func.name这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

    import functools
    
    def log(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print 'call %s():' % func.__name__
            return func(*args, **kw)
        return wrapper
    

    或者针对带参数的decorator:

    from functools import wraps
    def log(text):
        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                print '{} {}'.format(text, func.__name__)
                ret = func(*args, **kwargs)
                return ret
            return wrapper
        return decorator
    

    import functools是导入functools模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()的前面加上@functools.wraps(func)即可。

    当然,即使是你用了functools的wraps,也不能完全消除这样的副作用。你会发现,即使是你你用了functools的wraps,你在用getargspec时,参数也不见了。要修正这一问题,我们还得用Python的反射来解决,当然,我相信大多数人的程序都不会去getargspec。所以,用functools的wraps应该够用了。

    class式的 Decorator

    把一个类用来做装饰器用,其实让类本身可调用(含call方法)就是跟函数形式的装饰器没差啊。

    class MyDecorator(object):
     
        def __init__(self, fn):
            print "inside myDecorator.__init__()"
            self.fn = fn
     
        def __call__(self):
            self.fn()
            print "inside myDecorator.__call__()"
     
    @MyDecorator
    def aFunction():
        print "inside aFunction()"
     
    print "Finished decorating aFunction()"
     
    aFunction()
     
    # 输出:
    # inside myDecorator.__init__()
    # Finished decorating aFunction()
    # inside aFunction()
    # inside myDecorator.__call__()
    

    1)一个是init(),这个方法是在我们给某个函数decorator时被调用,所以,需要有一个fn的参数,也就是被decorator的函数。
    2)一个是call(),这个方法是在我们调用被decorator函数时被调用的。
    上面输出可以看到整个程序的执行顺序。

    这看上去要比“函数式”的方式更易读一些。

    上面这段代码中,我们需要注意这几点:

    1)如果decorator有参数的话,init() 成员就不能传入fn了,而fn是在call的时候传入的。

    • 这里对于decorator有参数的情况怎么处理不知道该如何处理了。有兴趣的朋友去试试

    16. 一些decorator的示例

    • 1.函数调用做缓存
      整个网上都用这个例子做decorator的经典范例
    from functools import wraps
    def memo(fn):
        cache = {}
     
        @wraps(fn)
        def wrapper(*args):
            result = cache.get(args)
            if result is None:
                result = fn(*args)
                cache[args] = result
            return result
     
        return wrapper
     
    @memo
    def fib(n):
        if n < 2:
            return n
        return fib(n - 1) + fib(n - 2)
    

    上面这个例子中,是一个斐波拉契数例的递归算法。我们知道,这个递归是相当没有效率的,因为会重复调用。比如:我们要计算fib(5),于是其分解成fib(4) + fib(3),而fib(4)分解成fib(3)+fib(2),fib(3)又分解成fib(2)+fib(1)…… 你可看到,基本上来说,fib(3), fib(2), fib(1)在整个递归过程中被调用了两次。

    而我们用decorator,在调用函数前查询一下缓存,如果没有才调用了,有了就从缓存中返回值。一下子,这个递归从二叉树式的递归成了线性的递归。

    另外一个常见的例子是爬虫里的 URL Cache:

    import urllib
    
    def web_lookup(url, saved={}):
        if url in saved:
            return saved[url]
        page = urllib.urlopen(url).read()
        saved[url] = page
        return page
    

    可以这样写:

    @cache
    def web_lookup(url):
        return urllib.urlopen(url).read()
    
    def cache(func):
        saved = {}
        @wraps(func)
        def newfunc(*args):
            if args in saved:
                return saved[args]
            result = func(*args)
            saved[args] = result
            return result
        return newfunc
    
    • 2.Profiler的例子
    import cProfile, pstats, StringIO
     
    def profiler(func):
        def wrapper(*args, **kwargs):
            datafn = func.__name__ + ".profile" # Name the data file
            prof = cProfile.Profile()
            retval = prof.runcall(func, *args, **kwargs)
            #prof.dump_stats(datafn)
            s = StringIO.StringIO()
            sortby = 'cumulative'
            ps = pstats.Stats(prof, stream=s).sort_stats(sortby)
            ps.print_stats()
            print s.getvalue()
            return retval
     
        return wrapper
    
    • 3.注册回调函数
      下面这个示例展示了通过URL的路由来调用相关注册的函数示例:
    class MyApp():
        def __init__(self):
            self.func_map = {}
     
        def register(self, name):
            def func_wrapper(func):
                self.func_map[name] = func
                return func
            return func_wrapper
     
        def call_method(self, name=None):
            func = self.func_map.get(name, None)
            if func is None:
                raise Exception("No function registered against - " + str(name))
            return func()
     
    app = MyApp()
     
    @app.register('/')
    def main_page_func():
        return "This is the main page."
     
    @app.register('/next_page')
    def next_page_func():
        return "This is the next page."
     
    print app.call_method('/')
    print app.call_method('/next_page')
    

    注意:
    1)上面这个示例中,用类的实例来做decorator。
    2)decorator类中没有call(),但是wrapper返回了原函数。所以,原函数没有发生任何变化。

    • 4.给函数打日志
      下面这个示例演示了一个logger的decorator,这个decorator输出了函数名,参数,返回值,和运行时间。
    from functools import wraps
    def logger(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            ts = time.time()
            result = fn(*args, **kwargs)
            te = time.time()
            print "function      = {0}".format(fn.__name__)
            print "    arguments = {0} {1}".format(args, kwargs)
            print "    return    = {0}".format(result)
            print "    time      = %.6f sec" % (te-ts)
            return result
        return wrapper
     
    @logger
    def multipy(x, y):
        return x * y
     
    @logger
    def sum_num(n):
        s = 0
        for i in xrange(n+1):
            s += i
        return s
     
    print multipy(2, 10)
    print sum_num(100)
    print sum_num(10000000)
    

    上面那个打日志还是有点粗糙,让我们看一个更好一点的(带log level参数的):

    import inspect
    def get_line_number():
        return inspect.currentframe().f_back.f_back.f_lineno
     
    def logger(loglevel):
        def log_decorator(fn):
            @wraps(fn)
            def wrapper(*args, **kwargs):
                ts = time.time()
                result = fn(*args, **kwargs)
                te = time.time()
                print "function   = " + fn.__name__,
                print "    arguments = {0} {1}".format(args, kwargs)
                print "    return    = {0}".format(result)
                print "    time      = %.6f sec" % (te-ts)
                if (loglevel == 'debug'):
                    print "    called_from_line : " + str(get_line_number())
                return result
            return wrapper
        return log_decorator
    
    @logger('debug')
    def multipy(x, y):
        return x * y
    

    但是,上面这个带log level参数的有两具不好的地方,
    1) loglevel不是debug的时候,还是要计算函数调用的时间。
    2) 不同level的要写在一起,不易读。

    import inspect
     
    def advance_logger(loglevel):
     
        def get_line_number():
            return inspect.currentframe().f_back.f_back.f_lineno
     
        def _basic_log(fn, result, *args, **kwargs):
            print "function   = " + fn.__name__,
            print "    arguments = {0} {1}".format(args, kwargs)
            print "    return    = {0}".format(result)
     
        def info_log_decorator(fn):
            @wraps(fn)
            def wrapper(*args, **kwargs):
                result = fn(*args, **kwargs)
                _basic_log(fn, result, args, kwargs)
            return wrapper
     
        def debug_log_decorator(fn):
            @wraps(fn)
            def wrapper(*args, **kwargs):
                ts = time.time()
                result = fn(*args, **kwargs)
                te = time.time()
                _basic_log(fn, result, args, kwargs)
                print "    time      = %.6f sec" % (te-ts)
                print "    called_from_line : " + str(get_line_number())
            return wrapper
     
        if loglevel is "debug":
            return debug_log_decorator
        else:
            return info_log_decorator
    

    你可以看到两点,
    1)我们分了两个log level,一个是info的,一个是debug的,然后我们在外尾根据不同的参数返回不同的decorator。
    2)我们把info和debug中的相同的代码抽到了一个叫_basic_log的函数里,DRY原则。

      1. 一个MySQL的Decorator
        转载作者用过下面这个例子于工作中,可以将sql语句作为装饰器本身的参数来达到函数仅仅关注对sql查询结果处理的效果
    import umysql
    from functools import wraps
     
    class Configuraion:
        def __init__(self, env):
            if env == "Prod":
                self.host    = "coolshell.cn"
                self.port    = 3306
                self.db      = "coolshell"
                self.user    = "coolshell"
                self.passwd  = "fuckgfw"
            elif env == "Test":
                self.host   = 'localhost'
                self.port   = 3300
                self.user   = 'coolshell'
                self.db     = 'coolshell'
                self.passwd = 'fuckgfw'
     
    def mysql(sql):
     
        _conf = Configuraion(env="Prod")
     
        def on_sql_error(err):
            print err
            sys.exit(-1)
     
        def handle_sql_result(rs):
            if rs.rows > 0:
                fieldnames = [f[0] for f in rs.fields]
                return [dict(zip(fieldnames, r)) for r in rs.rows]
            else:
                return []
     
        def decorator(fn):
            @wraps(fn)
            def wrapper(*args, **kwargs):
                mysqlconn = umysql.Connection()
                mysqlconn.settimeout(5)
                mysqlconn.connect(_conf.host, _conf.port, _conf.user, \
                                  _conf.passwd, _conf.db, True, 'utf8')
                try:
                    rs = mysqlconn.query(sql, {})
                except umysql.Error as e:
                    on_sql_error(e)
     
                data = handle_sql_result(rs)
                kwargs["data"] = data
                result = fn(*args, **kwargs)
                mysqlconn.close()
                return result
            return wrapper
     
        return decorator
     
    @mysql(sql = "select * from coolshell" )
    def get_coolshell(data):
        ... ...
        ... ..
    
    • 6.线程异步
      下面是个非常简单的异步执行的decorator,注意,异步处理并不简单,下面只是一个示例。
    from threading import Thread
    from functools import wraps
     
    def async(func):
        @wraps(func)
        def async_func(*args, **kwargs):
            func_hl = Thread(target = func, args = args, kwargs = kwargs)
            func_hl.start()
            return func_hl
     
        return async_func
     
    if __name__ == '__main__':
        from time import sleep
     
        @async
        def print_somedata():
            print 'starting print_somedata'
            sleep(2)
            print 'print_somedata: 2 sec passed'
            sleep(2)
            print 'print_somedata: 2 sec passed'
            sleep(2)
            print 'finished print_somedata'
     
        def main():
            print_somedata()
            print 'back in main'
            print_somedata()
            print 'back in main'
     
        main()
    
    • 7.超时函数
      这个函数的作用在于可以给任意可能会hang住的函数添加超时功能,这个功能在编写外部API调用 、网络爬虫、数据库查询的时候特别有用。

    timeout装饰器的代码如下:

    # coding=utf-8
    # 测试utf-8编码
    import sys
    
    reload(sys)
    sys.setdefaultencoding('utf-8')
    
    import signal, functools
    
    class TimeoutError(Exception): pass
    
    def timeout(seconds, error_message="Timeout Error: the cmd 30s have not finished."):
        def decorated(func):
            result = ""
    
            def _handle_timeout(signum, frame):
                global result
                result = error_message
                raise TimeoutError(error_message)
    
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                global result
                signal.signal(signal.SIGALRM, _handle_timeout)
                signal.alarm(seconds)
    
                try:
                    result = func(*args, **kwargs)
                finally:
                    signal.alarm(0)
                    return result
                return result
    
            return wrapper
    
        return decorated
    
    @timeout(2)  # 限定下面的slowfunc函数如果在2s内不返回就强制抛TimeoutError Exception结束
    def slowfunc(sleep_time):
        a = 1
        import time
        time.sleep(sleep_time)
        return a
    
    # slowfunc(3) #sleep 3秒,正常返回 没有异常
    
    print slowfunc(11)  # 被终止
    
    • 8.Trace函数

    有时候出于演示目的或者调试目的,我们需要程序运行的时候打印出每一步的运行顺序 和调用逻辑。类似写bash的时候的bash -x调试功能,然后Python解释器并没有内置这个有用的功能,那么我们就“自己动手,丰衣足食”。
    这个例子看出转载作者对源码还是挺了解的

    Trace装饰器的代码如下:

    # coding=utf-8
    # 测试utf-8编码
    import sys
    reload(sys)
    sys.setdefaultencoding('utf-8')
    
    import sys,os,linecache
    def trace(f):
      def globaltrace(frame, why, arg):
        if why == "call": return localtrace
        return None
      def localtrace(frame=1, why=2, arg=4):
        if why == "line":
          # record the file name and line number of every trace
          filename = frame.f_code.co_filename
          lineno = frame.f_lineno
          bname = os.path.basename(filename)
          print "{}({}): {}".format(  bname,
                        lineno,
                        linecache.getline(filename, lineno)),
        return localtrace
      def _f(*args, **kwds):
        sys.settrace(globaltrace)
        result = f(*args, **kwds)
        sys.settrace(None)
        return result
      return _f
    
    @trace
    def xxx():
      a=1
      print a
      print 22
      print 333
    
    xxx() #调用
    
    #######################################
    C:\Python27\python.exe F:/sourceDemo/flask/study/com.test.bj/t2.py
    t2.py(31):   a=1
    t2.py(32):   print a
    1
    t2.py(33):   print 22
    22
    t2.py(34):   print 333
    333
    
    Process finished with exit code 0
    
    • 9.单例模式
    # coding=utf-8
    # 测试utf-8编码
    # 单例装饰器
    import sys
    reload(sys)
    sys.setdefaultencoding('utf-8')
    
    # 使用装饰器实现简单的单例模式
    def singleton(cls):
        instances = dict()  # 初始为空
        def _singleton(*args, **kwargs):
            if cls not in instances:  #如果不存在, 则创建并放入字典
                instances[cls] = cls(*args, **kwargs)
            return instances[cls]
        return _singleton
    
    @singleton
    class Test(object):
        pass
    if __name__ == '__main__':
        t1 = Test()
        t2 = Test()
        # 两者具有相同的地址
        print t1
        print t2
    
    • 10.LRUCache
      下面要分享的这个LRUCache不是我做的,是github上的一个库,我们在实际环境中有用到。

    先来说下这个概念,cache的意思就是缓存,LRU就是Least Recently Used,即最近最少使用,是一种内存管理算法。总结来说这就是一种缓存方法,基于时间和容量。
    一般在简单的python程序中,遇到需要处理缓存的情况时最简单的方式,声明一个全局的dict就能解决(在python中应尽量避免使用全局变量)。但是只是简单情况,这种情况会带来的问题就是内存泄漏,因为可能会出现一直不命中的情况。
    由此导致的一个需求就是,你要设定这个dict的最大容量,防止发生泄漏。但仅仅是设定最大容量是不够的,设想当你的dict变量已被占满,还是没有命中,该如何处理。
    这时就需要加一个失效时间了。如果在指定失效期内没有使用到该缓存,则删除。

    综述上面的需求和功能就是LRUCache干的事了。不过这份代码做了更进一层的封装,可以让你直接把缓存功能做为一个装饰器来用。具体实现可以去参考代码,别人实现之后看起来并不复杂 :)

    from lru import lru_cache_function
    
    @lru_cache_function(max_size=1024, expiration=15*60)
    def f(x):
        print "Calling f(" + str(x) + ")"
        return x
    
    f(3) # This will print "Calling f(3)", will return 3
    f(3) # This will not print anything, but will return 3 (unless 15 minutes have passed between the first and second function call).
    

    代码: https://github.com/the5fire/Python-LRU-cache/blob/master/lru.py

    从python3.2开始内置在functools了lru_cache的功能,说明这个需求是很普遍的。

    相关文章

      网友评论

        本文标题:python装饰器

        本文链接:https://www.haomeiwen.com/subject/mymexqtx.html