05-装饰器

python学习网 2017-08-09 20:21:14

装饰器

什么是装饰器

什么是装饰器?装饰的工具,比如戴眼镜,眼镜就是一个装饰器,眼镜并没有改眼睛的原始构造,但是还让我看的更清楚了(添加了新的功能),如下图。装饰器可以是任何可调用对象(比如说函数,类),被装饰者也可以是任意可调用对象(比如说函数)。

为什么要用装饰器

开放封闭原则,就是说你开发的一个东西一旦上线了,就要尽量去避免更改了,或者说你就别改了。但是有时候必须要修改,因为需求不断的变更,因此就需要在开发的时候留出一定的可扩展的可能性,这种可能性是在不修改源代码不修改调用方式来加功能。

在了解装饰器之前首先要了解闭包。

闭包

内部函数的代码包含对外部作用域名字的引用,而不是对全局名字作用域的引用。其实,闭包指的是延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。函数是不是匿名的没有关系,关键是它能访问定义体之外定义的非全局变量。

a = "i am global"
def f1():
    a = "i am local"
    def f2():
        print(a)
    return f2
f = f1()
print(f)
f()

结果:
<function f1.<locals>.f2 at 0x0000000484833A60>
i am local

作用域在定义的时候其实就已经规定好了,可以看到上面的代码,虽然f接受了f2的内存地址并在全局调用了f2,但是由于f2的作用域并不是全局的因此f2会去优先找f1()局部的这个a变量,而不会看全局的a。

f2属于内部函数,它引用的a在外部作用域也包含一个a,但是引用的并不是全局的那个,比如下面的情况就不是闭包:

x = 1
def f1():
    def f2():
        print(x)
        
f1()

f2()在内部引用的x实际上是调用的全局的x,闭包一定是对外部作用域的引用而不是全局的。

def f1():
    x = 1
    def f2():
        print(x)
    return f2
f = f1()
print(f.__closure__)

结果:
(<cell at 0x0000003D9B976498: int object at 0x000000007587B440>,)

__closure__可以显示它对外部命名空间的调用,这里是以一个列表的形式,这里显示的是这个值所在的内存地址,我们是可以把这个值取出来的:

def f1():
    x = 1
    def f2():
        print(x)
    return f2
f = f1()
print(f.__closure__[0].cell_contents)

结果:
1

也就是说闭包函数在返回的同时可以返回它的作用域,即这个闭包函数对外部作用域的调用。如果调用的作用域不是外部的而是全局的不会报错,但是__closure__返回的就是None了。

x = "i am global"
def f1():
    # x = 1
    def f2():
        print(x)
    return f2
f = f1()
print(f.__closure__)

结果:
None

小结

  • 必须是内部定义的函数
  • 函数包含对外部作用域而不是全局作用域名字的引用

闭包最重要的作用就是返回状态,状态即外部作用域。

Example: 1
from urllib.request import urlopen

def f1(url):
    def f2():
        print(urlopen(url).read())
    return f2

baidu = f1("http://www.baidu.com")
baidu()

这里我引入了urllib模块,目的是获取网页的内容,但是获取什么网页的内容需要url传值,这里我们就可以利用闭包的方式,用baidu这个变量保存闭包函数的值,baidu内部不仅包含爬到的百度网页的内容,并且包含外部作用域f1中传过来的www.baidu.com,所以当我们要爬百度的内容的时候直接调用就可以了。

Example: 2
def make_averager():
	series = []
    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total/len(series)
    return averag

>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0

上面的函数其实是一个持续计算不断增加的系列值的均值的一个高阶函数。调用 make_averager 时,返回一个 averager 函数对象。每次调用 averager 时,它会把参数添加到系列值中,然后计算当前平均值 。series被封装进了avg的__closure__里面去了,通过print(avg.__closure__[0].cell_contents)可以查看到。

综上,闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。注意,只有嵌套在其他函数中的函数才可能需要处理不在全局作用域中的外部变量

说完了闭包就来看一下装饰器。

无参装饰器

看例子:

import time

def say_hello():
    time.sleep(2)
    print("hello everyone")

say_hello()

这就是一个函数打印hello everyone。现在我要统计这个程序执行的时间.

import time

def sum_time(func):
    def wrapper(*args,**kwargs):
        start = time.time()
        func(*args,**kwargs)
        stop = time.time()
        print("run time is %s" % (stop-start))
    return wrapper

@sum_time
def say_hello():
    time.sleep(2)
    print("hello everyone")
    
结果:
hello everyone
run time is 2.000821352005005

上面的用法就是装饰器的用法,加一个新功能,使用@装饰器的名字就可以了。那么上面的@sum_time都干了什么,我们只要添加了@sum_time,它就会把它正下方的函数名作为参数添加进去,也就是sum_time(say_hello)这个情况。然后把返回值赋值给say_hello,即say_hello = sum_time(say_hello)这个就是装饰器的用法。我们既没有修改原函数,也没有修改调用方法。

所以说,总结一下:

@装饰器的名字 ====>  函数名 = 装饰器名字(函数名)

因此这个装饰器是需要有一个返回值的,因为要重新赋值给原函数。调用原函数其实就是装饰器装饰后的返回值,其实返回值就是一个内存地址。

装饰器的本质是一个可调用的对象,这里其实就是函数,@函数名。再来看这个装饰器函数。

def sum_time(func):
    def wrapper(*args,**kwargs):
        start = time.time()
        func(*args,**kwargs)
        stop = time.time()
        print("run time is %s" % (stop-start))
    return wrapper

这其实就是一个闭包函数,我们说在@sum_time的时候,装饰器会把正下方的函数当成参数传递给sum_time后调用,最后sum_time会返回内置函数wrapper,其实就是返回内置函数wrapper的内存地址,那么我们调用say_hello其实就是调用wrapper。装饰其中的func(*args,**kwargs)的内存地址是原say_hello函数的地址,调用say_hello的时候他的内存地址就已经不是原say_hello的内存地址了。

有参装饰器

def auth2(auth_type):
    def auth(func):
        def wrapper(*args,**kwargs):
            if auth_type == "file":
                name = input("Username:")
                passwd = input("Password:")
                if name == "egon" and passwd == "123":
                    print("auth successfully")
                    res = func(*args,**kwargs)
                    return res
                else:
                    print("auth error")
            elif auth_type == "sql":
                print("还他妈不会呢!")
        return wrapper
    return auth

@auth2(auth_type = "file")     # index = auth(index)
def index():
    print("welcome to index page")

index()

@auth2(auth_type = "sql")     # index = auth(index)
def index2():
    print("welcome to index page")

index2()

结果:
Username:egon
Password:13082171785
auth successfully
welcome to index page
还他妈不会呢!

装饰器的补充

装饰器需要写到被装饰函数的正上方,并且需要独占一行,因此可以写多个装饰器:

@bbb  #func = bbb(aaa(func))
@aaa  #func = aaa(func)
def func():
    pass

含参数的多个装饰器

@ccc('c')  #func = ccc('c')(bbb('b')(aaa('a')(func)))
@bbb('b')  #func = bbb('b')(aaa('a')(func))
@aaa('a')  #func = aaa('a')(func)
def func():
    pass
    
res = aaa('a')
@res
func = aaa('a')(func)

打印原函数的注释:

import time
from functools import wraps

def sum_time(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        start = time.time()
        res = func(*args,**kwargs)
        stop = time.time()
        print("run time is %s" % (stop-start))
    return wrapper

@sum_time
def say_hello():
    '''say_hello test'''
    time.sleep(2)
    print("hello everyone")

say_hello()
print(say_hello.__doc__)

结果:
hello everyone
run time is 2.0000414848327637
say_hello test
阅读(786) 评论(0)