Python 中的装饰器

情到浓时终转凉″ 提交于 2020-02-17 07:37:12

说到装饰器是我们每个学Python人中的心痛。

1.闭包

学习装饰器首先我们先学习闭包:

闭包条件

   1 在一个外函数中定义了一个内函数。

       2 内函数里使用了外函数的临时变量。

       3 并且外函数的返回值是内函数的引用(即函数名)。

闭包的定义

闭包的概念就是当我们在函数内定义一个函数时,这个内部函数使用了外部函数的临时变量,且外部函数的返回值是内部函数的引用时,我们称之为闭包。

出现的背景

一般情况下,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。

闭包的基本结构:

 

def func1(msg): #外函数
    name="xiao"
    def inner(): #内函数
        na = name # 使用外函数的变量
        nb = msg
    print(inner.__closure__)     
    print(inner.__closure__[0].cell_contents)
    print(inner.__closure__[1].cell_contents)
    return inner #返回内函数的引用
f=func1(123)
f() 

#判断函数是不是闭包函数用__closure__方法 如果是闭包函数就是返回一个元组包含<cell,
 # 如果不是闭包函数就就返回none。这个cell包含了我们被引用的所有外部变量

结果:

(<cell at 0x000001EFD0610CA8: int object at 0x00000000699F7D20>, <cell at 0x000001EFD0610CD8: str object at 0x000001EFD04B4C38>)
123
xiao

闭包存在的意义在哪里?

我认为闭包存在的意义在于它保存了外层函数的变量,如果他不能保存外存函数的变量,那么闭包和其他函数没有什么区别。

例子:

def get_avg():
    scores = []

    def inner_count_avg(val):
        scores.append(val)
        return sum(scores)/len(scores)

    return inner_count_avg


avg = get_avg()
print(avg(5)) # 相当于 inner_count_avg(5)
print(avg(6)) # inner_count_avg(6)

# 这样看来外函数get_avg只被执行了一次

结果:

5.0
5.5

 

 

2.装饰器

装饰器:装饰器是一个函数,这个函数是用来装饰其他函数的,为其他函数添加新功能且不改变原函数的调用方式和内部代码。

装饰器的内层函数就是一个闭包,因为它使用了外部函数的参数func,

原则:1.不能改变被修饰函数的源代码。

           2.不能修改被修饰函数的调用方式。

高阶函数

高阶函数一个函数接收另一个函数名当做参数,或者把另一个函数作为结果返回的函数就是高阶函数.这是<<流畅的Python>>一书中定义的. 装饰器也是高阶函数.

补充:

嵌套函数:在一个函数里定义新的一个函数。

装饰器=高阶函数+嵌套函数

装饰器的两大特性

特性一:能把被装饰的函数替换成其他函数.

特性二:装饰器在加载模块的时候立即执行.

 

registry = []


def register(func):
    print("runnning register(%s)" % func)
    registry.append(func)
    return func


@register
def f1():
    print('runing f1()')


@register
def f2():
    print('runing f2()')


def f3():
    print('runing f3()')


def main():
    print('running main()')
    print('registery->', registry)
    f1()
    f2()
    f3()


if __name__ == '__main__':
    main()

 

在其他py里导入该模块时

runnning register(<function f1 at 0x10ccba840>)
runnning register(<function f2 at 0x10ccba8c8>)

上边的例子说明了加载该模块时,装饰器就已经运行,被装饰器装饰的函数只有在明确的调用的时候才运行.

装饰器例子

例子要求:1.写一个装饰器,计算两个程序运行耗时。

                  2.不改变源代码和调用方式。

例子:

1 # Author :ZHANG
2 #-*-coding:utf-8-*-
3 import time
4 def text1():
5     time.sleep(3)
6     print("in the text1")
7 def text2():
8     time.sleep(2)
9     print("in the text2")
View Code

方法:

 1 # Author :ZHANG
 2 #-*-coding:utf-8-*-
 3 import time
 4 def solu(func):
 5     def wreppe():
 6         star_time=time.time()
 7         func()
 8         stop_time=time.time()
 9         print("耗时为%s秒."%(stop_time-star_time))
10     return wreppe
11 @solu      #这里其实为:text1=solu(text1)
12 def text1():
13     time.sleep(3)
14     print("in the text1")
15 @solu    #同text1
16 def text2():
17     time.sleep(2)
18     print("in the text2")
19 text1()    #这里并不是真的text1(),其实是一个替代,solu(text1)()
20 text2()   #同text1
View Code

结果:

1 in the text1
2 耗时为3.000171661376953秒.
3 in the text2
4 耗时为2.0001144409179688秒.
View Code

这里我们要特别注意这个@符号,其实可以用下面的代码来代替只不过我们用@比较简便罢了,但是我们要知道真正的意义

import time
def solu(func):
    def wreppe():
        star_time=time.time()
        func()
        stop_time=time.time()
        print("耗时为%s秒."%(stop_time-star_time))
    return wreppe
def text1():
    time.sleep(3)
    print("in the text1")
text1=solu(text1)#这里其实就是变量值替换了
text1()    #这里并不是真的text1(),其实是一个替代,solu(text1)()

 

继续增加要求:text2加上多个参数,包括关键参数

方法:

 1 # Author :ZHANG
 2 #-*-coding:utf-8-*-
 3 import time
 4 def solu(func):
 5     def wreppe(*args,**kwargs):
 6         star_time=time.time()
 7         func(*args,**kwargs)
 8         stop_time=time.time()
 9         print("耗时为%s秒."%(stop_time-star_time))
10     return wreppe
11 @solu     #这里其实为:text1=solu(text1)=wreppe
12 def text1():
13     time.sleep(3)
14     print("in the text1")
15 @solu  #同text1
16 def text2(*args,**kwargs):
17     time.sleep(2)
18     print("text2:",args,kwargs)
19 text1()         #这里并不是真的text1(),其实是一个替代,solu(text1)()
20 text2(2,"we",a=1)  #同text1
View Code

结果:

1 in the text1
2 耗时为3.000171661376953秒.
3 text2: (2, 'we') {'a': 1}
4 耗时为2.0001144409179688秒.
View Code

马上到高潮:

新例子要求为:

home函数return后面添加“from home”

 1 #-*-coding:utf-8-*-
 2 username,password="zhang",12345
 3 def deco(func):
 4     def wreppe(*args,**kwargs):
 5         user=input("enter your name>>>").strip()
 6         passwd=int(input("enter your password").strip())#密码输入这里我简化了许多,现在是为了突出另外一个主题
 7         if user==username and passwd==password:
 8             func(*args,**kwargs)
 9             print("\033[32;1muser has passed authentication\033[0m")
10         else:
11             exit("\033[31;1minvalid username or password\033[0m")
12     return wreppe
13 def index():
14     print("welcome to index page")
15 @deco
16 def home():
17     print("welcome to home page")
18 home()
View Code

注意这个home函数添加“”from home”后,如果你打印他会得到如下结果:

enter your name>>>zhang
enter your password12345
welcome to home page
user has passed authentication
None #注意

Process finished with exit code 0

结果return后面得到一个none而不是“from home”,这就不符合装饰器不修改被修饰函数源代码的要求。

方法:

 1 username,password="zhang",12345
 2 def deco(func):
 3     def wreppe(*args,**kwargs):
 4         user=input("enter your name>>>").strip()
 5         passwd=int(input("enter your password").strip())#密码输入这里我简化了许多,现在是为了突出另外一个主题
 6         if user==username and passwd==password:
 7             res=func(*args,**kwargs)    #改变这个区域,把func(*args,**kwargs) 执行结果传给一个变量,就可以了
 8             print("\033[32;1muser has passed authentication\033[0m")
 9             return res   #改变这个区域,这里就相当于把res中储存的结果打印出来了,因为最后是print(home())
10         else:
11             exit("\033[31;1minvalid username or password\033[0m")
12     return wreppe
13 def index():
14     print("welcome to index page")
15 @deco
16 def home():
17     print("welcome to home page")
18     return "from home"
19 
20 print(home())
View Code

结果:

1 enter your name>>>zhang
2 enter your password12345
3 welcome to home page
4 user has passed authentication
5 from home
View Code

还有一种情况:@deco(参数)需要学习

 

-------------------------------------------------------------------------------------------------第二次题目-----------------------------------------------------------------------

情况一:函数中带有参数

import time
def func(func):
    def inner(*args): #因为func2中需要传递参数,所以这里要有*args
        tm=args                       #这里返回的是一个元组
        start=time.time()
        func(*args)    #这里的*args是引用的inner的参数,这里为什么是*args,而不是args,因为*args返回的是一个元组,因为A,B是分开的,所以需要把这个元组打撒
        stop=time.time()
        print("程序运行时间为{}秒".format(stop-start))
    return inner
@func
def func2(a,b):
    print("func2:%s"%(a+b))

func2(3,5) #这里的真实意思 func(func2)(3,5)

 

 结果:

func2:8
程序运行时间为0.0秒

情况二: 函数中带有返回值

import time
def func(func):
    def inner(*args):
        tm=args                       #这里返回的是一个元组
        start=time.time()
        set=func(*args)
        stop=time.time()
        print("程序运行时间为{}秒".format(stop-start))
        return set
    return inner
@funcdef func2(a,b):    print("func2:%s"%(a+b))    return "数学真好"print(func2(3,5))

结果:

func2:8
程序运行时间为0.0秒
数学真好

 情况三装饰器中带参数。

给内容添加标签,函数控制内容,装饰器控制添加什么标签

def html_tags(tag_name):
    """
    给函数添加tag标签
    :param tag_name: 标签名字
    :return:
    """
    def wrapper_func(func):
        def wrapper(*args,**kwargs):
            content =func(*args,**kwargs)
            return "<{tag}>{content}</{tag}>".format(tag=tag_name,content=content)
        return wrapper
    return wrapper_func
@html_tags("b")  #这里就等于 hello = html_tags("b)(hello)
def hello(name='Toby'):
    """
    返回要贴标签的内容
    :param name:
    :return:
    """
    return 'hello{}!'.format(name)
b = hello("小花")     #这里就等于 html_tags('b')(hello)('小花')
print(b)

结果:

<b>hello小花!</b>

 

装饰器的固定格式:

def func(f):
    def inner(*args,**kwargs):
        re= f(*args,**kwargs)
        return re
    return inner

 装饰器修复技术(wraps)

装饰器修复技术是用来修复执行函数称为真正的函数,作用将被修饰的函数的某些属性比如__doc__,__name__赋给装饰器中的闭包函数,如果你不需要这些属性,wraps就没有什么用。

先不加wraps的情况

def wrapper(func):
    def inner(*args,**kwargs):
        print("在前面执行")
        func()
        print('在后面执行')
    return inner
@wrapper
def f():

    """
    这是一个用来测量
    :return:
    """
    print("hahaa")
if __name__ == '__main__':
    print(f.__name__)
    print(f.__doc__)

结果为:

我们可以看到执行f()并不是执行真正的f函数,而是执行的wrapper(f)(),也就是真正执行的inner() 函数

inner
None

加wraps的情况

from functools import wraps
def wrapper(func):
    @wraps(func)
    def inner(*args,**kwargs):
        print("在前面执行")
        func()
        print('在后面执行')
    return inner
@wrapper
def f():

    """
    这是一个用来测量
    :return:
    """
    print("hahaa")
if __name__ == '__main__':
    print(f.__name__)  #修饰的函数名
    print(f.__doc__)  #修饰函数名里边的注释

 

结果:

f

    这是一个用来测量
    :return:
    

装饰器还具有缓存的功能

当装饰器的外层函数的变量是可变对象的时候,该变量具有计数器的作用,也可以当缓存

def decorator(func):
    """
    记录被装饰函数的执行次数
    :param func:
    :return:
    """
    #注意这里使用可变对象
    a = [0]
    def decorated_func(*args,**keyargs):
        func(*args, **keyargs)
        #因为闭包是浅拷贝,如果是不可变对象,每次调用完成后符号都会被清空,导致错误
        a[0] += 1
        print ("%s have bing called %d times" % (func.__name__, a[0]))
    return decorated_func

@decorator
def func(x):
    print (x)

func(3)
func(3)

结果:

3
func have bing called 1 times
3
func have bing called 2 times

注意: 如果你把@decorator去掉话,这样调用

decorator(func)(3)
decorator(func)(3)

装饰器就没有了缓存的功能

结果:

3
func have bing called 1 times
3
func have bing called 1 times

具体原因,我也不清楚

装饰器的执行顺序

def one(func):
    print('----1----')
    def two():
        print('----2----')
        func()
    return two

def a(func):
    print('----a----')
    def b():
        print('----b----')
        func()
    return b

@one
@a
def demo():
    print('----3----')

demo() 

结果:

----a----
----1----
----2----
----b----
----3----

我们这里可能大多数人容易猛,我们不用@我们用另外一种方式来写装饰器

def one(func):
    print('----1----')
    def two():
        print('----2----')
        func()
    return two

def a(func):
    print('----a----')
    def b():
        print('----b----')
        func()
    return b


def demo():
    print('----3----')


one(a(demo))()

结果:

----a----
----1----
----2----
----b----
----3----

我们可以发现这两种方法结果一样,但是第二种装饰器方法容易理解

我们只需要研究它的执行顺序就可以

one(a(demo))()首先是demo这个参数传进来第二步是执行a(demo) 结果打印__a__ 返回b函数名第三步是执行one(b)函数,把b当做函数穿进去,打印__1__,返回two第四步是执行two(),打印__2__,执行b函数,打印__b__,执行demo函数

 

总结执行顺序:

先调用@a执行a函数的外函数,--->one函数的外函数---->one内函数---->a函数的内函数---->执行demo函数

按照实现功能来说:

先实现one函数的功能,再实现a函数的功能

在实际应用的场景中,当我们采用上面的方式写了两个装饰方法比如先验证有没有登录 @login_required , 再验证权限够不够时 @permision_allowed 时,我们采用下面的顺序来装饰函数:

@login_required
@permision_allowed
def f()
  # Do something
  return

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!