Python学习程序员程序猿阵线联盟-汇总各类技术干货

Python学习笔记二十三(闭包 / 装饰器 )

2018-05-26  本文已影响58人  DragonFangQy

函数

什么是函数? 将具有某种功能的代码放到一起, 构成一个函数.
为什么说函数? 因为需要研究一个问题, 函数可以嵌套调用, 那么可不可以嵌套定义?

函数的嵌套调用
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
装饰器运行 文字描述
特殊记忆方法

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

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

装饰过程:

调用过程:

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

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

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'} 

通过改变闭包内部函数可以给带参被装饰函数传参, 那么返回值怎么办?

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)函数的引用.
那么如果使用这么一个函数作为装饰器,会是什么效果?

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 被调用
# <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'}

运行成功,给类装饰器传参完成,有几点需要注意

类装饰器传参-方式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

到此结 DragonFangQy 2018.5.26


  1. 闭包

  2. 开闭原则

  3. __call__方法

上一篇下一篇

猜你喜欢

热点阅读