俄罗斯贵宾会-俄罗斯贵宾会官网
做最好的网站

Python函数篇:装饰器【俄罗斯贵宾会】

本章结构:

装饰器本质上是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等应用场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

1.理解装饰器的前提准备

严格来说,装饰器只是语法糖,装饰器是可调用的对象,可以像常规的可调用对象那样调用,特殊的地方是装饰器的参数是一个函数

2.装饰器:无参/带参的被装饰函数,无参/带参的装饰函数

现在有一个新的需求,希望可以记录下函数的执行时间,于是在代码中添加日志代码:

3.装饰器的缺点

import time
#遵守开放封闭原则
def foo():
    start = time.time()
    # print(start)  # 1504698634.0291758从1970年1月1号到现在的秒数,那年Unix诞生
    time.sleep(3)
    end = time.time()
    print('spend %s'%(end - start))
foo()

4.python3的内置装饰器

bar()、bar2()也有类似的需求,怎么做?再在bar函数里调用时间函数?这样就造成大量雷同的代码,为了减少重复写代码,我们可以这样做,重新定义一个函数:专门设定时间:

5.本文参考

import time
def show_time(func):
    start_time=time.time()
    func()
    end_time=time.time()
    print('spend %s'%(end_time-start_time))


def foo():
    print('hello foo')
    time.sleep(3)

show_time(foo)

 

但是这样的话,你基础平台的函数修改了名字,容易被业务线的人投诉的,因为我们每次都要将一个函数作为参数传递给show_time函数。而且这种方式已经破坏了原有的代码逻辑结构,之前执行业务逻辑时,执行运行foo(),但是现在不得不改成show_time(foo)。那么有没有更好的方式的呢?当然有,答案就是装饰器。

理解装饰器的前提:1.所有东西都是对象(函数可以当做对象传递) 2.闭包

def show_time(f):
    def inner():
        start = time.time()
        f()
        end = time.time()
        print('spend %s'%(end - start))
    return inner

@show_time #foo=show_time(f)
def foo():
    print('foo...')
    time.sleep(1)
foo()

def bar():
    print('bar...')
    time.sleep(2)
bar()

闭包的概念:
1)函数嵌套
2)内部函数使用外部函数的变量
3)外部函数的返回值为内部函数

输出结果:

下面写一个最为简单的闭包的例子:

foo...
spend 1.0005607604980469
bar...
1 def test(name):
2     def test_in():
3         print(name)
4     return test_in
5 
6 func = test('whyz')
7 func()

函数show_time就是装饰器,它把真正的业务方法f包裹在函数里面,看起来像foo被上下时间函数装饰了。在这个例子中,函数进入和退出时 ,被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。

装饰器的原型:

@符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作

 1 import time
 2 def showtime(func):
 3     def wrapper():
 4         start_time = time.time()
 5         func()
 6         end_time = time.time()
 7         print('spend is {}'.format(end_time - start_time))
 8 
 9     return wrapper
10 
11 def foo():
12     print('foo..')
13     time.sleep(3)
14 
15 foo = showtime(foo)
16 foo()

装饰器在Python使用如此方便都要归因于Python的函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。

不带参数的装饰器:(装饰器,被装饰函数都不带参数)

装饰器有2个特性,一是可以把被装饰的函数替换成其他函数, 二是可以在加载模块时候立即执行

 1 import time
 2 def showtime(func):
 3     def wrapper():
 4         start_time = time.time()
 5         func()
 6         end_time = time.time()
 7         print('spend is {}'.format(end_time - start_time))
 8 
 9     return wrapper
10 
11 @showtime  #foo = showtime(foo)
12 def foo():
13     print('foo..')
14     time.sleep(3)
15 
16 @showtime #doo = showtime(doo)
17 def doo():
18     print('doo..')
19     time.sleep(2)
20 
21 foo()
22 doo()
def decorate(func):
    print('running decorate', func)
    def decorate_inner():
        print('running decorate_inner function')
        return func()
    return decorate_inner

@decorate
def func_1():
    print('running func_1')

if __name__ == '__main__':
    print(func_1)
    #running decorate <function func_1 at 0x000001904743DEA0>
    # <function decorate.<locals>.decorate_inner at 0x000001904743DF28>
    func_1()
    #running decorate_inner function
    # running func_1

带参数的被装饰的函数

通过args 和 *kwargs 传递被修饰函数中的参数

 1 import time
 2 def showtime(func):
 3     def wrapper(a, b):
 4         start_time = time.time()
 5         func(a,b)
 6         end_time = time.time()
 7         print('spend is {}'.format(end_time - start_time))
 8 
 9     return wrapper
10 
11 @showtime #add = showtime(add)
12 def add(a, b):
13     print(a+b)
14     time.sleep(1)
15 
16 @showtime #sub = showtime(sub)
17 def sub(a,b):
18     print(a-b)
19     time.sleep(1)
20 
21 add(5,4)
22 sub(3,2)

 

带参数的装饰器(装饰函数),

def decorate(func):
    def decorate_inner(*args, **kwargs):
        print(type(args), type(kwargs))
        print('args', args, 'kwargs', kwargs)
        return func(*args, **kwargs)
    return decorate_inner

@decorate
def func_1(*args, **kwargs):
    print(args, kwargs)

if __name__ == '__main__':
    func_1('1', '2', '3', para_1='1', para_2='2', para_3='3')

#返回结果
#<class 'tuple'> <class 'dict'>
# args ('1', '2', '3') kwargs {'para_1': '1', 'para_2': '2', 'para_3': '3'}
# ('1', '2', '3') {'para_1': '1', 'para_2': '2', 'para_3': '3'}

俄罗斯贵宾会,实际是对原有装饰器的一个函数的封装,并返回一个装饰器(一个含有参数的闭包函数),
当使用@time_logger(3)调用的时候,Python能发现这一层封装,并将参数传递到装饰器的环境去

 

 1 import time
 2 def time_logger(flag = 0):
 3     def showtime(func):
 4         def wrapper(a, b):
 5             start_time = time.time()
 6             func(a,b)
 7             end_time = time.time()
 8             print('spend is {}'.format(end_time - start_time))
 9             
10             if flag:
11                 print('将此操作保留至日志')
12 
13         return wrapper
14 
15     return showtime
16 
17 @time_logger(2)  #得到闭包函数showtime,add = showtime(add)
18 def add(a, b):
19     print(a+b)
20     time.sleep(1)
21 
22 add(3,4)

 

类装饰器:一般依靠类内部的__call__方法

 

 1 import time
 2 class Foo(object):
 3     def __init__(self, func):
 4         self._func = func
 5 
 6     def __call__(self):
 7         start_time = time.time()
 8         self._func()
 9         end_time = time.time()
10         print('spend is {}'.format(end_time - start_time))
11 
12 @Foo  #bar = Foo(bar)
13 def bar():
14     print('bar..')
15     time.sleep(2)
16 
17 bar()

带参数的被装饰函数 

使用装饰器的缺点:

import time
# 定长
def show_time(f):
    def inner(x,y):
        start = time.time()
        f(x,y)
        end = time.time()
        print('spend %s'%(end - start))
    return inner

@show_time
def add(a,b):
    print(a+b)
    time.sleep(1)

add(1,2)

本文由俄罗斯贵宾会发布于编程,转载请注明出处:Python函数篇:装饰器【俄罗斯贵宾会】

您可能还会对下面的文章感兴趣: