title: "Python闭包与装饰器"
descriptime: "Python闭包与装饰器"
keywords: ["python","decorate"]
tags: ["Python"]
categories: ["Python"]
draft: false


闭包

定义:内部函数对外部函数作用域里变量的引用

  • 函数内的属性都是有生命周期的,只在函数运行期间存活 闭包内的闭包函数私有化了变量,完成了数据的封装。
  • 闭包指的是延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。函数是不是匿名没有关系,关键是它能访问定义体之外定义的非全局变量。
  • 在一个内部函数中,对外部作用域的变量进行引用,(并且一般外部函数的返回值为内部函数),那么内部函数就被认为是闭包
  • 闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。
def f1():
x = 'hello '
def f2():
print(x + 'world')
return f2
x = f1() # 此时x就相当于f2
x() #相当于 f2()

以上示例中,返回return f2()就叫做闭包
带参数的闭包

def f1():
x = 'hello'
def f2(y):
print(x+y)
return f2

f1()(' world')#f1() ==> f2

升级一下小示例,外函数也是参数的情况

some_list = [1,2,3]

def f1(obj):
def f2():
obj[0] += 1
print(obj)
return f2

jia = f1(some_list)
jia() #[2, 2, 3]
jia() #[3, 2, 3]

装饰器

是python的一种语法糖。一般用来在不更改原有函数的基础上,对原函数添加新的功能。

不含参数的装饰器

一般示例如下

@some_func
def func_example():
pass

其中,func_example表示被装饰函数,some_func表示装饰器。@some_func即相当于some_func(func_example)()

示例1:

def wen(func):
print('Who are you ?')
return func

@wen
def func1():
print("I'm Gourds")

以上示例虽然没有调用func1()不过却输出了Who are you ?说明: 装饰器在加载模块时立即执行,而被装饰的函数只在明确调用时运
不过显然这不是我想要的结果,我希望只有调用func1()的时候才问我,示例2就对了
示例2:

def wen(func):
def inner():
print('Who are you ?')
func()
return inner

@wen
def func1():
print("I'm Gourds")

func1()

实际,这个示例中func1()等同于wen(func1)()

示例3:给睡觉加个统计,看看睡了多久

import random
import time

def wait_how_long(func):
def inner():
ts = time.time()
func()
td = time.time()
print('Time is {:.2}s'.format(td-ts))
return inner

@wait_how_long
def sleep_a_moment():
print('sleeping...')
sj = random.randint(1,5)
time.sleep(sj)
print('awake')

sleep_a_moment()

带参数的被装饰函数

示例:给睡觉时间上限改成可调整的

import random
import time

def wait_how_long(func):
def inner(*args,**kwargs):
ts = time.time()
func(*args,**kwargs)
td = time.time()
print('Time is {:.2}s'.format(td-ts))
return inner

@wait_how_long
def sleep_a_moment(num):
print('sleeping...')
sj = random.randint(1,num)
time.sleep(sj)
print('awake')

sleep_a_moment(5)

示例:牵涉返回值的情况,加上

import random
import time

def wait_how_long(func):
def inner(*args,**kwargs):
ts = time.time()
rst = func(*args,**kwargs) #需要将被装饰返回值存起来
td = time.time()
print('Time is {:.2}s'.format(td-ts))
return rst #结束时返回
return inner

@wait_how_long
def sleep_a_moment(num):
print('sleeping...')
sj = random.randint(1,num)
time.sleep(sj)
return 'music' #当然,前提是被装饰函数有返回值

x = sleep_a_moment(5)
print(x)

示例:多个装饰器的执行情况

def test1(func):
def wrapper(*args, **kwargs):
print('before test1 ...')
func(*args, **kwargs)
print('after test1 ...')
return wrapper #返回内层函数的引用

def test2(func):
def wrapper(*args, **kwargs):
print('before test2 ...')
func(*args, **kwargs)
print('after test2 ...')
return wrapper #返回内层函数的引用

@test2
@test1
def add(a, b):
print(a+b)

print(add)
print(add.__name__)
x = add(1, 2)

#输出
# <function test2.<locals>.wrapper at 0x7fbf3fcbe790>
# wrapper
# before test2 ...
# before test1 ...
# 3
# after test1 ...
# after test2 ...

带参数的装饰器

注意: 如果被装饰函数有参数的话,只需要在装饰器函数的最内部函数传入参数即可。这个是python的设计,不这样的话,需要多一层函数。带参数的装饰器会执行两个括号some_func(some_arg)J()()

示例:

#实际上就是正常的装饰器,外面再加一层带参数的函数
def z3(msg):
def z1(func):
print('this is z1')
def z2():
print('say: {}'.format(msg))
print('this is z2 befor')
func()
print('this z2 after')
return z2
return z1
@z3('hello')
def f1():
print('this is f1')

print(f1)
print(f1.__name__)
f1()
#输出
# this is z1
# <function z3.<locals>.z1.<locals>.z2 at 0x7ffa09eb6700>
# z2
# say: hello
# this is z2 befor
# this is f1
# this z2 after

注意: 如上的这些装饰器都有一个问题,就是存在函数名问题。可以通过functool.wraps来进行一下修复

特殊工具functoool.wraps

如下,直接在闭包函数上加上@functools.wraps(func)这个特殊的装饰器即可

import functools

def z3(msg):
def z1(func):
print('this is z1')
@functools.wraps(func) #就是这
def z2():
print('say: {}'.format(msg))
print('this is z2 befor')
func()
print('this z2 after')
return z2
return z1
@z3('hello')
def f1():
print('this is f1')

print(f1)
print(f1.__name__)
f1()

#输出
# this is z1
# <function f1 at 0x7fc71ed2da60>
# f1
# say: hello
# this is z2 befor
# this is f1
# this z2 after