Python闭包和装饰器

1. 闭包函数

image-20211116172304658

闭包概念:在一个内部函数中,对外部作用域的变量进行引用。

  • 闭: 定义在函数内部的函数

  • 包:内部函数使用了外部函数名称空间中的名字

符合上述两个特征的函数都可以称之为"闭包函数"

闭包就是你调用了一个函数A,这个函数A返回了一个函数B给你,函数B中使用了函数A的数据。这个返回的函数B就叫做闭包

# 定义闭包函数:
def foo():
    x = 123
    def func():
        print("x=", x)
    return func

# func()为定义在foo()内的函数
# func()中使用了外部函数foor()中定义的数据

闭包函数其实是给函数传参的第二种方式

第一种方法就是平时用的给函数传参

def foo(x, y):
    return x + y

print(foo(10, 20))  # 使用时直接给函数传参

第二种方法利用闭包传参

# 代码:
def foo():
    x = 10
    y = 20
    def func():
        if x > y:
            return x
        return y
    return func 

res = foo()
print(res)
print(res())
# 执行结果:
<function foo.<locals>.func at 0x7fb4e6c70268>
20
# func() 在foo()中,符合闭包第一条。
# func()函数中没有写形参,但它在函数里使用x,y这两个参数是没有问题的,因为它外面的函数foo()里有这两个参数,所以func()拿过来用。符合闭包第二条
# 但是这么写 x 和y的值在foo()中就写死了,如何灵活运用,不管传什么值都可以比较:
# 修改版本:

def foo(x, y):   #由foo()函数接受两个参数
    def func():
        if x > y:
            return x
        return y
    return func 
res = foo(2, 3)

print(res)
print(res())

# 执行结果:
<function foo.<locals>.func at 0x7fb4e57bc268>
3

使用闭包实例:

# 代码:
import requests

#url="https://www.baidu.com"

def site(url):
    def get_content():
        res = requests.get(url)
        if res.status_code == 200:
            with open(r'web.txt', 'ab') as f:
                f.write(res.content)

    return get_content
res = site("https://www.baidu.com")
res()  
res()
res()
# res为site()函数返回的函数,res为闭包函数。 在外面调用多少次res()它拿到的参数永远为"https://www.baidu.com"

结论:无论在何处调用闭包函数,它使用的仍然是它外部函数的变量。如get_content()函数永远使用site()函数给的变量

2. 装饰器

在不改变被"装饰对象内部代码"和"原有调用方式"的基础之上添加额外功能

装饰:给被装饰对象添加额外的功能

器:指的是工具

装饰器的原则:开放封闭原则

  • 开放:对扩展开放
  • 封闭:对修改封闭

2.1 模拟装饰器

计算一个函数执行时间

# 代码:
import requests
import time

def get_content(url):
    res = requests.get(url)
    if res.status_code == 200:
        with open(r'web.txt', 'wb') as f:
            f.write(res.content)




start_time = time.time()
get_content("https://www.baidu.com")
end_time = time.time()

print("get_content execute time :", end_time - start_time)

# 执行结果:
get_content execute time : 0.0453946590423584
    
# 在不改变函数get_content()内容代码,也不改变get_content()调用方式的提前下给它增加了一个计算函数执行时间的功能

2.2 简易版装饰器

给函数增加统计执行时间功能。

import time

def index():
    time.sleep(1)
    print("Hello index()")

def outer(func):
    def get_time():
        start_time = time.time()
        func()
        end_time = time.time()
        print("function run time: %s" % (end_time - start_time))
    return get_time

index = outer(index)   # outer(index)把返回值get_time函数返回,然后赋值给index.这时index是get_time函数,不是之前的index函数了,原来的index函数给了func.
index() # 调用函数,这个调用的其实是get_time函数。

具体执行:

import time

def index():
    time.sleep(1)
    print("Hello index()")


print("1. first index funcation address: ",index)
print()
def outer(func):
    def get_time():
        start_time = time.time()
        func()
        print("func  funcation address: ",func)
        print()
        end_time = time.time()
        print("function run time: %s" % (end_time - start_time))
    print("get_time  funcation address: ", get_time)
    print()
    return get_time

index = outer(index)
print("2. second index funcation address: ",index)
print()
print("second index() funcation execute:")
print()
index()

# 执行结果:
1. first index funcation address:  <function index at 0x7fe92dc47f28>

get_time  funcation address:  <function outer.<locals>.get_time at 0x7fe92db682f0>

2. second index funcation address:  <function outer.<locals>.get_time at 0x7fe92db682f0>

second index() funcation execute:

Hello index()
func  funcation address:  <function index at 0x7fe92dc47f28>

function run time: 1.0011003017425537
    
    
# 第一个index()的内存地址和 func() 的是一样的:<function index at 0x7fe92dc47f28>
# 第二个 index()的内存地址和 get_time()的是一样的:<function outer.<locals>.get_time at 0x7fe92db682f0>
# 这就说明最后执行的index()已经不是之前的index了,而是get_time函数。而之前的index已经重新赋给func了。

image

2.3 装饰器(解决函数传参)

上面的装饰器只适用于没有参数的函数,如果有参数的函数则无法处理了。

如果给一个需要传值的参数,使用装饰器时就会报错:

TypeError: xxx() missing 1 required positional argument: 'xxxx'

解决方法:

import time

def foo(name):
    time.sleep(1)
    print("Hello %s" % name)


def decorator(func):
    def get_vars(*args, **kwargs):    # 这样不管传不传参,或者传多个位置参数,多个关键字参数都可以
        start_time = time.time()
        func(*args, **kwargs) 
        stop_time = time.time()
        print("funcation execute time:", stop_time - start_time)


    return get_vars


foo = decorator(foo)
foo("hans")

# 执行结果:
Hello hans
funcation execute time: 1.0010788440704346

有参函数和无参函数都可以处理:

# 代码:
import time

def foo(name):
    time.sleep(1)
    print("Hello %s" % name)


def boo():
    print("Hello world")
    time.sleep(1)

def decorator(func):
    def get_vars(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        stop_time = time.time()
        print("funcation execute time:", stop_time - start_time)


    return get_vars

print("有参函数:")
foo = decorator(foo)
foo("hans")


print("无参函数:")
boo = decorator(boo)
boo()

# 执行结果:
有参函数:
Hello hans
funcation execute time: 1.0001640319824219
无参函数:
Hello world
funcation execute time: 1.001054048538208

2.4 装饰器(解决函数返回值)

解决了传参问题,一般函数都会有返回值,所以还需要解决函数返回值问题。

# 代码
import time

def foo(name):
    time.sleep(1)
    print("Hello %s" % name)
    return name

def decorator(func):
    def get_vars(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        stop_time = time.time()
        print("funcation execute time:", stop_time - start_time)


    return get_vars


foo = decorator(foo)
res = foo("hans")
print(res)

# 执行结果:
Hello hans
funcation execute time: 1.0010926723480225
None
# foo()函数是有返回值的, 但现在返回值为None.不符合预期。

增加返回值:

# 代码:
import time

def foo(name):
    time.sleep(1)
    print("Hello %s" % name)
    return name

def decorator(func):
    def get_vars(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print("funcation execute time:", stop_time - start_time)
        return res


    return get_vars


foo = decorator(foo)
res = foo("hans")
print(res)

# 执行结果:
Hello hans
funcation execute time: 1.0011241436004639
hans  # 为返回值

至此装饰器解决了装饰对象传参和返回值的问题。

2.5 使用装饰器做认证

需求1:认证成功再执行具体的功能(每个功能都需要认证)

def signUp():
    print("注册")

def shop():
    print("购物")

def decorator(func):
    def  auth(*args, **kwargs):
        username = input("name>>>:").strip()
        password = input("passwd>>>:").strip()
        if username.lower() == 'hans'  and password == '123':
           res =  func(*args, **kwargs)
        else:
            print("您没有登录")

    return auth

signUp=decorator(signUp)
signUp()
shop=decorator(shop)
shop()

# 执行结果:
name>>>:hans
passwd>>>:123
注册
name>>>:hans
passwd>>>:123
购物

需求2:认证成功再执行具体的功能(认证完一次,后面的都不需要认证(单点登陆))

# 代码
def signUp():
    print("注册")

def shop():
    print("购物")

def change():
    print("修改订单");

is_login={"login":False}

def decorator(func):
    def  auth(*args, **kwargs):
        if is_login.get("login"):
            res = func(*args, **kwargs)
            return res
        username = input("name>>>:").strip()
        password = input("passwd>>>:").strip()
        if username.lower() == 'hans'  and password == '123':
           res =  func(*args, **kwargs)
           is_login["login"]=True
           return res
        else:
            print("您没有登录")

    return auth


signUp=decorator(signUp)
signUp()
shop=decorator(shop)
shop()
change=decorator(change)
change()

# 执行结果:
[root@hans_tencent_centos82 tmp]# python3 q.py 
name>>>:hans    #第一次登录成功则后面三个函数全部都执行
passwd>>>:123
注册
购物
修改订单

[root@hans_tencent_centos82 tmp]# python3 q.py 
name>>>:af
passwd>>>:asdf
您没有登录
name>>>:hans 	# 第二次登录成功,后面函数都执行
passwd>>>:123
购物
修改订单
[root@hans_tencent_centos82 tmp]# python3 q.py 
name>>>:df
passwd>>>:sf
您没有登录
name>>>:sadf
passwd>>>:asf
您没有登录
name>>>:hans		# 最后一次登录成功,则最后一个函数执行
passwd>>>:123
修改订单

2.6 装饰器模版

def decorator(func):    # func就是要装饰的函数对象
    def  inner(*args, **kwargs):   #  *args, **kwargs 就是给func()传的值
        	print("函数执行前添加的额外功能")
            res = func(*args, **kwargs)
            print("函数执行后添加的额外功能")
            return res
    return inner

2.7 装饰器语法糖

上面使用装饰器的用法都不规范,在Python中有专门的语法糖来使用装饰器。

装饰器语法糖书写规范:

语法糖必须紧贴在被装饰对象的上方

# 代码
import time

def decorator(func):
    def  inner(*args, **kwargs):
        	print("函数执行前添加的额外功能")
            res = func(*args, **kwargs)
            print("函数执行后添加的额外功能")
            return res
    return inner

@decorator     # 装饰器语法糖
def index():
    time.sleep(1)
    print("Hello index()")
  
index()

# 执行结果:
函数执行前添加的额外功能
Hello index()
函数执行后添加的额外功能

装饰器语法糖内部原理
会自动将下面紧贴着的被装饰对象名字当做参数传给装饰器函数调用,@decorator其实就相当是之前写的index=decorator(index)

2.8 双层装饰器

即要打印函数运行的时间,还要做认证

# 代码:
import time

def get_time(func):
    def inner(*args, **kwargs):
        start_time = time.time() 
        res = func(*args, **kwargs)
        stop_time = time.time() 
        print("funcation execute time:%.2f" % (stop_time - start_time))
        return res 
    return inner

def decorator(func):
    def  auth(*args, **kwargs):
        username = input("name>>>:").strip()
        password = input("passwd>>>:").strip()
        if username.lower() == 'hans'  and password == '123':
           res =  func(*args, **kwargs)
           return res
        else:
            print("您没有登录")

    return auth

@decorator     # 装饰器语法糖
@get_time
def index():
    time.sleep(1)
    print("Hello index()")
  
index()

# 执行结果:
[root@hans_tencent_centos82 tmp]# python3 r.py 
name>>>:hans   # 执行认证装饰器
passwd>>>:123
Hello index()
funcation execute time:1.00    # 执行了统计时间的装饰器

多个装饰器叠加使用时,自下而上执行。

decorator(get_time(index))

2.9 装饰器修复技术

上面的操作看着是直接执行原来的函数,但是使用printhelp就能看到它原来的样子。

import time

def get_time(func):
    def inner(*args, **kwargs):
        start_time = time.time() 
        res = func(*args, **kwargs)
        stop_time = time.time() 
        print("funcation execute time:%.2f" % (stop_time - start_time))
        return res 
    return inner

@get_time
def index():
    time.sleep(1)
    print("Hello index()")
  
index()

print(index)
help(index)
# 执行结果:
Hello index()
funcation execute time:1.00
    
<function get_time.<locals>.inner at 0x7f9b58cff2f0>  # print(index) 打印结果

Help on function inner in module __main__:   # help(index) 结果
inner(*args, **kwargs)

可以看到indexget_time里面的inner函数。

如何让打印的时候看到的依然是index函数,可以使用functools包中的wraps模块

import time
from functools import  wraps

def get_time(func):
    @wraps(func)
    def inner(*args, **kwargs):
        start_time = time.time() 
        res = func(*args, **kwargs)
        stop_time = time.time() 
        print("funcation execute time:%.2f" % (stop_time - start_time))
        return res 
    return inner

@get_time
def index():
    """ test funcation"""
    time.sleep(1)
    print("Hello index()")
    
index()
print(index)

# 执行结果:
Hello index()
funcation execute time:1.00
<function index at 0x7f5d13f59b70>   # 这次可以看到print(index)打印的为 index不是之前的inner

2.10 有参装饰器

使用装饰器时,可以使装饰器接受参数

# 给装饰器传值,并根据传的值来判断认证的方式

import time

from functools import wraps

def give_args(source):
    def loginAuth(func):
        @wraps(func)
        def auth(*args, **kwargs):
            if source == 'file':
                print("file 认证")
                res = func(*args, **kwargs) 
                return res
            elif source == 'MySQL':
                print("MySQL 认证")
                res = func(*args, **kwargs) 
                return res
            else:
                print("Error")

        return auth

    return loginAuth

@give_args("file")
def foo(name):
    print("Hello, %s" % name)
    return 0

res = foo("Hans")

print("返回值为 %s,执行成功" % res)

@give_args("MySQL")
def boo(name):
    print("Hello, %s" % name)
    return 0

res = boo("Jack")

print("返回值为 %s,执行成功" % res)

# 执行结果:
[root@hans_tencent_centos82 tmp]# python3 t.py
file 认证
Hello, Hans
返回值为 0,执行成功
MySQL 认证
Hello, Jack
返回值为 0,执行成功

2.11 练习

# 判断七句print执行顺序
def outter1(func1):
    print('加载了outter1')
    def wrapper1(*args, **kwargs):
        print('执行了wrapper1')
        res1 = func1(*args, **kwargs)
        return res1
    return wrapper1

def outter2(func2):
    print('加载了outter2')
    def wrapper2(*args, **kwargs):
        print('执行了wrapper2')
        res2 = func2(*args, **kwargs)
        return res2
    return wrapper2

def outter3(func3):
    print('加载了outter3')
    def wrapper3(*args, **kwargs):
        print('执行了wrapper3')
        res3 = func3(*args, **kwargs)
        return res3
    return wrapper3


@outter1
@outter2
@outter3
def index():
    print('from index')
    
index()   

执行过程:

image

执行结果:

[root@hans_tencent_centos82 tmp]# python3 u.py 
加载了outter3
加载了outter2
加载了outter1
执行了wrapper1
执行了wrapper2
执行了wrapper3
from index
posted on 2021-11-17 17:06  Hans_Wang  阅读(166)  评论(0编辑  收藏  举报

回到顶部