12 Python的闭包和装饰器

一、 函数对象

函数对象指的是函数可以被当做’数据’来处理,具体可以分为四个方面的使用,我们如下

1.1 函数可以被引用

def add(x,y):
    return x+y

func = add
res = func(1,2)
print(res)

1.2 函数可以作为容器类型的元素

def add(x,y):
    return x+y

dic={'add':add,'max':max}
print(dic)
# {'add': <function add at 0x100661e18>, 'max': <built-in function max>}
res = dic['add'](1,2)
print(res)
# 3

1.3 函数可以作为参数传入另外一个函数

def add(x,y):
    return x+y

def foo(x,y,func):
    return func(x,y)

res = foo(1,2,add)
print(res)
# 3

1.4 函数的返回值可以是一个函数

def add(x,y):
    return x+y

def bar():
    return add

func = bar()
res = func(1, 2)
print(res)
# 3

二、 闭包函数

闭包函数 = 名称空间和作用域+函数嵌套+函数对象

核心:名字的查找关系是以函数定义阶段为准

2.1 什么是闭包函数

闭的意思是,该函数是内嵌函数,在别的函数内部定义出来的函数

def index():
	def foo():
		pass

包的意思指的是该函数包含对外层函数(不是对全局作用域)作用域名字的引用

def index():
	x = 1
	def foo():
		print(x)

函数对象

def f1():
	x = 333
	def f2():
		print(x)
	return f2

2.2 为何要有闭包函数

得到了两种为函数体传参的方式

# 方式1:直接把函数体需要的参数定义成形参
def index(x):
    print(x)
    
# 闭包的方式为函数传参
def f1(x):
	def f2():
		print(x)
	return f2

三、装饰器

3.1 什么是装饰器

器指的是工具,可以定义成函数

装饰指的是为其他事物添加额外的东西

装饰器:定义一个函数,该函数是用来为其他函数添加额外的功能

’装饰’代指为被装饰对象添加新的功能,’器’代指器具/工具,装饰器与被装饰的对象均可以是任意可调用对象。概括地讲,装饰器的作用就是在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能。装饰器经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等应用场景,装饰器是解决这类问题的绝佳设计,有了装饰器,就可以抽离出大量与函数功能本身无关的雷同代码并继续重用

3.2 为何要用装饰器

软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。

软件包含的所有功能的源代码以及调用方式,都应该避免修改,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃,而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器。

装饰器就是在不修改被装饰对象源代码以及调用方式的前提下为被装饰对象添加新功能

3.3 装饰器的实现

函数装饰器分为:无参装饰器和有参装饰两种,二者的实现原理一样,都是’函数嵌套+闭包+函数对象’的组合使用的产物

3.3.1 无参装饰器的实现

如果想为下述函数添加统计其执行时间的功能

import time

def index():
    time.sleep(3)
    print('Welcome to the index page')
    return 200

index() #函数执行

遵循不修改被装饰对象源代码的原则,我们想到的解决方法可能是这样

import time

def index():
    time.sleep(3)
    print('Welcome to the index page')
    return 200

start_time=time.time()
index() #函数执行
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))

考虑到还有可能要统计其他函数的执行时间,于是我们将其做成一个单独的工具,函数体需要外部传入被装饰的函数从而进行调用,我们可以使用参数的形式传入

import time

def index():
    time.sleep(3)
    print('Welcome to the index page')
    return 200

def wrapper(func): # 通过参数接收外部的值
    start_time=time.time()
    res=func()
    stop_time=time.time()
    print('run time is %s' %(stop_time-start_time))
    return res

但之后函数的调用方式都需要统一改成

wrapper(index)
# wrapper(其他函数)

这便违反了不能修改被装饰对象调用方式的原则,于是我们换一种为函数体传值的方式,即将值包给函数,如下

import time

def index():
    time.sleep(3)
    print('Welcome to the index page')
    return 200

def timer(func):
    def wrapper(): # 引用外部作用域的变量func
        start_time=time.time()
        res=func()
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time))
        return res
    return wrapper

这样我们便可以在不修改被装饰函数源代码和调用方式的前提下为其加上统计时间的功能,只不过需要事先执行一次timer将被装饰的函数传入,返回一个闭包函数wrapper重新赋值给变量名 /函数名index,如下

index=timer(index)
#得到index=wrapper,wrapper携带对外作用域的引用:func=原始的index
index() 
# 执行的是wrapper(),在wrapper的函数体内再执行最原始的index

至此我们便实现了一个无参装饰器timer,可以在不修改被装饰对象index源代码和调用方式的前提下为其加上新功能。但我们忽略了若被装饰的函数是一个有参函数,便会抛出异常

import time

def timer(func):
    def wrapper(): # 引用外部作用域的变量func
        start_time=time.time()
        res=func()
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time))
        return res
    return wrapper

def home(name):
    time.sleep(5)
    print('Welcome to the home page',name)

home=timer(home)
home('老杨')
# TypeError: wrapper() takes 0 positional arguments but 1 was given

之所以会抛出异常,是因为home(‘老杨’)调用的其实是wrapper(‘老杨’),而函数wrapper没有参数。wrapper函数接收的参数其实是给最原始的func用的,为了能满足被装饰函数参数的所有情况,便用上*args+**kwargs组合,于是修正装饰器timer如下

import time

def index():
    time.sleep(3)
    print('Welcome to the index page')
    return 200

def timer(func):
    def wrapper(*args,**kwargs):
        start_time=time.time()
        res=func(*args,**kwargs)
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time))
        return res
    return wrapper

def home(name):
    time.sleep(5)
    print('Welcome to the home page',name)

home=timer(home)
home('老杨')

此时我们就可以用timer来装饰带参数或不带参数的函数了,但是为了简洁而优雅地使用装饰器,Python提供了专门的装饰器语法来取代index=timer(index)的形式,需要在被装饰对象的正上方单独一行添加@timer,当解释器解释到@timer时就会调用timer函数,且把它正下方的函数名当做实参传入,然后将返回的结果重新赋值给原函数名

import time

def timer(func):
    def wrapper(*args,**kwargs):
        start_time=time.time()
        res=func(*args,**kwargs)
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time))
        return res
    return wrapper

@timer # home=timer(home)
def home(name):
    time.sleep(5)
    print('Welcome to the home page',name)

home('老杨')

如果我们有多个装饰器,可以叠加多个

@deco3
@deco2
@deco1
def index():
    pass

叠加多个装饰器也无特殊之处,上述代码语义如下

index=deco3(deco2(deco1(index)))
# 无参装饰器
def deco(func):
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        return res
    return wrapper

3.2.2 有参装饰器的实现

def outer(mode):
    def deco(func):
        def wrapper(*args, **kwargs):
            if mode == 'QQ':
                res = func(*args, **kwargs)
                return res
            elif mode =='微信':
                print('无法登录')
        return wrapper
    return deco

@outer('QQ')
def test(name):
    print(f'{name}登录成功')

test('老杨')
def wraper(a):
	def foo(func):
		def outer(*args,**kwargs):
			res = func()
			return res
		return outer
	return foo

让装饰器和原函数一样,包含函数说明(了解即可)

from functools import wraps
def deco(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        return res
    return wrapper

@deco
def test(name):
    """
    这是登录的函数
    :param name:
    :return:
    """
    print(f'{name}登录成功')

print(help(test))
  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值