Python学习笔记二十三(闭包 / 装饰器 )
函数
什么是函数? 将具有某种功能的代码放到一起, 构成一个函数.
为什么说函数? 因为需要研究一个问题, 函数可以嵌套调用, 那么可不可以嵌套定义?
函数的嵌套调用
def func1():
print("func1")
def func2():
# 嵌套调用
func1()
func1()
func2()
# 运行结果
# func1
# func1
函数能使用函数名调用, 那么函数名是什么? 标识符, 标识符里包含变量 / 函数名, 那么函数能不能通过变量传递 / 调用吗?
def func1():
print("func1")
def func2():
# 函数通过变量传递
f2 = func1
print("f2 type:%s" % type(f2))
# 通过变量调用函数
f2()
func1()
func2()
# 运行结果
# func1
# f2 type:<class 'function'>
# func1
函数可以通过变量传递 / 调用,那么回到最初的问题, 函数能嵌套调用, 函数能嵌套定义吗?
闭包
def func2():
# 定义函数func1
def func1():
print("func1")
func2()
# 运行结果
通过运行结果可以得出, 可以嵌套定义 ( 没报错:) ) , 那么为什么没有结果?
函数能够传递,函数在定义的时候不会执行, 那么可以明确一点func2 被调用,所以func2 被执行了.
func2 被执行 ,那么func1 定义在func2 函数体内, 所以func1 被定义了, 但是func1 没有被调用.那么能不能通过函数传递调用func1?
def func2():
# 定义函数func1
def func1():
print("func1")
return func1
f = func2() # 得到func1 的引用
print(type(f)) # 查看f 的类型
f() # 调用函数
# 运行结果
# <class 'function'>
# func1
这种形式叫闭包[1], 在函数内部定义另一个函数, 并且外部函数func2 返回内部函数func1 的引用.
虽然这是一个闭包, 但是没人会这么用, 用两个函数去做一个函数可以完成的事, 而且还这么麻烦.
相信大家都画过数学中的函数图像, 画图像需要确定坐标, 今天就用程序确定 x = 某值时 , y的值 ( y= x的平方 )
def func2(x):
# 内部函数func1 要使用一个X 的值, 需要从外部传入
# 为什么要从外部传入?
def func1():
print(x ** 2)
return func1
f = func2(2) # 得到func1 的引用
f() # 调用函数
# 运行结果
# 4
一定要从外部传入吗?不一定,就上面这个例子只是简单演示闭包的用法, 而且上面的例子用一个函数也可以完成, 那么闭包大多数的应该用在哪? 装饰器或者叫语法糖.
函数装饰器
只要你写过类方法 / 静态方法, 那么你就用过装饰器.下面来看看装饰器
print("start") # 开始Debug
# 函数定义 会创建 不会执行
def func2(x):
print("func2 被调用")
print("x type = %s" % type(x))
def func1():
print("func1 被调用")
x()
return func1
@func2 # 程序暂停,执行 func3 = func2(func3)
def func3():
print("func3 被调用")
func3() # 调用函数
# 运行结果
# start
# func2 被调用
# x type = <class 'function'>
# func1 被调用
# func3 被调用
装饰器的运行 debug
01装饰器-debug.gif装饰器运行 图解
02装饰器-图解.png装饰器运行 文字描述
- 程序执行到 line:5, func2 被定义 ,
- 程序执行到 line:16是一个装饰器,不执行,
- 运行(检测) line:17 定义func3, 同时检测到是一个函数,返回 line:16 执行func2
- 如果line:17 不是函数,继续向下执行检测,直到遇到函数, 再次返回
- func2 被执行 ( 因为@func2 ==> func3 = func2(func3) ),
- x = 原func3 ,既 x 指向原func3 的函数体;
- print("func2 被调用") # func2 被调用
print("x type = %s" % type(x)) # x type = <class 'function'> - func1 被定义
- return func1, 既 func3 = func2(func3) = func1,装饰完成func3 被指向func1
- 程序执行到 line:21,因为func3 在上面被装饰过,所以现在的func3( 装饰完的 ) 指向func1
- func3() ==> func1(), 既 line:10 被执行, print("func1 被调用")
- line:11 因为x = 原func3 ,所以 x() 原func3 的方法体被执行, print("func3 被调用")
- 函数调用完成, 返回被调用处 ,所以 print("func3 被调用") ( line:18) 完成后 返回 x() 处(line:11) ,
- x() 执行完(line:11) , 返回func3() 处 (line:21), 程序执行完成
特殊记忆方法
根据上面的装饰器运行过程, 我们尝试推出一个方法, 便于记忆和解决装饰器的过程.
03特殊记忆方法.png使用特殊记忆方法
print("start") # 开始Debug
# 函数定义 不会执行
def func2(x):
print("func2 被调用")
print("x type = %s" % type(x))
def func1():
print("func1 被调用")
x()
return func1
def func4(x):
print("func4 被调用")
print("x type = %s" % type(x))
def func5():
print("func5 被调用")
x()
return func5
@func4
@func2
def func3():
print("func3 被调用")
func3() # 调用函数
# 运行结果
# start
# func2 被调用
# x type = <class 'function'>
# func4 被调用
# x type = <class 'function'>
# func5 被调用
# func1 被调用
# func3 被调用
04特殊记忆方法-使用.png
装饰过程:
- func3 是一个函数的定义压栈
- @func2 得到func3 函数的引用 压栈
- func2 被调用 func1 压栈 ( func2 被调用 x type = <class 'function'>)
- @func4 的到func1 函数的引用 压栈
- func4 被调用 func5 压栈 ( func4 被调用 x type = <class 'function'>)
调用过程:
- 装饰后调用func3 ,func3 指向func5 ,func5被调用
- func5 出栈, func5 被调用
- x(),x 指向func1 func1 被调用
- func1 被调用 func4 出栈 ( 装饰过程,执行过,出栈无效果, 只有func4出栈, func1 在栈顶次可以调用) ,func1 被调用
- func1 出栈, func1 被调用
- x(),x 指向func3 func3 被调用
通过上面的我们大概了解装饰过程,以及装饰后的调用过程, 那么如果是有参有返回值的函数怎么装饰呢?
首先我们知道了 闭包外部函数的形参存储着原函数( 被装饰函数) , 闭包内部调用了原函数( 被装饰函数),因为装饰器的调用是由解释器完成的,既@闭包外部函数 , 外部函数的参数个数我们无法改变, 那么只能改变闭包内部函数给 原函数( 被装饰函数)传参.
05传参.png如图, func3 需要有参数,@func2 的参数个数无法改变, 我们尝试改变func1 的参数,同时x 最终指向原函数,所以x 必须和func3 的参数一致.
print("start") # 开始Debug
# 函数定义 不会执行
def func2(x):
def func1(*args, **kwargs):
print("func1 被调用")
print("args %s " % args.__str__())
print("kwargs %s " % kwargs.__str__())
x(*args, **kwargs)
return func1
@func2
def func3(*args, **kwargs):
print("func3 被调用")
print("args %s " % args.__str__())
print("kwargs %s " % kwargs.__str__())
func3("Dragon", "Fang", Dragon=18, Fang="nan") # 调用函数
# 运行结果
# start
# func1 被调用
# args ('Dragon', 'Fang')
# kwargs {'Dragon': 18, 'Fang': 'nan'}
# func3 被调用
# args ('Dragon', 'Fang')
# kwargs {'Dragon': 18, 'Fang': 'nan'}
通过改变闭包内部函数可以给带参被装饰函数传参, 那么返回值怎么办?
- 调用函数都会回到被调用处,装饰后func3() 调用,既func1 被调用
- func1 执行, x() 被调用==>原func3 被调用, 原func3执行完成有返回值,返回到 x() 处,
- x() 得到值, 如果不将值返回, 那么func1 执行完默认返回None,既 装饰后func3() 处的到None 值,所以 x() 处需要将得到的值返回.
print("start") # 开始Debug
# 函数定义 不会执行
def func2(x):
def func1(*args, **kwargs):
print("func1 被调用")
return x(*args, **kwargs)
return func1
@func2
def func3(*args, **kwargs):
print("func3 被调用")
return "DragonFang"
print(func3("Dragon", "Fang", Dragon=18, Fang="nan"))
# 运行结果
# start
# func1 被调用
# func3 被调用
# DragonFang
上面的装饰器可以装饰以下四种函数
- 无参数,无返回值
- 无参数,有返回值
- 有参数,无返回值
- 有参数,有返回值
装饰器应用场景
装饰器可以在不改变原函数的基础上,添加新的功能 ( 符合开闭原则[2]), 例如计算函数运行耗时, 在优化时常用.
import time
def func(func_args):
def in_func(*args, **kwargs):
# 在函数开始运行前,得到当前时间
start_time = time.time()
result_value = func_args(*args, **kwargs)
# 在函数运行结束后,获取当前时间,作差 ==> 得到函数的运行时间
print(time.time() - start_time)
return result_value
return in_func
@func
def test():
time.sleep(2)
test()
# 运行结果
# 2.00081205368042
函数装饰器-给装饰器传参
有个问题,函数内部嵌套函数的定义是两层, 如果是三层呢?或者更多层呢?先看看三层,三层往上不讨论,层级太深不是一件好事.
def out_test():
def test(x):
def in_test(*args, **kwargs):
print("func1 被调用")
return x(*args, **kwargs)
return in_test
return test
如上, 三层函数, 首先这是一个函数, 可用通过out_test() 得到test(x) 函数的引用, 然后可以通过test(x) 的到in_test (*args, **kwargs)函数的引用.
那么如果使用这么一个函数作为装饰器,会是什么效果?
报错?缺少参数?添加参数,添加不定长参数,一步到位 :)
print("start") # 开始Debug
def out_test(*args, **kwargs):
print("out_test 被调用")
def test(x):
print("test 被调用")
def in_test(*args, **kwargs):
print("in_test 被调用")
return x(*args, **kwargs)
return in_test
return test
@out_test("Dragon", "Fang", Dragon=18, Fang="nan")
def func3(*args, **kwargs):
print("func3 被调用")
return "DragonFang"
print(func3("Dragon", "Fang", Dragon=18, Fang="nan"))
# 运行结果
# start
# out_test 被调用
# test 被调用
# in_test 被调用
# func3 被调用
# DragonFang
最外层被调用,然后内层被调用, out_test 被调用, 然后 test 被调用, 最后 in_test 被调用, 那么他们分别作了什么事?
def out_test(*args, **kwargs):
print("out_test 被调用")
print(args)
print(kwargs)
def test(x):
print("test 被调用")
print(x)
def in_test(*args, **kwargs):
print("in_test 被调用")
print(args)
print(kwargs)
return x(*args, **kwargs)
return in_test
return test
@out_test("test", Dragon=20)
def func3(*args, **kwargs):
print("func3 被调用")
return "DragonFang"
print(func3("Dragon", "Fang", Dragon=18, Fang="nan"))
# 运行结果
# out_test 被调用
# ('test',)
# {'Dragon': 20}
# test 被调用
# <function func3 at 0x000001BAA0C48A60>
# in_test 被调用
# ('Dragon', 'Fang')
# {'Dragon': 18, 'Fang': 'nan'}
# func3 被调用
# DragonFang
out_test 被调用打印了值, 然后test 被调用 , func3 被打印, 因此我们可以推测出@out_test("test", Dragon=20) ==> out_test("test", Dragon=20) 然后 使用 out_test 的返回值对func3 进行装饰
那么可以通过三层函数的形式给装饰器传参.
类装饰器
@classmethod / @staticmethod 都是类装饰器
# classmethod 源码
class classmethod(object):
"""
classmethod(function) -> method
Convert a function to be a class method.
A class method receives the class as implicit first argument,
just like an instance method receives the instance.
To declare a class method, use this idiom:
class C:
@classmethod
def f(cls, arg1, arg2, ...):
...
It can be called either on the class (e.g. C.f()) or on an instance
(e.g. C().f()). The instance is ignored except for its class.
If a class method is called for a derived class, the derived class
object is passed as the implied first argument.
Class methods are different than C++ or Java static methods.
If you want those, see the staticmethod builtin.
"""
def __get__(self, *args, **kwargs): # real signature unknown
""" Return an attribute of instance, which is of type owner. """
pass
def __init__(self, function): # real signature unknown; restored from __doc__
pass
@staticmethod # known case of __new__
def __new__(*args, **kwargs): # real signature unknown
""" Create and return a new object. See help(type) for accurate signature. """
pass
__func__ = property(lambda self: object(), lambda self, v: None, lambda self: None) # default
__isabstractmethod__ = property(lambda self: object(), lambda self, v: None, lambda self: None) # default
__dict__ = None # (!) real value is ''
仿照@classmethod ,制作一个类装饰器.
07类装饰器-必须有init魔法方法.png报错?init魔法方法缺少参数?给参数
class ClassMethod(object):
def __init__(self, *args, **kwargs):
print(args)
print(kwargs)
@ClassMethod
def func3(*args, **kwargs):
print("func3 被调用")
return "DragonFang"
# 运行结果
# (<function func3 at 0x000001CC350C2E18>,)
# {}
@ClassMethod 调用了init 魔法方法? 那么应该有一个ClassMethod 类的对象被创建,
既@ClassMethod ==> ClassMethod() 得到对象, 使用对象装饰func3.
验证,调用装饰后的func3
class ClassMethod(object):
def __init__(self, *args, **kwargs):
print(args)
print(kwargs)
@ClassMethod
def func3(*args, **kwargs):
print("func3 被调用")
return "DragonFang"
func3()
# 运行结果
# Traceback (most recent call last):
# File "E:/workspace/pycharm/pycharm/model.py", line 12, in <module>
# func3()
# (<function func3 at 0x00000276789F2E18>,)
# TypeError: 'ClassMethod' object is not callable
# {}
报错? 'ClassMethod' object is not callable ,对象不可调用, 证明有一对象被创建, 而且对象需要有一个回调方法. 魔法方法__call__ [3] 可以使一个实例对象变为一个可调用对象.
class ClassMethod(object):
def __init__(self, *args, **kwargs):
print("__init__")
print(args)
print(kwargs)
def __call__(self, *args, **kwargs):
print("__call__")
print(args)
print(kwargs)
@ClassMethod
def func3(*args, **kwargs):
print("func3 被调用")
return "DragonFang"
func3()
# 运行结果
# __init__
# (<function func3 at 0x00000204124F2E18>,)
# {}
# __call__
# ()
# {}
成功运行,证明我们的类装饰器没问题, 那么函数装饰器能传参, 类装饰器怎么传参? call 魔法方法有两个空值, 是不是可以利用一下?
class ClassMethod(object):
def __init__(self, *args, **kwargs):
print("__init__")
print(args)
print(kwargs)
def __call__(self, *args, **kwargs):
print("__call__")
print(args)
print(kwargs)
@ClassMethod("Dragon", "Fang", Dragon=18, Fang="nan")
def func3(*args, **kwargs):
print("func3 被调用")
return "DragonFang"
func3()
# 运行结果
# Traceback (most recent call last):
# __init__
# File "E:/workspace/pycharm/pycharm/model.py", line 19, in <module>
# ('Dragon', 'Fang')
# func3()
# {'Dragon': 18, 'Fang': 'nan'}
# TypeError: 'NoneType' object is not callable
# __call__
# (<function func3 at 0x000001FCCACB2E18>,)
# {}
call魔法方法被执行, 输出 (<function func3 at 0x000001FCCACB2E18>,) 和 {} ,然后
'NoneType' object is not callable ,空对象不能调用? 我们知道函数 或者方法 默认是有默认返回值的, 默认值是None ,也就是说 call 魔法方法返回了一个None 给调用处,既@ClassMethod("Dragon", "Fang", Dragon=18, Fang="nan") 处, 使用 @None 装饰func3 是不对的, 那么我们返回一个函数, 既使用函数装饰器装饰func3
class ClassMethod(object):
def __init__(self, *args, **kwargs):
print("__init__")
self.args = args
self.kwargs = kwargs
def __call__(self, func):
print("__call__")
self.func = func
return self.test
def test(self):
print("test")
self.func(*self.args, **self.kwargs)
@ClassMethod("Dragon", "Fang", Dragon=18, Fang="nan")
def func3(*args, **kwargs):
print("func3 被调用")
print(args)
print(kwargs)
func3()
# 运行结果
# __init__
# __call__
# test
# func3 被调用
# ('Dragon', 'Fang')
# {'Dragon': 18, 'Fang': 'nan'}
运行成功,给类装饰器传参完成,有几点需要注意
- 参数,参数是传给init 魔法方法的,需要使用属性接收
- call 魔法方法返回的是一个函数的引用
- test方法中,self.func 是原函数, 通过属性传不定长参数需要拆包
类装饰器传参-方式2
上面是在初始化时传参,那能不能将通过方法传参?
class ClassMethod(object):
def __init__(self, *args, **kwargs):
print("__init__")
self.args = args
self.kwargs = kwargs
def __call__(self, func):
print("__call__")
self.func = func
return self.test
@classmethod
def test(cls, *args, **kwargs):
cls.args = args
cls.kwargs = kwargs
@ClassMethod.test("Dragon", "Fang", Dragon=18, Fang="nan")
def func3(*args, **kwargs):
print("func3 被调用")
print(args)
print(kwargs)
func3()
# 运行结果
# Traceback (most recent call last):
# test
# File "E:/workspace/pycharm/pycharm/model.py", line 18, in <module>
# @ClassMethod.test("Dragon", "Fang", Dragon=18, Fang="nan")
# TypeError: 'NoneType' object is not callable
test 被打印,说明test 类方法被调用, 'NoneType' object is not callable 又是这错误! 给test类方法一个返回值
class ClassMethod(object):
def __init__(self, *args, **kwargs):
print("__init__")
print(args)
print(kwargs)
def __call__(self):
print("__call__")
@classmethod
def test(cls, *args, **kwargs):
cls.args = args
cls.kwargs = kwargs
return cls
@ClassMethod.test("Dragon", "Fang", Dragon=18, Fang="nan")
def func3(*args, **kwargs):
print("func3 被调用")
print(args)
print(kwargs)
func3()
# 运行结果
# __init__
# (<function func3 at 0x0000023DD1962E18>,)
# {}
# __call__
通过以上实验可以推断@ClassMethod.test("Dragon", "Fang", Dragon=18, Fang="nan")
==> 先调用test 类方法, 然后 调用 init魔法方法初始化对象, 最后通过实例对象装饰func3
那么按照这个顺序完善一下ClassMethod 类
class ClassMethod(object):
def __init__(self, func):
print("__init__")
self.func = func
def __call__(self):
return self.func(*ClassMethod.args, **ClassMethod.kwargs)
@classmethod
def test(cls, *args, **kwargs):
cls.args = args
cls.kwargs = kwargs
return cls
@ClassMethod.test("Dragon", "Fang", Dragon=18, Fang="nan")
def func3(*args, **kwargs):
print("func3 被调用")
print(args)
print(kwargs)
func3()
# 运行结果
# __init__
# func3 被调用
# ('Dragon', 'Fang')
# {'Dragon': 18, 'Fang': 'nan'}
perfect 完美
总结:
- 闭包:函数内部嵌套函数的定义,构成闭包
- 闭包的作用:大部分用在装饰器
- 装饰器:@函数名 / @类名 的形式叫做装饰器或者语法糖
- 装饰器的作用:在不改变原函数的基础上添加功能
- 装饰前的函数原函数,被闭包外部函数的形参保存
- 装饰后的函数,实际上是闭包的内部函数,
- 通用装饰器格式:
# 不需要给装饰器传参
def func(func_args):
def in_func(*args, **kwargs):
print("添加功能")
return func_args(*args, **kwargs)
return in_func
# 需要给装饰器传参
def out_func(*out_args, **out_kwargs):
def func(func_args):
def in_func(*args, **kwargs):
print("添加功能")
return func_args(*args, **kwargs)
return in_func
return func
- 装饰器特殊记忆方式,出栈入栈,入栈: 装饰过程,出栈:调用过程
- 类装饰器,类需要包含 init 魔法方法 和 call 魔法方法 构成基本的类装饰器
- 类装饰器传参,除了init 魔法方法 和 call 魔法方法 之外需要定义另外的方法
- @ClassMethod("Dragon", "Fang", Dragon=18, Fang="nan"), 这种传参方式需要定义另外的方法, 辅助调用原函数
- @ClassMethod.test("Dragon", "Fang", Dragon=18, Fang="nan"), 这种传参方式需要定义另外的方式, 辅助保存参数
到此结 DragonFangQy 2018.5.26