python的闭包和装饰器

本文深入探讨了Python中的闭包概念,展示了如何利用闭包修改数据,并详细解释了装饰器的工作原理及其在不同场景下的应用,包括单个参数、不定长参数及带有返回值的函数装饰。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

闭包

作用域

定义在函数内部的变量是局部变量。局部变量不能在外部使用。示例:

def fun():
    num = 10 # 局部变量
print(num)  # NameError: name 'num' is not defined

函数嵌套

在一个函数A内部再定义一个函数B,这个B就是嵌套函数。

def outer():
    # outer 是外围函数
    msg = "I love you"

    def inner():
        # inner是嵌套函数
        print(msg)
    inner()
print_msg()  # 输出 I love you

闭包

外部函数A定义了局部变量a,内部函数B使用了a,且A的返回值为函数B。

def outer():
    msg = "I love you"
    def inner():
        print(msg)
    return inner
c = outer()
c()  # 输出	I love you

一般情况下,函数中的局部变量仅在函数的执行期间可用,闭包使得局部变量在函数外被访问了。

这里的c就是一个闭包,闭包本质是一个函数,它由inner函数和变量msg组成

闭包 c
函数 inner
变量 msg

闭包——修改数据

x = 3

def test1():
    x = 2

    def test2():
        print('-----A------%d' % x)
        x = 1
        print('-----B------%d' % x)
    return test2

t = test1()
t()     # 报错。

程序报错,如果注释掉第8行,则输出两次2。

这是因为在第一次print时,发现函数内部有x,却没有在print语句前找到自己的x,所以报错。

类似声明全局变量一样(global),要声明nonlocal

    def test2():
        nonlocal x
        print('-----A------%d' % x)
        x = 1
        print('-----B------%d' % x)
    return test2

装饰器

开放封闭

  • 封闭:已经实现的功能代码块 不允许被修改
  • 开放:但可以给这个代码块增加一些扩展

情景引入:

公司已经开发了一个登录的功能,每个人都可以登录。但由于疫情爆发,现在要对登录功能加个验证:只有不发骚的人才能登录。

装饰器写法

# ↓↓ 先来一个闭包
def set_func(func):
    def call_func():
        print('……验证是否发骚中……')
        func()
    return call_func

# @set_func
def login():
    print('……排队进入中……')

login()  # 输出	……排队进入中……

当取消第8行的注释时,输出

……验证是否发骚中……
……排队进入中……

不改动函数内部的代码,利用@func的形式,给原函数增加功能,这个func就是装饰器

装饰器实现过程

def set_func(func):
    def call_func():
        print('……验证是否发骚中……')
        func()

    return call_func

def login():
    print('……排队进入中……')
    
# 步骤一:
ret = set_func(login)
ret()

用变量ret指向我的闭包函数,然后ret()执行,就完成了登录之前校验的需求。

变量名可以随意取,那么把ret叫做login也没问题:

# 步骤2——>因为ret可以随意起名
login = set_func(login)
login()

写法简化:

# 步骤3——>写法简化
@set_func   # 等价于 login = set_fun(login)
def login():
    print('……排队进入中……')

对xx函数进行装饰

单个参数的函数

def set_func(func):
    def call_func(n):
        print('……验证是否发骚中……')
        func(n)

    return call_func


@set_func
def login(num):
    print('……%d号正在排队进入中……' % num)


login(6)
# 输出:	
# ……验证是否发骚中……
# ……6号正在排队进入中……

不定长参数的函数

def set_func(func):
    def call_func(*args, **kwargs):  # 这里的*表示告诉python这里用多个参数传递
        print('……验证是否发骚中……')
        func(*args, **kwargs)       # 这里的*表示要进行拆包

    return call_func


@set_func
def login(num, *args, **kwargs):
    print('……%d号正在排队进入中……' % num)
    print('……正在排队进入中……', args)
    print('……正在排队进入中……', kwargs)


login(6)
login(6, 7)
login(6, 7, 8)
login(6, 7, 8, mm=10)

有返回值的函数

def set_func(func):
    def call_func(*args, **kwargs):
        print('……验证是否发骚中……')
        return func(*args, **kwargs)
    return call_func


@set_func
def login(num, *args, **kwargs):
    print('……%d号正在排队进入中……' % num)
    print('……正在排队进入中……', args)
    print('……正在排队进入中……', kwargs)
    return 'ok'


ret = login(6)
print(ret)

↑↑ 这也是通用装饰器的写法。

多个装饰器装饰同一函数

def set_func1(func):
    print("……开始装饰权限1……")
    def call_func(*args, **kwargs):
        print('……在原功能之前新增功能1……')
        func(*args, **kwargs)
        print('……在原功能之后新增功能1……')
    return call_func


def set_func2(func):
    print("……开始装饰权限2……")
    def call_func(*args, **kwargs):
        print('……在原功能之前新增功能2……')
        func(*args, **kwargs)
        print('……在原功能之后新增功能2……')
    return call_func


@set_func1
@set_func2
def login():
    print('……原函数执行中……')

login()

终端结果:

……开始装饰权限2……
……开始装饰权限1……
……在原功能之前新增功能1……
……在原功能之前新增功能2……
……原函数执行中……
……在原功能之后新增功能2……
……在原功能之后新增功能1……
@set_func1
@set_func2 # 我们可以把经过func2装饰后的整体看作一个函数,然后这个新函数被func1装饰
def login():
    print('……原函数执行中……')

相当于:

@set_func1
fun

这个fun相当于:

@set_func2
def login():
    print('……原函数执行中……')

所以,装饰器是按照从内向外的顺序进行功能的增加的

类作为装饰器

class Test:
    def __init__(self, func):
        self.func = func
        pass

    def __call__(self, *args, **kwargs):
        print('……这里是装饰器添加的功能……')
        return self.func()


@Test  # 相当于 get_str = Test(get_str)
def get_str():
    print('hahah')
    return 'haha'

get_str()

可以按照@Test相当于get_str = Test(get_str)来倒推。

其他的,可以用@Test.xx来做扩展。

带有参数的装饰器

def set_func(func):
    def call_func():
        print('……权限级别1级,验证……')
        return func()

    return call_func


@set_func
def test1():
    print('……test1……')
    return 'ok'


@set_func(1)  # 带有参数的装饰器
def test2():
    print('……test2……')
    return 'ok'

如果装饰器后带有参数(第15行),其实是做了两步操作

  • 1、调用set_func且将1当作实参传递
  • 2、用上一步调用得到的返回值,当作装饰器,再对test2进行装饰
# 修改原来的装饰器:在原装饰器的外部套函数
def set_level(level_num):
    def set_func(func):
        def call_func():
            if level_num == 1:
                print('……权限级别1级,验证……')
            elif level_num == 2:
                print('……权限级别2级,验证……')
            return func()
        return call_func
    return set_func

@set_level(1)
def test1():
    print('……test1……')
    return 'ok'

主要方法:

  • 写好满足需求的普通装饰器(不带参数)
  • 在原来的装饰器外层套函数,返回值是原装饰器
  • 要用什么参数,就只在最外层的函数内传什么参数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值