day04闭包装饰器
函数参数
函数名作为对象
细节
-
Python是一门以 面向对象为基础的语言, 一切皆对象, 所以: 函数名也是对象.
-
直接打印函数名, 打印的是函数的地址. 函数名()则是在调用函数.
-
函数名可以作为对象使用, 所以它可以像变量一样赋值, 且赋值后的 变量名() 和 调用 原函数名() 效果是一样的.
格式
def func(): print('hello word') f = func f() # hello word
演示
# 需求: 定义1个无返回值的函数func01(), 并直接输出函数名观察效果. # 1. 定义函数. def func01(): print('hello world!') # return None # 在main函数中测试 if __name__ == '__main__': # 2. 直接打印函数名, 打印的是函数的地址. 说明 函数名 = 对象 print(func01) # <function func01 at 0x000002069305A200> # 3. 写 函数名() 则是在调用函数. func01() # hello world print('-' * 21) print(func01()) # hello world先输出, 再打印None print('-' * 21) # 4. 函数名可以作为对象使用, 所以它可以像变量一样赋值, 且赋值后的 变量名() 和 调用 原函数名() 效果是一样的. f = func01 # 把 函数名(函数的地址) 赋值给变量f print(f) # <function func01 at 0x000002069305A200> f() # hello world print('-' * 21) print(f()) # hello world先输出, 再打印None
函数名作为实参传递
函数名的作用
-
函数名可以作为对象, 像变量那样赋值.
-
函数名可以作为实参进行传递.
代码
# 需求: 定义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
闭包
介绍
概述
属于python的一种独有写法, 可以实现,: 对外部变量进行临时存储
回顾
函数内的局部变量在函数调用结束后生命周期结束
作用
延长函数内(外部函数) 局部变量的生命周期
构成条件
-
有嵌套 外部函数内嵌套 内部函数
-
有引用 在内部函数中使用 外部函数的变量
-
有返回 在外部函数中, 返回内部函数名, 即: 内部函数 对象
格式
def 外部函数名(形参列表): ...... def 内部函数名(形参列表): # 有嵌套 ...... 使用外部函数的变量 # 有引用 return 内部函数名 # 有返回
入门代码
def fn_outer(num1): def fn_inner(num2): # 有嵌套 sum = num1 + num2 # 有引用 print(f'求和结果:{sum}') return fn_inner # 有返回 if __name__ == '__main__': fn = fn_outer(10) fn(10) fn(11) fn(12)
执行流程图解
nonlocal介绍
概述
nonlocal: 可以实现在内部函数中, 修改外部函数的 变量值
类似于global关键字
给内部函数赋权, 使其可以修改外部函数的变量值
演示
def fn_outer(): a = 100 def fn_inner(): nonlocal a # 使用nonlocal关键字 a += 10 # 修改外部函数的变量 print(f'a:{a}') return fn_inner if __name__ == '__main__': fn = fn_outer() fn() # a:110 fn() # a:120 fn() # a:130
图解
装饰器
在不改变原有函数的基础上给原有函数增加额外的功能
介绍
概述
装饰器也是闭包的一种, 即: 装饰器 = 闭包 + 额外的功能
前提
-
有嵌套
-
有引用
-
有返回
-
有额外功能
格式
def 外部函数(形参列表): ...... def 内部函数(形参列表): 功能扩展 ...... 使用外函数变量 return 内部函数名
用法
方式1: 传统用法
变量名 = 装饰器名(被装饰函数名) => 变量名一般为被装饰函数名
变量名()
方式2: 语法糖
在被装饰的函数上, 写: @装饰器名, 之后跟调用普通函数方式一样
代码演示
# 定义装饰器, 增加登录功能 def check_login(fn_name): def inner(): print('登陆中...') fn_name() return inner # 定义评论功能 @check_login def comment(): print('评论成功!!!') if __name__ == '__main__': # 方式1, 传统写法 # comment = check_login(comment) # comment() # 方式2, 语法糖 comment()
装饰器的用法
装饰器的内部函数 格式要和 被装饰函数的格式保持一致 即:
被装饰函数是无参无返回值, 则: 装饰器的内部函数 也是 无参无返回值
被装饰函数是有参无返回值, 则: 装饰器的内置函数 也是 有参无返回值
......
无参无返回
# 需求: 在无参无返回值的原有求和函数前, 添加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()
有参无返回
# 需求: 在有参无返回值的原有求和函数前, 添加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)
无参有返回
# 需求: 在无参有返回值的原有求和函数前, 添加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}')
有参有返回
# 定义装饰器, 装饰有参数有返回值的函数 def print_info(fn_name): def inner(a, b): print('正在计算中') return fn_name(a, b) return inner # @print_info def get_sum(a, b): num_sum = a + b return num_sum if __name__ == '__main__': # 调用方式1: 语法糖 # print(get_sum(10, 20)) # 调用方式2: 传统方式 get_sum = print_info(get_sum) print(get_sum(10, 20))
装饰不定长参数
# 定义装饰器装饰带有不定长参数的函数 def print_info(fn_name): def inner(*args, **kwargs): print('正在努力计算中') return fn_name(*args, **kwargs) return inner # 定义函数, 同时接收不定长位置参数和关键字参数, 遍历累加求和 @print_info def get_sum(*args, **kwargs): num_sum = 0 # 遍历位置参数并求和 for i in args: num_sum += i # 遍历关键字参数并求和 for j in kwargs.values(): num_sum += j return num_sum if __name__ == '__main__': # 调用方式1 # get_sum = print_info(get_sum) # print(get_sum(1, 2, 3, 4, a=5, b=6, c=7)) # 调用方式2 print(get_sum(1, 2, 3, 4, a=5, b=6, c=7))
多装饰器装饰1个函数
由内到外装饰(传统写法), 从上到下执行(装饰器)
代码
def check_login(fn_name): def inner(): print('登录校验中...') fn_name() return inner def check_code(fn_name): def inner(): print('登录校验中...') fn_name() return inner @check_login @check_code def comment(): print('评论成功!!!') if __name__ == '__main__': # 方式1 # cc = check_code(comment) # cl = check_login(cc) # cl() # 方式2 comment()
图解
带有参数的装饰器(一个装饰器装饰多个函数)
一个装饰器的参数只能有一个
如果装饰器有多个参数, 则需要在该装饰器的外边再定义一层函数, 用于封装多个参数, 并返回装饰器
格式
def logging(flag): def decorator(fn_name): def inner(): if flag == '+': print('正在努力计算加法中') elif flag == '-': print('正在努力计算减法中') fn_name() return inner return decorator @logging('+') def sum_add(): a, b = 1, 2 print(f'求和结果为: {a + b}') @logging('-') def subtract(): a, b = 1, 2 print(f'求和结果为: {a - b}') if __name__ == '__main__': sum_add() subtract()
改进
通过对象的__ name __属性实现
* 代码:
def decorator(fn_name): def inner(): if fn_name == 'sum_add': print('正在努力计算加法中') elif fn_name == 'subtract': print('正在努力计算减法中') fn_name() return inner @decorator def sum_add(): a, b = 1, 2 print(f'求和结果为: {a + b}') @decorator def subtract(): a, b = 1, 2 print(f'求和结果为: {a - b}') if __name__ == '__main__': sum_add() subtract()
扩展:类装饰器(了解)
装饰器还有一种特殊的用法就是类装饰器,就是通过定义一个类来装饰函数。
class 类装饰器(): # 装饰器代码 @类装饰器名称 def 函数(): # 函数代码
举个栗子:编写一个Check类装饰器,用于实现用户的权限验证
''' 类装饰器编写规则: ① 必须有一个__init__初始化方法,用于接收要装饰函数的函数 ② 必须把这个类转换为可以调用的函数 问题:如何把一个类当做一个装饰器函数进行调用(把类当做函数) ''' class Check(): def __init__(self, fn): # fn就是要修饰函数的名称,当Check装饰器类被调用时,系统会自动把comment函数名称传递给fn变量 self.__fn = fn # __call__方法:把一个类转换为函数的形式进行调用 def __call__(self, *args, **kwargs): # 编写装饰器代码 print('请先登录') # 调用comment函数本身 self.__fn(*args, **kwargs) # 编写一个函数,用于实现评论功能,底层comment = Check(comment) @Check def comment(): print('评论功能') # 调用comment函数,实现评论功能 comment()
@Check 等价于 comment = Check(comment), 所以需要提供一个init方法,并多增加一个fn参数。
要想类的实例对象能够像函数一样调用,需要在类里面使用call方法,把类的实例变成可调用对象(callable),也就是说可以像调用函数一样进行调用。
在call方法里进行对fn函数的装饰,可以添加额外的功能。
目标:① 了解闭包的作用以及闭包的基本语法(三步走)
② 能独立完成通用装饰器的编写
③ 能使用装饰器传递参数