美文网首页python学习
闭包和装饰器

闭包和装饰器

作者: 潇潇雨歇_安然 | 来源:发表于2018-05-29 10:01 被阅读14次

一、闭包

  • 什么是闭包?在了解这个概念之前,我们先来看一个小例子。
def create_line(k,b):
    '''定义一个表示直线的函数'''
    def line(x):
        ret = k * x + b
        return ret
    return line

line_11_22 = create_line(11,22) #返回的是一个函数line
print(line_11_22(1)) #line_11_22(1)相当于执行line(1)
print(line_11_22(2))
print(line_11_22(3))

上面的例子中,我们看到有一个外部函数,内部定义了一个函数,外部函数返回的是内部函数,而内部函数同时使用到了外部函数的变量k和b。

像这样,在一个外部函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包。在上面的例子中,我们发现,当我们在执行输出的时候,传入的是一个参数,但是却可以计算值来,从这点可以看出,闭包可以保存之前出入的数据。

  • 特点:闭包可以保存数据和功能。

  • 通过闭包,我们可以修改外部函数中的变量,在Python3中我们修改外部变量的值是通过使用关键字nonlocal来实现的,如下:

def set_func(count=0):
    def counter():
        nonlocal count # nonlocal 表示引用的是外部函数的变量。
        count += 10
        return count
    return counter

ct = set_func(10)
print(ct()) # 20

二、装饰器

装饰器是程序开发中经常会用到的一个功能,用好了装饰器,开发效率如虎添翼,所以下面跟大家介绍下装饰器,在介绍装饰器之前,先来一个小例子。

def task1():
    print('无参数')

task1()

如上面的例子,如果我们要修改task1函数的功能必须每次都修改task1函数内部的代码,这样做很麻烦,我们其实可以用装饰器来解决这个问题。那么什么是装饰器呢?

  • 装饰器:在不修改原有函数定义的前提下,给函数扩展相应的功能,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。我们使用:@装饰器函数名 放在一个函数定义前面来装饰一个函数。

那么装饰器可以针对哪些函数呢,下面我们一一列举:

1. 无参数无返回值的函数

def decorater(func):
    def wrapper():
        func()
    return wrapper

@decorater
def task1():
    print('无参数')

task1()

分析:
当我们在task1函数定义前加了@decorater之后,装饰器函数底层做了一个操作,即:task1 = decorater(task1),此时func指向了task1,task1指向了wrapper,当我们在调用task1()的时候,相当于执行了wrapper(),在wrapper函数里面执行了task1函数。

2. 无参数有返回值的函数

def decorater(func):
    def wrapper():
        return func()
    return wrapper

@decorater
def task2():
    return '有返回值'

t = task2()
print(t)

3. 有参数无返回值的函数

def decorater(func):
    def wrapper(num):
        func(num)
    return wrapper

@decorater
def task3(num):
    print('有参数,无返回值 %d' % num)

task3(10)

4. 有参数有返回值的函数

def decorater(func):
    def wrapper(num):
        return func(num)
    return wrapper

@decorater
def task4(num):
    return num + 10

t2 = task4(10)
print(t2)

5. 万能装饰器

上面的4种方法都没什么问题,但是写起来太麻烦,下面我们实现一种万能的装饰器,可以有、无参数,也可以有、无返回值。

def decorater(func):
    def wrapper(*args,**kwargs):
        return func(*args,**kwargs)
    return wrapper

@decorater
def task4(*args,**kwargs):
    print('args:',args)
    print('kwargs:',kwargs)
    return 10

t1 = task4()
print(t1)
t2 = task4(10,20)
print(t2)
t3 = task4(15,25,age=30)
print(t3)

运行结果:
args: ()
kwargs: {}
10
args: (10, 20)
kwargs: {}
10
args: (15, 25)
kwargs: {'age': 30}
10

可以看到,无论我们装饰的函数有没有参数和返回值,都可以装饰成功。

6. 多个装饰器装饰一个函数

#短信验证的装饰器
4 def message_fun(func):
5    print('正在进行短信验证装饰')
6   def call_mesage():
7       print('正在进行短信安全验证')
8     func()
9   return call_mesage

#手势验证的装饰器
12 def gesture_fun(func):
13   print('正在进行手势验证装饰')
14    def call_gesture():
15       print('正在进行手势安全验证')
16        func()
17   return call_gesture

19 @message_fun # t = message_fun(t)
20 @gesture_fun # t = gesture_fun(t)
21 def t():
22    print('装饰结束了哟')

24 t()

运行结果:
正在进行手势验证装饰
正在进行短信验证装饰
正在进行短信安全验证
正在进行手势安全验证
装饰结束了哟

从运行结果我们可以看到,装饰的时候是从内到外,执行的时候是从外到内,这是什么意思呢?就是谁距离被装饰的函数最近,谁最先装饰这个函数,谁距离被装饰的函数越远,谁最先执行。
我们来分析一下这个过程(-》表示指向):

分析装饰过程:
4行:定义函数message_fun
12行:定义函数gesture_fun
19行:此时t还没有定义,所以等待执行
20行:此时t也还没定义,所以也是等待执行
21行:定义函数t
20行:
    1. 执行函数gesture_fun(t),跳到第13行,执行 此时 gesture_fun.func  -》 t
    2. print('正在进行手势验证装饰')
    3. 第14行,定义函数call_gesture ,t = gesture_fun(t), 此时 t -》call_gesture
19行:
    1.执行函数message_fun(t),跳到第5行,执行 此时 message_fun.func -》call_gesture
    2.print('正在进行短信验证装饰')
    3.第6行,定义函数call_mesage,t = message_fun(t),此时 t -》 call_mesage
24行: 
    1. 此时t - 》 call_mesage,执行第7行
    2. print('正在进行短信安全验证'),在执行第8行,执行func(),跳到第15行,
    3. print('正在进行手势安全验证'),执行func(),跳到第21行,
    4. print('装饰结束了哟')       

7. 类装饰器

装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重写了 call() 方法,那么这个对象就是callable的。

class Person(object):
    '''类装饰器'''
    def __init__(self,func):
        self.__func = func

    def __call__(self, *args, **kwargs):
        self.__func()

@Person  #等价于 task_test = Person(task_test)
def task_test():
    print('hehe')

task_test() #执行这个对象

8. 装饰器带参数,在原有的基础上设置外部变量

import functools

86 def timefun_arg(pre='hello'):
87    def timefun(func):
88        @functools.wraps(func)
89        def wrapped_func():
90           print('%s called at %s :%s' % (func.__name__,ctime(), pre))
91           return func()
92        return wrapped_func
93    return timefun

95 @timefun_arg('Hi')  #底层实现:foo = timefun_arg('Hi')(foo)
96 def foo():
97    print('哈哈')

99  foo()

我们分析下上面的过程:

分析过程:
86行:定义函数timefun_arg
95行:先执行 timefun_arg('Hi'),此时pre='Hi',然后timefun -》timefun_arg('Hi'),此时foo还未定义,所以等待。。
96行:定义foo函数
95行:此时执行 timefun(foo),然后func -》foo,返回wrapped_func,此时foo -》wrapped_func
99行:foo() 相当于执行wrapped_func(),然后执行 print('%s called at %s :%s' % (func.__name__,ctime(), pre))
91行:返回func(), 此时func —》foo,所以跳到第97行,打印:print('哈哈')

以上的decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的'foo'变了'wrapped_func',因为返回的那个wrapped_func()函数名字就是'wrapped_func',所以,需要把原始函数的__name__等属性复制到wrapped_func()函数中,否则,有些依赖函数签名的代码执行就会出错。不需要编写wrapped_func.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的。上面的一个例子我们已经加了这段代码。在打印的时候发现,他的名字已经变成了跟函数名字一样的。

最后我们再来实现一个需求:
'''
思考一下能否写出一个@log的decorator,使它既支持:
@log # f = log(f)
def f():
pass

又支持:
@log('execute') #f = log('execute')(f)
def f():
pass
'''
代码如下:

113 def log(arg='haha'):
114    def decorator(func):
115        @functools.wraps(func)
116        def wrapper():
117            print('name: %s,arg: %s' % (func.__name__,arg))
118            return func()
119        return wrapper
120    if callable(arg): #说明使用的是 @log方式
121       return decorator(arg)
122    else:
123        return decorator
124
125 @log
126 def f1():
127    print('大家')

129 @log('哈哈')
130 def f2():
131    print('笑了')

133 f1()
134 f2()

我们也来分析一下过程:

分析过程:
113行:定义函数log
125行:底层实现:f1 = log(f1),arg—>f1,
114行,定义一个函数decorator,
120行,满足条件,直接返回decorator(arg),此时f1-》decorator(arg),执行114行,此时func-》arg,返回wrapper,
    最终f1-》wrapper
133行,执行f1() 相当于执行wrapper(),print('name: %s,arg: %s' % (func.__name__,arg)),
    返回func(),此时func-》arg-》f1,相当于执行f1(),打印 print('大家')
129行,底层实现:f2 = log('哈哈')(f2),先执行log('哈哈'),arg->'哈哈',114行,定义函数decorator,
    120行,不满足,123行,满足,返回,此时f2 = decorator(f2),此时func-》f2,返回wrapper,最终f2 -》wrapper
134行,f2(),相当于执行wrapper(),打印,print('name: %s,arg: %s' % (func.__name__,arg)),
    返回func(),此时func-》f2,所以执行f2(),130行,打印print('笑了')
    

相关文章

  • python 高级 13闭包 装饰器

    闭包和装饰器 1.8 闭包和装饰器 学习目标 1. 能够说出闭包的定义形式 2. 能够说出装饰器的实现形式 ...

  • Python装饰器-专题笔记

    学会装饰器,Python更进阶 函数作用域到闭包到装饰器讲解,及闭包和装饰器的运用。 [√] 慕课网Meshare...

  • Python的自定义超时机制——装饰器的妙用

    装饰器 关于装饰器的入门,可以参考这篇文章:12步轻松搞定python装饰器简单来说,装饰器其实就是一个闭包(闭包...

  • python装饰器

    学习了闭包的概念之后,再来学习装饰器就简单很多。装饰器就是闭包的一个应用 代码举例 但是装饰器在使用时分加载态和调...

  • Python简明教程第15节:装饰器参数和装饰器解除

    装饰器参数 上面介绍的装饰器中的闭包和被装饰的函数的参数是相同的。 其实只要保证闭包和被装饰的函数中的参数保持一致...

  • Python-闭包和修饰器

    作用域 闭包 code: 装饰器 code: 装饰器案例 code:

  • 闭包和装饰器

    一、闭包 什么是闭包?在了解这个概念之前,我们先来看一个小例子。 上面的例子中,我们看到有一个外部函数,内部定义了...

  • 闭包和装饰器

    在一个函数内部再定义一个函数,并且这个函数用到了外面的函数的变量,那么将这个函数以及用到的一些变量称之为闭包 de...

  • 闭包和装饰器

    闭包 之前一直不明白闭包的定义-内层函数引用了外层函数的变量(包括它的参数)就构成了闭包。我觉得也没什么了不起的,...

  • 闭包和装饰器

    什么是闭包? 各种专业文献的闭包定义都非常抽象,我的理解是:闭包就是能够读取其他函数内部变量的函数。 """ 使用...

网友评论

    本文标题:闭包和装饰器

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