闭包函数与装饰器

闭包函数与装饰器

闭包函数

什么时闭包函数?

闭:封闭

包:包裹

比如手机时闭包函数(内层函数),被手机包装盒(外层函数)包裹起来,手机可以使用包装盒中的东西,内层函数可以引用外层函数的名字。

基于函数对象的概念,可以将函数返回到任意位置去调用,但作用域的关系是在定义完函数时就已经被确定了的,与函数的调用位置无关。

x = 1             #1.定义变量x=1
def f1():         #2.定义函数f1
    def f2():     #7.定义函数f2
        print(x)  #10.打印x
    return f2     #8.返回函数f2的内存地址

def f3():         #3.定义函数f3
    x = 3         #5.定义变量x=3
    f2 = f1()     #6.定义变量名f2等于函数f1执行的结果
    f2()          #9.执行f2函数,而f2函数的内容就是为打印x


f3()              #4.执行函数f3
#结果为
1
闭包函数定义的规范

1 闭包函数必须在函数内部定义

2 闭包函数可以引用外层函数的名字

PS:闭包函数是函数嵌套 函数对象 名称空间与作用域结合体

闭包函数是为了装饰器做准备

也就是说函数被当做数据处理时,始终以自带的作用域为准。若内嵌函数包含对外部函数作用域(而非全局作用域)中变量的引用,那么该’内嵌函数’就是闭包函数,简称闭包(Closures)

def func(y):         #1.定义函数func
     x = 100         #4.定义变量x=100
     def inner():    #5.定义函数inner
         print(x)    #7.打印X,该x即引用了外层函数定义的x=100
         print(y)    #8.打印Y
     return inner    #6.返回inner函数给外层使用
inner = func(1)      #2.定义变量名inner等于调用传入参数y=1的func函数
inner()              #3.执行inner函数
#结果为
100
1

闭”代表函数是内部的,“包”代表函数外’包裹’着对外层作用域的引用。因而无论在何处调用闭包函数,使用的仍然是包裹在其外层的变量。

闭包函数的用途

目前为止,我们得到了两种为函数体传值的方式,一种是直接将值以参数的形式传入,另外一种就是将值包给函数

#方式一
import requests
def spider_func(url):
    response = requests.get(url)
    print(len(response.text))
    print(response.text)

url='http://www.youdao.com/'
spider_func(url)

#方式二
import requests
def spider_func(url):
    def inner():
        response = requests.get(url)
        print(len(response.text))
        print(response.text)
    return inner
#爬取有道
inner = spider_func('http://www.youdao.com/')
inner()

装饰器

什么是装饰器?

装饰:装饰 修饰

器:工具

所以装饰器即装饰的工具

PS:装饰器必须要遵循'开放封闭'原则

开放:对函数功能的添加是开放的

封闭:对函数功能的修改是封闭的

装饰器的作用?

在不修改被装饰对象源代码与调用方式的前提下,添加新的功能

装饰器的定义必须遵循

不修改被装饰对象源代码

不修改被装饰对象调用方式

为什么要使用装饰器?

可以解决代码冗余问题,提高代码的可扩展性。

怎么使用装饰器?

装饰器的应用:

统计时间和登陆认证

编写装饰器:

通过闭包函数来实现装饰器

无参装饰器的实现

#给如下函数添加统计其执行时间的功能
import time
def index():
    time.sleep(3)
    print('Welcome to the index page')


index()

#按照遵循不修改被装饰对象源代码的原则,正常如下
import time
def index():
    time.sleep(3)
    print('Welcome to the index page')


start_time=time.time()
index()
stop_time=time.time()
print('run time is %s'%(stop_time - start_time))
#结果为
Welcome to the index page
run time is 3.0003936290740967

考虑到还有可能要统计其他函数的执行时间,于是我们将其做成一个单独的工具,函数体需要外部传入被装饰的函数从而进行调用,我们可以使用参数的形式传入

import time
def index():
    time.sleep(3)
    print('Welcome to the index page')
def wrapper(func): #通过参数接受外部的值
    start_time = time.time()
    res = func()
    stop_time = time.time()
    print(f'run time is %s' % (stop_time - start_time))
    return res
wrapper(index)
#输出结果为
Welcome to the index page
run time is 3.000837564468384
#但是我们发现这里函数的调用方式改变了,违反了装饰器定义中不能修改被装饰对象调用方式的原则

所以 我们换一种为函数体传值的方式,即将值包给函数

import time                #1.导入时间库
def index():               #2.定义函数index
    time.sleep(3)
    print('Welcome to the index page')
def wait_time(func):       #3定义函数wait_time
    def wrapper():         #5.定义函数wrapper
        start_time = time.time() #8.获取开始计时的时间戳
        res = func()             #9.定义变量res等于index函数的执行结果
        stop_time = time.time()  #10.获取执行结束后的时间戳
        print(f'run time is:{stop_time - start_time}') #11.打印消耗的时间
        return res          #12.返回index函数的执行结果
    return wrapper          #6.返回wrapper函数的内存地址并将其与变量名index想对应
index = wait_time(index)    #4.定义变量名index等于将index当作参数传入wait_time函数执行后的结果
index()                     #7.执行index函数,即执行wrapper函数

#至此我们便实现了一个无参装饰器wait_time,可以在不修改被装饰对象index源代码和调用方式的前提下为其加上新功能。但我们忽略了若被装饰的函数是一个有参函数,便会抛出异常。因为我们这里的index()是相当于调用的wrapper函数,,而函数wrapper没有参数。wrapper函数接收的参数其实是给最原始的func用的,为了能满足被装饰函数参数的所有情况,便用上*args+**kwargs组合
import time                #1.导入时间库
def index(*args, **kwargs):               #2.定义函数index
    time.sleep(3)
    print('Welcome to the index page')
def wait_time(func):       #3定义函数wait_time
    def wrapper(*args, **kwargs):         #5.定义函数wrapper
        start_time = time.time() #8.获取开始计时的时间戳
        res = func(*args, **kwargs)             #9.定义变量res等于index函数的执行结果
        stop_time = time.time()  #10.获取执行结束后的时间戳
        print(f'run time is:{stop_time - start_time}') #11.打印消耗的时间
        return res          #12.返回index函数的执行结果
    return wrapper          #6.返回wrapper函数的内存地址并将其与变量名index想对应
index = wait_time(index)    #4.定义变量名index等于将index当作参数传入wait_time函数执行后的结果
index()  

此时我们就可以用timer来装饰带参数或不带参数的函数了,但是为了简洁而优雅地使用装饰器,Python提供了专门的装饰器语法来取代index=timer(index)的形式,需要在被装饰对象的正上方单独一行添加@timer,当解释器解释到@timer时就会调用timer函数,且把它正下方的函数名当做实参传入,然后将返回的结果重新赋值给原函数名

def wait_time(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs) 
        stop_time = time.time()  
        print(f'run time is:{stop_time - start_time}') 
        return res          
    return wrapper

@timer # index=wait_time(index)
def index():
    time.sleep(3)
    print('Welcome to the index page')
index()

PS:注意使用python语法糖时,装饰器一定要在被装饰对象之前定义
原文地址:https://www.cnblogs.com/a736659557/p/11892359.html