Python基础篇:闭包、装饰器wrapper
·
一、闭包
元组字典解包
def func(*args, **kwargs):
print(type(args)) # <class 'tuple'>
print(type(kwargs)) # <class 'dict'>
# *args: tuple解包成位置参数
# **kwargs: dict解包成关键字参数
print(*args, **kwargs) # 1-2-3.
func(1, 2, 3, sep="-", end=".")
嵌套函数
函数定义可以嵌套函数,这在其它语言中也经常见到,但是可以嵌套多层函数还是很少见到。
def function():
print("function")
def func():
print("func")
def fun():
print("fun")
return fun
return func
function()()()
闭包
闭包的构成条件:有嵌套+有引用+有返回:
- 有嵌套:在函数嵌套(函数里面再定义函数)的前提下;
- 有引用:内部函数使用了外部函数的变量(还包括外部函数的参数);
- 有返回:外部函数返回了内部函数名。
闭包是用来延长函数内变量的声明周期。闭包可以保存函数内的变量,而不会随着调用完函数而被销毁。
在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,这种使用外部函数变量的内部函数称为闭包。
#外部函数
def 外部函数名(外部参数):
#内部函数
def 内部函数名(内部参数):
...[使用外部函数的变量]
return 内部函数名 #闭包
def func_outer(num1):
def func_inner(num2): #1 在函数嵌套(函数里面再定义函数)的前提下
sum =num1+ num2 #2 内部函数使用了外部函数的变量(还包括外部函数的参数)
print("现在的值:",sum)
return func_inner #3 外部函数返回了内部函数
f=func_outer(10) #执行闭包
f(1)
nonlocal:声明能够让内部函数去修改外部函数的变量
def func_outer():
a = 100
def func_inner():
# 注意要声明
nonlocal a
a = a + 1
print(f"修改的结果:{a}")
return func_inner
inner = func_outer()
# 修改的结果:101
inner()
# 修改的结果:102
inner()
二、装饰器
装饰器的作用是不改变原有函数的基础上,给原有函数增加额外功能。
装饰器本质上就是一个闭包函数
装饰器的构成条件:
- 有嵌套:在函数嵌套(函数里面再定义函数)的前提下;
- 有引用:内部函数使用了外部函数的变量(还包括外部函数的参数);
- 有返回:外部函数返回了内部函数名。
- 有额外功能:给需要装饰的原有函数增加额外功能。
装饰器是一个特殊的函数或者类,用于包装目标函数,可以在执行目标函数前或者执行目标函数后执行装饰器函数。装饰器的底层就是闭包。 和Java中的面向切面编程AOP作用类似,在使用上和Java中的注解类似以@开头。在目标函数定义的上方通过@符号+装饰器函数名来使用装饰器。
装饰器是一个特殊的函数:
- 装饰器函数接收一个函数类型参数,这个函数类型参数就是我们要执行的目标函数对象;
- 装饰器函数的返回值是一个
包装函数,包装函数就是一个普通的函数,接收两个参数,一个是可变长度参数*args,另一个是命名关键字参数**kwargs,包装器的函数的名字一般叫wrapper,如果不是wrapper最好通过@functools.wraps(func)来注解一下(最新版本的没有此限制)。包装函数内部调用目标函数,此时可以在调用目标函数前或者调用目标函数后加一些代码,这样就达到在目标函数前后执行一些代码。
3.1 函数实现
3.1.1 闭包实现
本质就是函数的参数类型是函数类型function
def outer(func):
def inner():
print('我要开始睡觉了')
func()
print('我睡醒了')
return inner
def sleep():
print('睡觉中zZZZ')
# 先获取内部函数返回值
innter = outer(sleep)
"""
我要开始睡觉了
睡觉中zZZZ
我睡醒了
"""
# 再调用内部函数
innter()
内部函数 = 外部函数(原有函数)
内部函数()
3.1.2 基础装饰器(目标函数无参无返回值)
def outer(func):
def inner():
print('我要开始睡觉了')
func()
print('我睡醒了')
return inner
@outer
def sleep():
print('睡觉中zZZZ')
"""
我要开始睡觉了
睡觉中zZZZ
我睡醒了
"""
# 一步到位:就像调普通函数一样,只需调用一次
sleep()
装饰器和闭包的唯一区别就是在调用方式上,闭包是调用的外部函数,而装饰器是直接调用目标函数的。
先执行目标函数上的装饰器,再执行目标函数。
3.1.3 打印函数耗时(目标函数有参无返回值)
import datetime
import time
def printtime(func):
"""方法耗时"""
def wrapper(*args, **kwargs):
print("开始计算耗时")
begin = datetime.datetime.now().timestamp()
func(*args, **kwargs)
end = datetime.datetime.now().timestamp()
print("结束计算耗时")
print(f'结束计算耗时:方法耗时{end - begin}秒')
return wrapper
@printtime
def sleep(second):
time.sleep(second)
print(f'sleep {second}s ...')
"""
开始计算耗时
sleep 2s ...
结束计算耗时
结束计算耗时:方法耗时2.0054750442504883秒
"""
sleep(2)
3.1.4 有参实现
装饰器传参: 需要在装饰器函数中嵌套两层函数,包装器函数最外侧接收包装器参数,函数名一般命名为decorator,
- 最外层函数:接收装饰器的参数
- 中间层函数(装饰器函数):接收目标函数参数
func(装饰器函数只能最多有一个func参数) - 最内层函数:接收目标函数对应的参数为位置参数和关键字参数
(*args, **kwargs)
import datetime
import time
def printtime(echo):
def decorator(func):
def wrapper(*args, **kwargs):
if echo:
print("开始计算耗时")
begin = datetime.datetime.now().timestamp()
func(*args, **kwargs)
end = datetime.datetime.now().timestamp()
if echo:
print("结束计算耗时")
print(f'结束计算耗时:方法耗时{end - begin}秒')
return wrapper
return decorator
@printtime(echo=False)
def sleep(second):
time.sleep(second)
print(f'sleep {second}s ...')
"""
sleep 2s ...
结束计算耗时:方法耗时2.003556966781616秒
"""
sleep(2)
3.1.4 同时使用多个装饰器
多个装饰器的装饰过程是:离函数最近的装饰器先装饰,然后外面的装饰器再进行装饰,由内到外的装饰过程。
但从语法糖表面现象看是装饰器从上往下执行。
def before(func):
def wrapper():
print('我要开始睡觉了')
func()
return wrapper
def after(func):
def wrapper():
func()
print('我睡醒了')
return wrapper
闭包方式调用。
after_inner = after(sleep)
before_inner = before(after_inner)
"""
我要开始睡觉了
睡觉中zZZZ
我睡醒了
"""
before_inner()
语法糖方式调用。
@before
@after
def sleep():
print('睡觉中zZZZ')
"""
我要开始睡觉了
睡觉中zZZZ
我睡醒了
"""
sleep()
3.2 类实现
装饰器可以用函数实现,也可以用类实现,
__init__构造器接收目标函数对象,__call__函数来调用目标函数。
3.2.1 无参实现
import datetime
import time
class Printtime:
def __init__(self, func):
self._func = func
def __call__(self, *args, **kwargs):
# '__':表示私有方法
begin = datetime.datetime.now().timestamp()
self._func()
end = datetime.datetime.now().timestamp()
print(f'方法{self._func.__name__}耗时{end - begin}秒')
@Printtime
def test():
time.sleep(2)
print('test wrapper: printtime')
test()
3.2.2 有参实现
__init__构造器接收装饰器参数(如msg)__call__函数来接收目标函数,并调用装饰函数。__callback装饰函数。
import datetime
import time
class Printtime:
def __init__(self, msg):
self.msg = msg
def __call__(self, func):
# _func的名字是固定的
self._func = func
return self.__callback
def __callback(self, *args, **kwargs):
# '__':表示私有方法
begin = datetime.datetime.now().timestamp()
self._func()
end = datetime.datetime.now().timestamp()
print(f'{self.msg}:方法{self._func.__name__}耗时{end - begin}秒')
@Printtime(msg='参数')
def test():
time.sleep(2)
print('test wrapper: printtime')
test()
3.3 Python常用的装饰器
@unique:用于检查枚举中的值是否重复,重复运行的时候会报错。@property:用于getter方法,调用该方法就像获取属性一样。通过对象.方法名一样来调用方法。@方法名.setter: 用于将setter方法包装成可以通过像属性赋值一样使用,使用给属性赋值的语法来调用setter方法。@staticmethod:静态方法调用,通过类名.方法名来调用,也支持通过对象名.方法名来调用。使用@staticmethod的方法的第一个参数不需要self。@functools.wraps(func):函数对象有一个__name__属性,可以拿到函数的名字。当使用包装器当将一个函数赋值给一个变量时,通过该变量获取函数的名字__name__属性时是获取到包装器函数的名字(一般习惯写成wrapper), 此时是不对的,解决办法可以在包装器内修改函数的__name__属性wrapper.__name__ = func.__name__,或者使用系统自带的装饰器@functools.wraps(func)来修改函数名。
from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
day1 = Weekday.Sat
print(day1.value)
class Student:
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100')
self._age = value
class Foobar:
@staticmethod
def foo():
print("foo")
# 通过类名.方法名调用
Foobar.foo()
# 也支持对象.方法名调用
foobar = Foobar()
foobar.foo()
class Outer:
@staticmethod
class Inner:
def test(self):
print("test")
Outer.Inner().test()
更多推荐


所有评论(0)