python闭包的应用场景_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:

# 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() # 调用函数

# 运行结果

#

# func1

这种形式叫闭包

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 =

# func1 被调用

# func3 被调用

装饰器的运行 debug

360ddf1fae91

01装饰器-debug.gif

装饰器运行 图解

360ddf1fae91

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 =

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), 程序执行完成

特殊记忆方法

根据上面的装饰器运行过程, 我们尝试推出一个方法, 便于记忆和解决装饰器的过程.

360ddf1fae91

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 =

# func4 被调用

# x type =

# func5 被调用

# func1 被调用

# func3 被调用

360ddf1fae91

04特殊记忆方法-使用.png

装饰过程:

func3 是一个函数的定义压栈

@func2 得到func3 函数的引用 压栈

func2 被调用 func1 压栈 ( func2 被调用 x type = )

@func4 的到func1 函数的引用 压栈

func4 被调用 func5 压栈 ( func4 被调用 x type = )

调用过程:

装饰后调用func3 ,func3 指向func5 ,func5被调用

func5 出栈, func5 被调用

x(),x 指向func1 func1 被调用

func1 被调用 func4 出栈 ( 装饰过程,执行过,出栈无效果, 只有func4出栈, func1 在栈顶次可以调用) ,func1 被调用

func1 出栈, func1 被调用

x(),x 指向func3 func3 被调用

通过上面的我们大概了解装饰过程,以及装饰后的调用过程, 那么如果是有参有返回值的函数怎么装饰呢?

首先我们知道了 闭包外部函数的形参存储着原函数( 被装饰函数) , 闭包内部调用了原函数( 被装饰函数),因为装饰器的调用是由解释器完成的,既@闭包外部函数 , 外部函数的参数个数我们无法改变, 那么只能改变闭包内部函数给 原函数( 被装饰函数)传参.

360ddf1fae91

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

上面的装饰器可以装饰以下四种函数

无参数,无返回值

无参数,有返回值

有参数,无返回值

有参数,有返回值

装饰器应用场景

装饰器可以在不改变原函数的基础上,添加新的功能 ( 符合开闭原则

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)函数的引用.

那么如果使用这么一个函数作为装饰器,会是什么效果?

360ddf1fae91

06使用三层函数作为装饰器.png

报错?缺少参数?添加参数,添加不定长参数,一步到位 :)

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 被调用

#

# 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 ,制作一个类装饰器.

360ddf1fae91

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"

# 运行结果

# (,)

# {}

@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

# func3()

# (,)

# TypeError: 'ClassMethod' object is not callable

# {}

报错? 'ClassMethod' object is not callable ,对象不可调用, 证明有一对象被创建, 而且对象需要有一个回调方法. 魔法方法__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

def func3(*args, **kwargs):

print("func3 被调用")

return "DragonFang"

func3()

# 运行结果

# __init__

# (,)

# {}

# __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

# ('Dragon', 'Fang')

# func3()

# {'Dragon': 18, 'Fang': 'nan'}

# TypeError: 'NoneType' object is not callable

# __call__

# (,)

# {}

call魔法方法被执行, 输出 (,) 和 {} ,然后

'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

# @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__

# (,)

# {}

# __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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值