Python进阶————闭包与装饰器


闭包与装饰器

  • 前言
  • 一、函数名的使用
    • 1.1 函数名作为对象
    • 1.2 函数名作为实参传递
  • 二、闭包
    • 2.1 闭包作用
    • 2.2 构成闭包的条件
    • 2.3 闭包语法格式
    • 2.4 闭包代码演示
    • 2.5 nonlocal关键字
  • 三、装饰器
    • 3.1 装饰器介绍与代码演示
    • 3.2 装饰器的具体使用
      • 3.2.1 装饰无参无返回值
      • 3.2.2 装饰有参无返回值
      • 3.2.3 装饰无参有返回值
      • 3.2.4 装饰有参有返回值
      • 3.2.5 装饰不定长参数
    • 3.3 多个装饰器装饰一个函数
    • 3.4 一个装饰器装饰多个函数
      • 3.4.1 普通写法
      • 3.4.2 进阶写法
  • 总结


前言

  • 接下来我们对Python中的闭包和装饰器进行分析学习。

一、函数名的使用

  • 1、函数名存放的是函数所在内存空间的地址。(大白话:函数名代表函数入口地址)
  • 2、函数名()执行函数名所存放空间地址中的代码。(大白话:函数名()代表函数调用: 直接调用)
  • 3、函数名可像普通变量一样赋值, 赋值后的结果与原函数名作用是一样的。(大白话:函数名可以做函数参数,函数名做函数参数是研究的重点)

1.1 函数名作为对象

案例: 演示函数名可以作为对象使用.

# 1. 定义函数.
def func01():
    print('hello world!')
    # return None


# 在main函数中测试
if __name__ == '__main__':
    # 2. 直接打印函数名, 打印的是函数的地址.  说明 函数名 = 对象
    print(func01)   # 

    # 3. 写 函数名() 则是在调用函数.
    func01()        # hello world
    print('-' * 21)
    print(func01()) # hello world先输出, 再打印None
    # 当你调用一个函数时,该函数会执行其内部的代码块。如果函数内部有print语句,那么这些print语句会首先执行,将指定的内容输出到控制台。
    # 如果函数内部没有return语句(或者return后面没有跟任何值,这等同于return None),那么函数执行完毕后会自动返回None。
    print('-' * 21)

    # 4. 函数名可以作为对象使用, 所以它可以像变量一样赋值, 且赋值后的 变量名()  和 调用 原函数名() 效果是一样的.
    f = func01      # 把 函数名(函数的地址) 赋值给变量f
    print(f)        # 
    f()             # hello world
    print('-' * 21)
    print(f())      # hello world先输出, 再打印None

1.2 函数名作为实参传递

"""
回顾:
    Python是一门以面向对象为基础的语言, 所以: 一切皆对象.
    函数名 也是 对象, 代表着函数的入口.

函数名 的作用如下:
    1. 函数名可以作为对象, 像变量那样赋值.
    2. 函数名可以作为实参进行传递.
"""

求: 定义1个无参函数method(), 定义1个带1个参数的函数func(), 把method()的函数名作为参数传递给func(), 并观察结果.

# 1. 定义1个无参函数method()
def method():
    print('我是 method 函数')

def get_sum(a, b):
    print('get_sum函数: 加法运算')
    return a + b

def get_subtract(a, b):
    print('get_subtract函数: 减法运算')
    return a - b

# 2. 定义1个带1个参数的函数func()
def func(fn_name):
    """
    接收函数名(函数对象), 然后调用该函数的.
    :param fn_name: 接收到的函数名(即: 函数对象)
    :return: 无
    """
    print('我是 func 函数')
    fn_name()

# 模拟自定义的计算器.
def my_calculation(a, b, fn_name):
    """
    接收两个整数 和 具体的计算规则, 对整数进行对应的计算.
    :param a: 要操作的第1个整数
    :param b: 要操作的第2个整数
    :param fn_name: 具体的计算规则
    :return: 计算结果.
    """
    return fn_name(a, b)

# 3. 在main函数中测试.
if __name__ == '__main__':
    # 演示: 函数名可以作为实参进行传递
    func(method)
    print('-' * 21)

    # 加法
    result1 = my_calculation(10, 3, get_sum)
    print(f'result1: {result1}')    # 13
    print('-' * 21)

    # 减法
    result2 = my_calculation(10, 3, get_subtract)
    print(f'result2: {result2}')    # 7

二、闭包

  • 调用了外部函数变量的 内部函数, 就称之为: 闭包.

2.1 闭包作用

  • 它可以保存函数内的变量, 即: 变量不会随着函数调用完毕而销毁.

2.2 构成闭包的条件

  • 1、 有嵌套. 函数嵌套, 外函数, 内函数
  • 2、 有引用. 内部函数使用外部函数的变量(包括外部函数的形成那列表)
  • 3、 有返回. 外部函数返回内部函数名(对象)

2.3 闭包语法格式

一定要看清楚return跟谁是一组(跟谁对齐的)

# 闭包格式:
     def 外部函数名(形参列表):            # 有嵌套
         ......
         def 内部函数名(形参列表):        # 有嵌套
             ....
             使用外部函数的变量           # 有引用
         return 内部函数名               # 有返回

2.4 闭包代码演示

代码如下(示例):

# 需求1: 非闭包写法, 定义1个函数用于保存变量10, 然后调用函数返回值变量, 并重复累加数值, 观察结果.
def func():
    return 10

# 调用函数.
print(func() + 1)   # 11
print(func() + 1)   # 11
print(func() + 1)   # 11
print('-' * 30)


# 需求2: 定义用于求和的闭包.
# 其中, 外部函数有参数num1, 内部函数有参数num2, 调用求和, 观察结果.
# 1. 闭包写法, 定义函数.
def fun_outer(num1):            # 有嵌套
    def fun_inner(num2):        # 有嵌套
        # 具体的求和动作.
        sum = num1 + num2       # 有引用
        print(f'sum的值: {sum}')
    return fun_inner            # 有返回值

# 2.调用上述的函数
# 2.1 调用外部函数.
f = fun_outer(10)   # 细节: f就是就是内部函数, 即: f = fun_inner

# 2.2 调用"内部"函数.
f(1)    # 11
f(1)    # 11
f(1)    # 11

2.5 nonlocal关键字

  • 作用: 声明能够让内部函数去修改外部函数的变量
"""
关键字介绍:
    回顾:
        global  修饰的变量叫全局变量, 在当前的模块(.py)文件中, 均可使用.

    nonlocal: 在内部函数中使用, 它可以实现, 在内部函数中 修改外部函数的 变量.
"""

代码如下(示例):


# 需求: 编写1个闭包, 让内部函数去访问外部函数的参数a = 100, 并观察效果.

# 1. 闭包写法, 实现上述需求.
def fun_outer():                    # 有嵌套
    a = 100     # 外部函数的 变量

    # 定义内部函数, 去访问, 并修改外部函数的变量.
    def fun_inner():                # 有嵌套
        # 核心细节: 在内部函数中修改外部函数的变量值, 要通过 nonlocal关键字实现.
        nonlocal a
        a = a + 1
        print(f'a的值为: {a}')       # 有引用

    return fun_inner                # 有返回


# 2. 调用函数.
# 2.1 调用外部函数, 获取其返回值, 即: 内部函数对象
fn = fun_outer()        # fn = fun_inner

# 2.2 调用"内部函数"
fn()
fn()
fn()

三、装饰器

  • 是闭包的一种写法, 本质上: 装饰器 = 闭包函数

3.1 装饰器介绍与代码演示

"""
1、 概述:
      它是闭包的一种写法, 本质上: 装饰器 = 闭包函数
2、 作用:
      在不改变原有函数的基础上, 对原有函数的功能做增强(扩展).
3、 前提:
      1. 有嵌套.         函数嵌套定义, 内部函数, 外部函数.
      2. 有引用.         内部函数使用外部函数的变量.
      3. 有返回值.       外部函数返回内部函数 对象.
      4. 有额外功能.     装饰器用于给 原有函数新增某些功能.
4、 装饰器的用法:
      方式1: 传统方式.
          变量名 = 装饰器名(要被装饰的函数名)
          变量名()       # 调用函数即可.

      方式2: 语法糖.
          在 要被装饰的函数上写  @装饰器名 即可.
          之后就是普通的调用函数格式即可, 已经实现了增强.
"""

代码如下(示例):

# 1.定义装饰器, 用于增加: 登陆功能.
def check_user(fn):         # 有嵌套
    """
    该函数就是充当装饰器的, 用于增加: 登陆功能.
    :param fn: 要被装饰的函数名.
    :return: 无
    """
    def inner():              # 有嵌套
        print('登录中...')     # 有额外功能.
        fn()                  # 有引用
    return inner              # 有返回值


# 2. 定义函数, 表示发表评论.
@check_user
def comment():
    print('发表评论!')

# 3. 在main函数中测试.
if __name__ == '__main__':
    # 4. 直接调用comment()函数
    # comment()

    # 5. 用装饰器 check_user()给 原有函数comment 做功能增强.
    # 方式1: 传统写法.
    # comment = check_user(comment)
    # comment()

    # 方式2: 装饰器语法糖写法.
    comment()

3.2 装饰器的具体使用

  • 一定要
    装饰器的内置函数 格式要和 被装饰的函数 格式保持一致. 即:

1、 被装饰的函数(原函数) 是无参无返回值的, 则: 装饰器的内置函数 也是 无参无返回值.
2、 被装饰的函数(原函数) 是有参无返回值的, 则: 装饰器的内置函数 也是 有参无返回值.

3.2.1 装饰无参无返回值

需求: 在无参无返回值的原有求和函数前, 添加1个友好提示, 即: 正在努力计算中…

# 1. 装饰器.
def print_info(fn_name):    # fn_name: 要被装饰的函数名(原函数名)
    def fn_inner():         # 有嵌套.  装饰器的内部函数 格式 要和 原函数 格式保持一致.
        print('[友好提示] 正在努力计算中...')  # 有额外功能
        fn_name()                          # 有引用
    return fn_inner         # 有返回


# 2. 要被装饰的函数, 即: 原函数(这里是: 无参无返回值的)
@print_info
def get_sum():
    a = 10
    b = 20
    # 求和
    sum = a + b
    # 打印结果.
    print(f'sum: {sum}')

# 在main函数中测试调用.
if __name__ == '__main__':
    # 装饰器 写法1: 传统写法.
    # get_sum = print_info(get_sum)
    # get_sum()

    # 装饰器 写法2: 语法糖
    get_sum()   # [友好提示] 正在努力计算中...    sum: 30

3.2.2 装饰有参无返回值

需求: 在有参无返回值的原有求和函数前, 添加1个友好提示, 即: 正在努力计算中…

# 1. 装饰器.
def print_info(fn_name):    # fn_name: 要被装饰的函数名(原函数名)
    def fn_inner(a, b):         # 有嵌套.  装饰器的内部函数 格式 要和 原函数 格式保持一致.
        print('[友好提示] 正在努力计算中...')  # 有额外功能
        fn_name(a, b)                      # 有引用
    return fn_inner         # 有返回


# 2. 要被装饰的函数, 即: 原函数(这里是: 有参无返回值的)
@print_info
def get_sum(a, b):
    # 求和
    sum = a + b
    # 打印结果.
    print(f'sum: {sum}')

# 在main函数中测试调用.
if __name__ == '__main__':
    # 装饰器 写法1: 传统写法.
    # get_sum = print_info(get_sum)
    # get_sum(100, 200)

    # 装饰器 写法2: 语法糖
    get_sum(10, 3)  # [友好提示] 正在努力计算中...      sum: 13

3.2.3 装饰无参有返回值

需求: 在无参有返回值的原有求和函数前, 添加1个友好提示, 即: 正在努力计算中…

# 1. 装饰器.
def print_info(fn_name):  # fn_name: 要被装饰的函数名(原函数名)
    def fn_inner():  # 有嵌套.  装饰器的内部函数 格式 要和 原函数 格式保持一致.
        print('[友好提示] 正在努力计算中...')  # 有额外功能
        return fn_name()  # 有引用

    return fn_inner  # 有返回


# 2. 要被装饰的函数, 即: 原函数(这里是: 无参有返回值的)
@print_info
def get_sum():
    a, b = 10, 3
    # 求和
    sum = a + b
    # 返回结果.
    return sum


# 在main函数中测试调用.
if __name__ == '__main__':
    # 装饰器 写法1: 传统写法.
    # get_sum = print_info(get_sum)
    # sum1 = get_sum()
    # print(f'sum1: {sum1}')

    # 装饰器 写法2: 语法糖
    sum2 = get_sum()
    print(f'sum2: {sum2}')

3.2.4 装饰有参有返回值

需求: 在有参有返回值的原有求和函数前, 添加1个友好提示, 即: 正在努力计算中…

# 1. 装饰器.
def print_info(fn_name):  # fn_name: 要被装饰的函数名(原函数名)
    def fn_inner(a, b):  # 有嵌套.  装饰器的内部函数 格式 要和 原函数 格式保持一致.
        print('[友好提示] 正在努力计算中...')  # 有额外功能
        return fn_name(a, b)  # 有引用

    return fn_inner  # 有返回


# 2. 要被装饰的函数, 即: 原函数(这里是: 有参有返回值的)
@print_info
def get_sum(a, b):
    # 求和
    sum = a + b
    # 返回结果.
    return sum


# 在main函数中测试调用.
if __name__ == '__main__':
    # 装饰器 写法1: 传统写法.
    # get_sum = print_info(get_sum)
    # sum1 = get_sum(10, 20)
    # print(f'sum1: {sum1}')

    # 装饰器 写法2: 语法糖
    sum2 = get_sum(11, 22)
    print(f'sum2: {sum2}')

3.2.5 装饰不定长参数

需求: 定义一个可以计算 多个数据 和 多个字典value值之和的 函数, 并调用.
要求: 在原有函数的计算结果之前, 加一个友好提示: 正在努力计算中.

# 1. 定义装饰器.
def print_info(fn_name):
    def fn_inner(*args, **kwargs):          # 有嵌套
        print('[友好提示]正在努力计算中...')    # 有额外功能.
        return fn_name(*args, **kwargs)     # 有引用
    return fn_inner                         # 有返回


# 2. 定义原函数, 表示具体的累加动作.
# @print_info
def get_sum(*args, **kwargs):  # (1, 2, 3, 4, 5),  {'a':1, 'b':2, 'c':3}
    # 2.1 定义求和变量, 记录求和结果.
    sum = 0

    # 2.2 回顾: *args, 接收所有的 位置参数, 封装成 元组, 即: (1, 2, 3)
    # 所以这里 等于 元组求和.
    for value in args:
        sum += value

    # 2.3 回顾: **kwargs, 接收所有的 键值对参数, 封装成 字典, 即: {'a':1, 'b':2, 'c':3}
    # 所以这里 等于 字典value值求和.
    for value in kwargs.values():
        sum += value

    # 2.4 返回求和计算结果.
    return sum


# 3. 在main函数中测试.
if __name__ == '__main__':
    # 4. 直接: 调用 get_sum()函数
    #                    位置参数       关键字参数
    # sum = get_sum(11, 22, 33, a=1, b=2, c=3)
    # print(f'求和结果: {sum}')

    # 5. 装饰器, 写法1: 传统方式.
    get_sum = print_info(get_sum)
    sum = get_sum(11, 22, 33, a=1, b=2, c=3)
    print(f'求和结果: {sum}')

    # 6. 装饰器, 写法2: 语法糖.
    # sum = get_sum(11, 22, 33, a=1, b=2, c=3)
    # print(f'求和结果: {sum}')

3.3 多个装饰器装饰一个函数

"""
案例:
    演示多个装饰器装饰1个函数.

细节:
    1. 多个装饰器装饰1个函数时, 会按照 由内到外的 顺序进行装饰, 即: 就近原则.
    2. 简单记忆:
        由内到外装饰(针对于: 传统写法),  从上往下执行(针对于: 语法糖写法).
"""

需求: 发表评论前, 需要先登录, 再进行验证码校验.

# 1. 定义装饰器, 增加 登陆功能.
def check_login(fn_name):
    def fn_inner():                 # 有嵌套
        print('登陆校验中!.....')     # 有额外功能.
        fn_name()                   # 有引用
    return fn_inner                 # 有返回


# 2. 定义装饰器, 增加 验证码校验功能.
def check_code(fn_name):
    def fn_inner():                 # 有嵌套
        print('验证码校验中!.....')     # 有额外功能.
        fn_name()                   # 有引用
    return fn_inner                 # 有返回


# 3. 定义原函数, 表示: 发表评论.
@check_login
@check_code
def comment():
    print("发表评论!")

# 在main函数中, 测试.
if __name__ == '__main__':
    # 4. 测试: 装饰器的写法1 传统写法.
    cc = check_code(comment)    # 增加: 验证码校验功能.
    cl = check_login(cc)        # 增加: 登陆功能.
    cl()
    print('-' * 21)

    # 5. 测试: 装饰器的写法2 语法糖.
    comment()

3.4 一个装饰器装饰多个函数

注意: 一个装饰器的参数只能有一个
如果装饰器有多个参数, 则可以在该装饰器的外边再定义1个函数, 用于封装多个参数, 并返回一个装饰器.

3.4.1 普通写法

需求: 定义1个既能装饰加法运算, 也能装饰减法运算的装饰器, 即: 带有参数的装饰器, 并测试.

# 1. 定义装饰器, 能装饰 加法, 减法运算.
def logging(flag):
    def decorator(fn_name):
    # def decorator(fn_name, flag):     # decorator: 装饰的意思,  这行代码会报错, 因为装饰器的参数只能有1个.
        def fn_inner():         # 有嵌套
            if flag == '+':
                print('[友好提示] 正在努力计算 加法 中...')  # 有额外功能
            elif flag == '-':
                print('[友好提示] 正在努力计算 减法 中...')  # 有额外功能
            fn_name()           # 有引用
        return fn_inner         # 有返回
    return decorator            # 有返回

# 2. 定义原函数, 表示: 加法运算.
@logging('+')
def add():
    a, b = 10, 3
    sum = a + b
    print(f'求和结果: {sum}')


# 3. 定义原函数, 表示: 减法运算.
@logging('-')
def subtract():
    a, b = 22, 11
    sub = a - b
    print(f'求差结果: {sub}')

# 在main函数中测试调用.
if __name__ == '__main__':
    # 4. 测试加法.
    add()
    print('-' * 21)

    # 5. 测试减法.
    subtract()

3.4.2 进阶写法

"""
案例:
    演示 带有参数的装饰器, 即: 一个装饰器 装饰多个 原函数.

结论:
    1. 一个装饰器的参数只能有1个.
    2. 如果装饰器有多个参数, 则可以在该装饰器的外边再定义1个函数, 用于封装多个参数, 并返回一个装饰器.

扩展:
    Python中的每个函数(对象)都有一个属性 __name__, 可以获取该函数的 名字.
"""

需求: 定义1个既能装饰加法运算, 也能装饰减法运算的装饰器, 即: 带有参数的装饰器, 并测试.

# 1. 定义装饰器, 能装饰 加法, 减法运算.
def decorator(fn_name):
    def fn_inner():         # 有嵌套
        # fn_name = 函数名 = 函数对象.
        # print(fn_name)      # 地址值, 说明 fn_name = 函数对象
        # print(fn_name.__name__) # 根据函数对象, 获取该函数的名字.

        if fn_name.__name__ == 'add':
            print('[友好提示] 正在努力计算 加法 中...')  # 有额外功能
        elif fn_name.__name__ == 'subtract':
            print('[友好提示] 正在努力计算 减法 中...')  # 有额外功能
        fn_name()           # 有引用
    return fn_inner         # 有返回

# 2. 定义原函数, 表示: 加法运算.
@decorator
def add():
    a, b = 10, 3
    sum = a + b
    print(f'求和结果: {sum}')


# 3. 定义原函数, 表示: 减法运算.

@decorator
def subtract():
    a, b = 22, 11
    sub = a - b
    print(f'求差结果: {sub}')

# 在main函数中测试调用.
if __name__ == '__main__':
    # 4. 测试加法.
    add()
    print('-' * 21)

    # 5. 测试减法.
    subtract()

总结

  • 以上就是今天要讲的python中的闭包与装饰器的全部内容。

你可能感兴趣的:(Python进阶知识,python,开发语言)