Python函数 - 闭包、装饰器

【闭包】

  在函数嵌套的前提下,内层函数引用了外层函数的变量(包括参数),外层函数又把内层函数当做返回值进行返回。这个“内层函数+所引用的外层变量”,称为闭包。
  从这段话中,可以解析出构成闭包的三个条件:
    1、形成函数嵌套;
    2、内层函数引用外层函数的变量(包括参数);
    3、外层函数的返回值是内层函数。


  一个最简单的实例:

def test():  #外层函数
    a = 1  #外层函数的变量

    def test2():  #内层函数
        print(a)  #内层函数引用外层函数的变量

    return test2  #外层函数的返回值是内层函数

  则在调用外层函数 test 的时候,它的返回值是一个函数 test2,需要用一个变量“func”接收一下,即 func = test() ,此时,func接收到的就是test的返回值---test2,那么,将func 执行,就会间接调用到test2函数,将test里面的变量 a的值打印出来。程序和运行结果如下:

def test():
    a = 1

    def test2():
        print(a)

    return test2


func = test()
func()

  运行结果:

1

Process finished with exit code 0


【装饰器】

  作用:在函数名以及函数体不改变的前提下,给一个函数附加一些额外的代码

  比如现有如下需求:有一个函数 print1:它的作用是打印“123”,想要在不改变函数体的前提下,给它增加一个功能:在打印“123”之前打印一条线。

def print1():
    print("123")

  打印线的函数为 printLine

def printLine():
    print("-" * 20)

  现在,只能想办法在printLine函数里面执行print1,如下(此处,printLine完成如下功能:printLine接收一个参数func,先打印一条线,再把接收过来的函数执行一遍,此处没有直接在printLine里面直接执行print1,那样就写死了,如果后面还有需求:在别的函数前面也打印一条线,写死就会降低代码的复用性)

def printLine(func):
        print("-" * 20)
        func()

  现在这样写:

printLine(print1)

先写个printLine,我们把print1当做参数传递到printLine里面,去调用这个函数。但是这样做有个问题,就是在这里,由于printLine函数的作用,在调用printLine函数的时候,已经执行了print1。而print1应该在业务逻辑代码里面执行的。


  这时,需要借助一个之前的工具-闭包,把print1变成拥有printLine函数体的一个函数就行,而不要直接去执行printLine。也就是起到这样的效果:“print1 = printLine(print1)”因此:

def printLine(func):
    def inner():
        print("-" * 20)
        func()
    return inner

def print1():
    print("123")

print1 = printLine(print1)

  在printLine函数里面,重新定义了一个函数inner,inner有这样的功能:先打印一条线,再把传进printLine来的函数func执行一遍。最后,printLine把inner当做返回值返回,这样,print1就能接收到返回来的inner。print1指向了inner,而这个inner是printLine生成的,printLine做的事情就是,把inner这个闭包返回给了外界。

  此时再执行函数print1:

# 业务逻辑代码
print1()

得到如下结果:

--------------------
123

Process finished with exit code 0

  这里,就借助了闭包这样的特性,针对于函数print1,给它额外地装饰了一些功能,并且并没有改变print1函数里面的内容。而完成这一创举的,就是函数printLine,我们把printLine称作是一个“装饰器”。这样的操作,称作是“装饰器”这样的设计模式。


  现在,如果还有其它函数有这样的装饰需求,比如在这两个打印内容前面打印一条线:

def print2():
    print("abc")
    

def print3():
    print("我爱中国")

  有了这样一个装饰器,我们只需要像上面的一样,写成这样:

def printLine(func):
    def inner():
        print("-" * 20)
        func()
    return inner


def print1():
    print("123")

print1 = printLine(print1)


def print2():
    print("abc")

print2 = printLine(print2)


def print3():
    print("我爱中国")

print3 = printLine(print3)

# 业务逻辑代码
print1()
print2()
print3()

  就可以实现:

--------------------
123
--------------------
abc
--------------------
我爱中国

Process finished with exit code 0

  但是,这样写起来,不是那么好看。Python给我们提供了“语法糖”这样一种写法:(一种更加方便的方式)

def printLine(func):
    def inner():
        print("-" * 20)
        func()

    return inner


@printLine
def print1():
    print("123")


# print1 = printLine(print1)

@printLine
def print2():
    print("abc")


# print2 = printLine(print2)

@printLine
def print3():
    print("我爱中国")


# print3 = printLine(print3)

# 业务逻辑代码
print1()
print2()
print3()

  如上所示,不再需要以前的
    print1 = printLine(print1)
    print2 = printLine(print2)
    print3 = printLine(print3)
  只需要在各个函数上面装饰一句“@printLine”。这样一来,整个代码的颜值、格调就都上来啦!


【装饰器的几个注意事项】

  1、装饰器的执行,是立即执行的,在装饰的时候就执行,而并不是等到调用被装饰的函数时才执行。也就是说,下面代码中,当执行到@printLine时,装饰器已经执行,而不是在业务逻辑代码中调用print3函数时才执行;

@printLine
def print3():
    print("我爱中国")

# 业务逻辑代码
print3()

  2、如果是多个装饰器叠加装饰一个对象,是“从上到下装饰,从外层到内层装饰”,而执行的时候,是从下到上,从内层到外层执行;

def printLine(func):
    def inner():
        print("-" * 20)
        func()

    return inner


def printStar(func):
    def inner():
        print("*" * 20)
        func()

    return inner


@printLine
@printStar
def print1():
    print("123")


# 业务逻辑代码
print1()

  结果如下:

--------------------
********************
123

Process finished with exit code 0

  3、对有参函数进行装饰时,要保证函数调用参数个数一致,为了通用,可以使用不定长参数,结合拆包操作进行处理”;

def printLine(func):
    def inner(*args, **kwargs):
        print("-" * 20)
        func(*args, **kwargs)

    return inner


@printLine
def print1(num1, num2, num3):
    print(num1, num2, num3)


@printLine
def print2(num):
    print(num)


# 业务逻辑代码
print1(666, 999, num3=888)
print2(123)

得到结果:

--------------------
666 999 888
--------------------
123

Process finished with exit code 0

  4、对有返回值的函数进行装饰,要保证函数返回值一致;

def printLine(func):
    def inner(*args, **kwargs):  # 为了通用,可变参数
        print("-" * 20)
        result = func(*args, **kwargs)  # 拆包操作
        return result  # 返回的值就是被调用函数的执行结果

    return inner


@printLine
def sum1(num1, num2, num3):
    print(num1, num2, num3)
    return num1 + num2 + num3  # 有返回值


@printLine
def sum2(num):
    print(num)  # 没有返回值


# 业务逻辑代码
result1 = sum1(147, 258, num3=369)
result2 = sum2(456)
print(result1, result2)

  结果如下:

--------------------
147 258 369
--------------------
456
774 None

Process finished with exit code 0

  4、带有参数的装饰器

# 根据不同的参数生成不同的装饰器
def getzhuangshiqi(char):
    def zhuangshiqi(func):
        def inner():
            print(char * 20)
            func()

        return inner

    return zhuangshiqi


@getzhuangshiqi("*")
@getzhuangshiqi("=")
def print1():
    print("666")


# 业务逻辑代码
print1()

  结果如下:

********************
====================
666

Process finished with exit code 0
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 一、装饰器的基本使用 在不改变函数源代码的前提下,给函数添加新的功能,这时就需要用到“装饰器”。 0.开放封闭原则...
    NJingZYuan阅读 3,493评论 0 0
  • 闭包和装饰器 闭包 定义:在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个...
    一只学不会编程的汪汪阅读 2,720评论 0 0
  • 0. 函数相关知识 1)Python中“一切皆对象”,函数也不例外 先定义一个函数: def func(): p...
    NJingZYuan阅读 2,828评论 0 1
  • 多重装饰器 ​众所周知,使用装饰器装饰一个函数时,装饰器会将原函数当做一个参数,传进装饰器函数中,然后返回一个新的...
    西西里加西阅读 5,697评论 1 1
  • 五一即将来临,世界这么大,我得去走走…… 雄安,举世瞩目!世人关注之焦点。 当然,我不列外,心早已出发…… ……
    玄真子阅读 2,895评论 0 0