Python闭包和装饰器

Python闭包和装饰器

一、函数的知识

1、函数名的作用
  • 函数名存放的是函数所在内存空间的地址
  • 函数名()执行函数名所存放空间地址中的代码
  • 函数名可以像普通变量一样赋值,赋值后的结果与原函数名作用是一样的
# 1.定义函数
def func():
    print('这是一个函数')

# 2.查看函数的作用
# 2.1 函数名
print(func)  # <function func at 0x0000014CC4AB1000>
# 2.2 调用函数
func()  # 这是一个函数
# 2.3 函数名可以像其他一样赋值
func2 = func
print(func2)  # <function func at 0x0000014CC4AB1000>
# 2.4 变量执行函数
func2()  # 这是一个函数
2、函数作为参数传递

当把函数名直接作为实参进行传递时,本质上传递的是:对应函数的引用地址值。

例如,将函数作为实参来传递:

(1)定义一个无参函数test();

(2)定义一个有一个参数的函数func();

(3)把无参函数test()的函数名传递给有参函数func(),并观察效果。

# 定义函数1
def test():
    print('这是一个函数')


# 定义函数2
def func(x):
    x()


# 调用函数
test()  # 这是一个函数
func(test)  # 这是一个函数

二、闭包

1、作用域

在Python代码中,作用域分为两种情况:全局作用域和局部作用域

在全局定义的变量 => 全局变量

在局部定义的变量 => 局部变量

2、全局变量与局部变量的访问范围

① 在全局作用域中可以访问全局变量,在局部作用域中可以访问局部变量

示例:

# 全局作用域(全局变量)
num1 = 10
def func():
    # 局部作用域(局部变量)
    num2 = 20
    # ① 在局部访问局部变量
    print(num2)

# ① 在全局访问全局变量
print(num1)  # 10
# 调用函数
func()  # 20

② 在局部作用域中可以访问全局变量

示例:

# 全局作用域(全局变量)
num1 = 10
def func():
    # ② 在局部作用域中可以访问全局变量
    print(num1)

# 调用函数
func()  # 10

③ 在全局作用域中不能访问局部变量

示例:

# 全局作用域(全局变量)
num1 = 10
def func():
    # 局部作用域(局部变量)
    num2 = 20

# 调用函数
func()
# 在全局作用域中调用局部变量num2
print(num2)

'''
NameError: name 'num2' is not defined. Did you mean: 'num1'?
变量num2没有被定义,你是想要num1吗?
'''
3、闭包的构成条件(三步走)

第一步:有嵌套

第二步:有引用

第三步:有返回(return)

闭包的语法:

# 外部函数
def 外部函数名(外部参数):
    # 内部函数
    def 内部函数名(内部参数):
        ...[使用]
    return 内部函数名

概念:在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数。我们把这个使用外部函数变量的内部函数称为闭包。

快速体验:

# 定义一个用于求和的闭包
def outer(num1):
    def inner(num2):
        sum = num2 + num1
        print(f'两数之和是{sum}')

    return inner


# 调用闭包
fun = outer(10)
fun(20)

'''
两数之和是30
'''

闭包的作用:闭包可以保存函数内的变量,而不会随着调用完函数而被销毁。

4、在闭包的内部实现对外部变量的修改(nonlocal关键字)

nonlocal:声明能够让内部函数去修改外部函数的变量

语法:

nonlocal 变量名

例如,编写一个闭包,让内部函数去访问外部函数内的参数a = 100,修改值,a = a + 1,并观察效果。

def outer():
    a = 100
    def inner():
        nonlocal a
        a=a+1
        print(f'a的取值{a}')
    return inner

f =outer()
f()  # a的取值101

三、装饰器

1、什么是装饰器

在不改变现有函数源代码以及函数调用方式的前提下,实现给函数增加额外的功能。

装饰器的构成条件:

  • 有嵌套:在函数嵌套的前提下;
  • 有引用:内部函数使用了外部函数的变量(参数);
  • 有返回:外部函数返回了内部函数名;
  • 有额外功能:给需要装饰的原有函数增加额外功能。
2、使用语法

方式一:传统方式:

语法:

变量名 = outer([外面参数列表])
变量名([内部参数列表])

示例:

'''
例如,发表评论前,都是需要先登录的。
此处,先定义有发表评论的功能函数,然后在不改变原有函数的基础上,需要提示用户要先登录~。
'''
def check(fn):
    def inner():
        # 开发登录功能
        print('登录功能')
        # 调用原函数
        fn()
    return inner


# 评论功能(前提:登录)
def comment():
    print('评论功能')

comment = check(comment)
comment()

'''
登录功能
评论功能
'''

方式二:语法糖

语法:

@outer
def 函数名():
	...

示例:

'''
例如,发表评论前,都是需要先登录的。
此处,先定义有发表评论的功能函数,然后在不改变原有函数的基础上,需要提示用户要先登录~。
'''


def check(fn):
    def inner():
        # 开发登录功能
        print('登录功能')
        # 调用原函数
        fn()

    return inner


# 评论功能(前提:登录)
@check
def comment():
    print('评论功能')


# 调用
comment()

'''
登录功能
评论功能
'''
3、装饰器的使用

函数的分类

无参无返回值的函数
    定义: def 函数名(): ...
    调用: 函数名()

有参无返回值的函数
    定义: def 函数名(形参): ...
    调用: 函数名(实参)
    
无参有返回值的函数
    定义: def 函数名(): ... return 返回值
    调用: 用变量接收返回值 = 函数名()
    
有参有返回值的函数
    定义: def 函数名(形参): ... return 返回值
    调用: 用变量接收返回值 = 函数名(实参)

用法一:装饰无参无返回值的函数

示例:

def func1_01(fn):
    def func_in():
        print('正在计算。。。')
        fn()

    return func_in


@func1_01
def func2_01():
    a = 10
    b = 20
    c = a + b
    print('c-->', c)


func2_01()

'''
正在计算。。。
c--> 30
'''

用法二:装饰有参无返回值的函数

示例:

def func1_02(fn):
    def func_in(a, b):
        print('正在计算。。。')
        fn(a, b)

    return func_in


@func1_02
def func2_02(a, b):
    c = a + b
    print('c-->', c)


func2_02(20, 20)

'''
正在计算。。。
c--> 40
'''

用法三:装饰无参有返回值的函数

示例:

def func1_03(fn):
    def func_in():
        print('正在计算。。。')
        return fn()

    return func_in


@func1_03
def func2_03():
    a = 20
    b = 30
    c = a + b
    print('c-->', c)
    return c


func2_03()

'''
正在计算。。。
c--> 50
'''

用法四:装饰有参有返回值的函数

示例:

def func1_04(fn):
    def func_in(a, b):
        print('正在计算。。。')
        return fn(a, b)

    return func_in


@func1_04
def func2_04(a, b):
    c = a + b
    print('c-->', c)
    return c


func2_04(30, 30)

'''
正在计算。。。
c--> 60
'''

用法五:通用装饰器

示例:

'''
*args: 元组类型
**kwargs: 字典类型
'''
def func1_05(fn):
    def func_in(*args, **kwargs):
        print('正在计算。。。')
        return fn(*args, **kwargs)

    return func_in


@func1_05
def func2_05(*args, **kwargs):
    sum = 0
    for value in args:
        sum += value
    for value in kwargs.values():
        sum += value
    print('位置不定长参数-->', sum)
    return sum


func2_05(30, 30, a=5, b=2, c=8)

'''
正在计算。。。
位置不定长参数--> 75
'''

四、装饰器进阶

1、多个装饰器装饰一个函数

多个装饰器的装饰过程是: 离函数最近的装饰器先装饰,然后外面的装饰器再进行装饰,由内到外的装饰过程。

示例:

'''
例如,发表评论前,都是需要先登录用户并进行验证码验证。
先定义有发表评论的功能函数,然后在不改变原有函数的基础上,需要提示用户要先登录和输入验证码
'''
def func_06(func):
    def func_in():
        print('先登录')
        func()

    return func_in


def func_08(func):
    def func_in():
        print('验证码')
        func()

    return func_in


@func_06
@func_08
def func_07():
    print('发表评论')


func_07()

'''
先登录
验证码
发表评论
'''
2、带有参数的装饰器

使用带有参数的装饰器,其实是在装饰器外面又包裹了一个函数,使用该函数接收参数,返回装饰器。

基本语法:

def 装饰器(fn):
    ...

@装饰器('参数')
def 函数():
    # 函数代码

示例:根据传递参数不同,打印不同的日志信息

def logging(flag):
    # flag = + 或 flag = -
    def decorator(fn):
        def inner(*args, **kwargs):
            if flag == '+':
                print('-- 日志信息:正在努力进行加法运算 --')
            elif flag == '-':
                print('-- 日志信息:正在努力进行减法运算 --')
            return fn(*args, **kwargs)
        return inner
    return decorator

@logging('+')
def sum_num(a, b):
    result = a + b
    return result

@logging('-')
def sub_num(a, b):
    result = a - b
    return result


print(sum_num(10, 20))
print(sub_num(100, 80))

'''
-- 日志信息:正在努力进行加法运算 --
30
-- 日志信息:正在努力进行减法运算 --
20
'''
3、扩展:类装饰器(了解)

装饰器还有一种特殊的用法就是类装饰器,就是通过定义一个类来装饰函数。

基本语法:

class 类装饰器():
    # 装饰器代码

@类装饰器名称
def 函数():
    # 函数代码

类装饰器的编写规则:

  • 必须有一个__init__初始化方法,用于接收要装饰函数的函数
  • 必须把这个类转换为可以调用的函数

问题:如何把一个类当作一个装饰器函数进行调用(把类当作函数)

示例:编写一个Check类装饰器,用于实现用户的权限验证

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函数的装饰,可以添加额外的功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值