11.闭包、装饰器

闭包、装饰器

闭包的概念及基本使用

  • 闭包的概念:在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包
  • 闭包构成的条件:
    • 存在函数的嵌套关系
    • 内层函数引用了外层函数的临时变量
    • 外层函数返回内层函数的引用(地址)

闭包中变量问题

  • 内层定义了和外层同名的变量,内层优先使用内层定义的变量,即使定义的变量的代码在内层的最后面
  • 解决办法:当内层存在和外层同名变量,而且内层需要使用外层定义的变量,此时应该使用 nonlocal 关键字进行约束
def function_out(num):
    print("function_out num = ", num)
    def function_in(num_in):
        nonlocal num
        num += num_in
        print("-----function_in-----num=", num)
        print("-----function_in-----num_in=", num_in)
    return function_in

f = function_out(10)  # 获取内层函数的地址保存到f变量中
# function_out num =  10

f(20)
# -----function_in-----num= 10
# -----function_in-----num_in= 20
function_out num =  10
-----function_in-----num= 30
-----function_in-----num_in= 20

装饰器

  • 装饰器的作用:不修改源代码的基础上,给函数增加新的功能
  • 装饰器使用:
    • 存在闭包
    • 需要装饰的函数
  • 写法:
    • @闭包的外层函数名
def function_out(func):
    def function_in():
        print("---开始验证---")
        func()
    return function_in

# @function_out 装饰了login() 函数,底层:login = function_out(login)
@function_out
def login():
    print("---开始登录---")

login()
---开始验证---
---开始登录---

装饰有参数的函数

  • 普通参数
待装饰
@functioin_out 
def login(num):
注意:
1)function_in(参数)
2)func(参数)
  • 可变参数
待装饰
@functioin_out 
def login(*args, **kwargs):
注意:
1)function_in(*args, **kwargs)
2)func(*args, **kwargs)
错误用法:
func(args,kwargs)
# 待装饰函数
def function_out(func):
    def function_in(num):
        print("---开始验证---")
        func(num)
    return function_in

@function_out
def login(num):
    print("---开始登录--- num = ", num)

login(10)
---开始验证---
---开始登录--- num =  10
# 待装饰函数
def function_out(func):
    def function_in(*args, **kwargs):
        print("---开始验证function_in---args = ", args)
        print("---开始验证function_in---kwargs = ", kwargs)
        func(*args, **kwargs)
    return function_in

@function_out
def login(*args, **kwargs):
    print("---开始登录login--- args = ", args)
    print("---开始登录login--- kwargs = ", kwargs)

login(10,20,30)
login(10, a = 20)
---开始验证function_in---args =  (10, 20, 30)
---开始验证function_in---kwargs =  {}
---开始登录login--- args =  (10, 20, 30)
---开始登录login--- kwargs =  {}
---开始验证function_in---args =  (10,)
---开始验证function_in---kwargs =  {'a': 20}
---开始登录login--- args =  (10,)
---开始登录login--- kwargs =  {'a': 20}

装饰有返回值的函数

  • 待装饰的函数必须有返回值(return)
  • 闭包的内层函数 func(*args,*kwargs) 改为 return func(args,**kwargs)
# 待装饰函数
def function_out(func):
    def function_in(*args, **kwargs):
        print("---开始验证function_in---args = ", args)
        print("---开始验证function_in---kwargs = ", kwargs)
        return func(*args, **kwargs)
    return function_in

@function_out
def login(*args, **kwargs):
    print("---开始登录login--- args = ", args)
    print("---开始登录login--- kwargs = ", kwargs)
    return args,kwargs

login(10, a = 20)
print()
print(login(20, a = 30))
---开始验证function_in---args =  (10,)
---开始验证function_in---kwargs =  {'a': 20}
---开始登录login--- args =  (10,)
---开始登录login--- kwargs =  {'a': 20}

---开始验证function_in---args =  (20,)
---开始验证function_in---kwargs =  {'a': 30}
---开始登录login--- args =  (20,)
---开始登录login--- kwargs =  {'a': 30}
((20,), {'a': 30})

在原装饰器上设置外部变量

  • 作用:向装饰器内部传递参数
  • 使用:
def test(path):
    print(path)
    def function_out(func):
        """外层函数"""
        print("function_out path= ", path)
        def function_in():
            print("----开始验证----")
            func()
        # 返回内层函数的引用
        return function_in
    # 返回装饰器的引用
    return function_out
  • 使用:@test("login.py")
    • test("login.py") ---> function_out
    • @ 第一步的结果 ---> @ founction_out
def test(path):
    print(path)
    def function_out(func):
        """外层函数"""
        print("function_out path =", path)
        def function_in():
            print("----开始验证----")
            func()
        # 返回内层函数的引用
        return function_in
    # 返回装饰器的引用
    return function_out

@test("login.py")   # 先执行 test("login.py") ---> function_out 引用 ---> @第一步的结果 @function_out
# @function_out   
def login():
    print("---开始登录login---")

@test("注册")
def register():
    print("---开始注册---")

login()
register()
login.py
function_out path = login.py
注册
function_out path = 注册
----开始验证----
---开始登录login---
----开始验证----
---开始注册---

多重装饰器

  • 多重装饰器:给一个函数进行多次装饰
  • 装饰原则:就近原则(靠近待装饰函数的先装饰,随后一层一层装饰)

<b>helloworld-1</b> | <i>helloworld-2</i> | <i><b>helloworld-3</b></i>

# 定义一个让文字加粗的装饰器
def makeBlod(func):
    def function_in():
        return "<b>"+func()+"</b>"
    return function_in

# 定义一个让文字倾斜的装饰器
def makeItalic(func):
    def function_in():
        return "<i>"+func()+"</i>"
    return function_in

@makeBlod
def test():
    return "helloworld-1"

@makeItalic
def test2():
    return "helloworld-2"

@makeItalic         # 倾斜后装饰
@makeBlod           # 就近原则,加粗先装饰
def test3():
    return "helloworld-3"

print(test())     # <b>helloworld-1</b>
print(test2())    # <i>helloworld-2</i>
print(test3())    # <i><b>helloworld-3</b></i>
<b>helloworld-1</b>
<i>helloworld-2</i>
<i><b>helloworld-3</b></i>

类装饰器

  • 作用:使用一个类为一个函数装饰
  • 类的书写:必须有两个方法

    • init 方法,必须接收 装饰器传递的参数 func
    • call 方法, self.func()
  • 格式:@类名

    待装饰的函数

login = Test(login)

  • 对象名() 调用对象的 call()
from typing import Any

class Test(object):

    def __init__(self) -> None:
        print("---init---方法")

    def run(self):
        print("---正在运行---")

    def __call__(self, *args: Any, **kwds: Any) -> Any:
        print("---call---方法")

# 创建对象
test = Test()
# test.run()

# 当对象名(),此时会去调用类中的__call__()方法
test()
---init---方法
---call---方法
# 装饰器类的使用
class Test(object):

    def __init__(self, func):
        print("---init---方法")
        print("---func---", func)
        # func 是login函数的引用
        self.func = func

    def run(self):
        print("---正在运行---")

    def __call__(self, *args, **kwds):
        print("---call---方法",*args, **kwds)
        print("---func---", self.func)
        self.func()

@Test  # login = Test(login), 
def login():
    print("---开始登录---")

login()
---init---方法
---func--- 
---call---方法
---func--- 
---开始登录---