python装饰器与闭包_python中的闭包和装饰器

闭包函数介绍

什么是闭包

维基百科中关于闭包的概念:

在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组 “私有” 变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。

对上面这段话总结一下,即python中的闭包需要满足3个条件:

1) 内嵌函数,即函数里定义了函数 —— 这对应函数之间的嵌套

2) 内嵌函数必须引用定义在外部函数里中的变量(不是全局作用域中的引用)—— 内部函数引用外部变量

3) 外部函数必须返回内嵌函数

闭包函数示例:

def funx():

x = 1

y = 2

def funy():

print(x, y)

return funy # 返回的函数就是 闭包函数

a = funx() # 这里的 变量 a 就是闭包函数

Tip:funx 返回的函数不仅仅是函数本身,返回的函数外面还包了一层作用域关系~

所有的闭包函数都有这个属性:__closure__(若没有就不是闭包函数,这是闭包函数的特点),a.__closure__为元组,每个元素包含了闭包外面的那层作用域中的一个变量的值,a.__closure__[0].cellcontents 和 a.__closure_\[1].cell_contents 分别引用作用域中 x 和 y 变量

print(type(a.__closure__)) #

print(a.__closure__)

# (, )

print(a.__closure__[0].cell_contents) # 1

print(a.__closure__[1].cell_contents) # 2

闭包的应用

闭包函数不光光是函数,还带了一层作用域;在调用外部函数时,可以通过参数传递不同的值,使得返回的闭包函数的作用域中所带的变量各不相同。上面示例中的 x,y 也可以通过参数传入:

def funx(x,y):

def funy():

print(x, y)

return funy # 返回的函数就是 闭包函数

a = funx(1,2) # 这里的 变量 a 就是闭包函数

所以可以总结一下,闭包的特点有2个:

1)自带作用域

2)延迟计算

个人总结了python的闭包大致有如下2个应用:

(1)通过自带的作用域保留状态

def foo(sum = 0):

def add(x):

nonlocal sum # nonlocal 指定这里使用的 sum 为外部函数中的 sum 变量

sum = sum + x

print('sum: ' + str(sum))

return add

g = foo(sum = 10)

g(4) # sum: 14

g(5) # sum: 19

g(6) # sum: 25

如上示例中,每次调用add函数(g())都会将所传递的参数与外部函数中的 sum 变量相加,并打印,参数的总和会保留在闭包函数自带作用域的 sum 变量中。在获取闭包函数时,还可以指定sum的初始大小~

(2)根据自带作用域的局部变量来得到不同的结果

使用自带作用域存储文件名,每次返回的闭包函数仅用于 针对一个文件、不同关键字的过滤

import os, sys

def foo(filename):

def find_line(parse_str):

# 判断文件是否存在

if not os.path.exists(filename):

print('file not exist~')

sys.exit(1)

with open(file=filename, mode='r', encoding='utf-8') as f:

for line in f:

if line.find(parse_str) != -1:

print(line, end='')

return find_line

log_1 = foo(filename=r'F:\tmp\abc.txt') # 专用于对文件 'F:\tmp\abc.txt' 的过滤

log_1('[ERROR]')

log_1('[INFO]')

log_2 = foo(filename=r'F:\tmp\abc.txt') # 专用于对文件 'F:\tmp\abc.txt' 的过滤

log_2('[ERROR]')

log_2('[INFO]')

开放封闭原则

开放封闭原则:对扩展是开放的,对修改是封闭的,即源代码不能修改,留出扩展的可能性~

在维护过程中,很多时候需要对原有的功能(例如函数)进行扩展,但是直接修改某个函数的源代码存在一定风险,理想的状况是在不修改源码的情况下对函数的原有功能进行扩展~

例如现在需要对如下 index 函数进行扩展,计算这个函数的执行时间~

import random, time

def index():

time.sleep(random.randrange(1,5))

print('welcome to index page')

def foo():

start_time = time.time()

index()

stop_time = time.time()

print('run time is %s' % (stop_time - start_time))

# 调用 index 函数

foo() # index()

这样的话,源代码没有发生改变,新功能也添加了,但是调用方式发生了改变,原本调用 index(),现在变成了 foo() ~,且若要为很多个函数添加相同的功能,只能一个一个的添加

现改用闭包函数,可为 多个函数添加相同的功能:

import random, time

def timmer(func):

def warpper():

start_time = time.time()

func()

stop_time = time.time()

print('run time is %s' % (stop_time - start_time))

return warpper

def index():

time.sleep(random.randrange(1,5))

print('welcome to index page')

def error():

time.sleep(random.randrange(2, 10))

print('welcome to error page')

index = timmer(index) # 原本直接调用index(),现在需要添加这么一行

index()

error = timmer(error)

error()

这样还是存在缺陷,就是每次执行前都需要 生成闭包函数(index = timmer(index))。那如何解决呢?就是使用装饰器~

装饰器

装饰器语法

装饰器的语法,在被装饰对象的正上方 添加 '@装饰器名字';将正下方的函数名当做一个参数传递给装饰器,并将返回值重新赋值给函数名~

上面的示例通过装饰器实现:

import random, time

def timmer(func):

def warpper():

start_time = time.time()

func()

stop_time = time.time()

print('run time is %s' % (stop_time - start_time))

return warpper

@timmer # 等效于 index = timmer(index)

def index():

time.sleep(random.randrange(1,5))

print('welcome to index page')

@timmer

def error():

time.sleep(random.randrange(2, 10))

print('welcome to error page')

index() # 调用的是warpper()

error()

这样就满足了开放封闭原则~

多个装饰器的使用

装饰器可以添加多个,执行顺序是 从下往上执行,如下示例中是 先添加auth,再执行timmer ~,即 index 函数先由 auth 进行封装,而后在这个基础之上再由 timmer 进行封装~

def timmer(func):

def warpper():

print('timmer_before_codes')

func() # 执行时这里的 func 就是 deco (即 auth(index))

print('timmer_after_codes')

return warpper

def auth(func):

def deco():

print('auth_before_codes')

func() # 执行时这里是原始的index()

print('auth_after_codes')

return deco

@timmer

@auth

def index():

print('welcome to index page')

index() # 调用 index()

调用index后输出结果如下:

timmer_before_codes

auth_before_codes

index page

auth_after_codes

timmer_after_codes

原始函数有参数的场景

很简单,就是将参数传递到被装饰的函数当中~

def auth(func):

def warpper(user):

print('before_user_login')

func(user)

print('after_user_login')

return warpper

@auth

def login(user):

print('%s login success' % user)

login('kitty')

但是这个时候无参函数无法再使用这个装饰器进行装饰~,*agrs, **kwargs,可以使用可变长参数解决这个问题:

def auth(func):

def warpper(*agrs, **kwargs):

print('before_user_login')

func(*agrs, **kwargs)

print('after_user_login')

return warpper

@auth

def login(user):

print('%s login success' % user)

@auth

def index():

print('welcome to index page')

login('kitty')

index()

原始函数有返回值的场景

若原始函数有返回值,在内部函数中 调用原始函数,获取返回值,并通过内部函数进行返回即可~

def timmer(func):

def warpper(*agrs, **kwargs):

start_time = time.time()

res = func(*agrs, **kwargs)

stop_time = time.time()

print('run time is %s' % (stop_time - start_time))

return res

return warpper

wraps 的常用功能

原始函数被装饰以后,原有的一些属性会被 装饰后的函数所替代,例如文档字符串~

def timmer(func):

def warpper(*agrs, **kwargs):

'warpper function' # 文档字符串

start_time = time.time()

res = func(*agrs, **kwargs)

stop_time = time.time()

print('run time is %s' % (stop_time - start_time))

return res

return warpper

@timmer

def index():

'index function' # 文档字符串

print('welcome to index page')

print(index.__doc__)

输出结果:

warpper function

原本想获取index函数的说明信息,而返回的却是 warpper 函数的。这里可以使用 @wraps(func) ,用以保留原函数自己的一些原始信息;若函数已被装饰,又想调用原始的函数,可以在调用函数时使用函数的 wrapped 属性 就能够使用原始的函数,而不是被装饰后的函数~

def timmer(func):

@wraps(func)

def warpper(*agrs, **kwargs):

'warpper function' # 文档字符串

start_time = time.time()

res = func(*agrs, **kwargs)

stop_time = time.time()

print('run time is %s' % (stop_time - start_time))

return res

return warpper

@timmer

def index():

'index function' # 文档字符串

print('welcome to index page')

print(index.__doc__)

index.__wrapped__() # 调用原始函数

输出结果:

index function

welcome to index page

有参装饰器

之前用到的都是无参装饰器,有参装饰器,简单的说就是在原有的装饰器外面再套上一层带参数的函数~

还是这个函数,现在除了添加计时功能外,还需要添加debug功能,debug是否启用通过参数来实现~

def index():

time.sleep(random.randrange(1,5))

print('welcome to index page')

添加有参装饰器:

import time, random

import logging

logger = logging.getLogger()

logger.setLevel(logging.DEBUG)

def timmer(is_debug):

def decorator(func):

def wrapper(*args, **kwargs):

if is_debug:

begin = time.time()

res = func(*args, **kwargs)

logging.debug( "[" + func.__name__ + "] --> " + str(time.time() - begin) )

else:

res = func(*args, **kwargs)

return res

return wrapper

return decorator

@timmer(is_debug = True)

def index():

time.sleep(random.randrange(1, 5))

print('welcome to index page')

index()

index函数上方的 @timmer(is_debug = True) 相当于 index = timmer(is_debug=True)(index),开启debug后输出结果如下:

welcome to index page

DEBUG:root:[index] --> 3.000962018966675

@timmer(is_debug = False),关闭 debug 后,函数的输出与装饰前一致:

welcome to index page

.................^_^

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值