zoukankan      html  css  js  c++  java
  • Python进阶之[非局部变量,闭包,装饰器]

    阅读Tacotron2源码 之 Python进阶

    1. Non-Local Variable with Nested Function
    2. Closure in Python
    3. Decorator



    1. Non-Local Variable with Nested Function

        在Python中,除了全局变量(Global Variable)和局部变量(Local Variable)之外,还有一种变量叫Non-Local Variable

        Non-Local Variable的存在来源于python允许Nested Function的存在。C/C++中,结构体(类)里面再定义结构体(类)可以做到,但是函数里面再定义函数则不被允许。Python可以实现嵌套函数的原因在于Everything in Python is Object,写嵌套函数的初心无非是外层函数想要利用内层函数的返回值。

        无关紧要,python三种变量都是相对的概念,全局变量好理解,就不写了,主要是Local VariableNon-Local Variable如何区别。直接看一组代码就能明白:

    # Version 1
    def outer():
        y = 40
        def inner():
            nonlocal y
            print("before modified :", y)
            y = 50
            print("after modified  :", y)
        inner()
        print("outer:", y)
    outer()
    
    >>>before modified : 40
    >>>after modified  : 50
    >>>outer: 50
    

        这是一个典型的Nested Funtion(嵌套函数),inner()函数嵌套在outer()函数的内部。对于outer()函数来说,y就是它的Local Variable;对于inner()函数来说,有了nonlocal关键字,y就是它的Non-Local Variable

        此时,你可能会认为Non-Local Variable是用nonlocal关键字定义的,其实不然,没有这个关键字,它也是Non-Local Variable。请看下面一组代码:

    # Version 2
    def outer():
        y = 40
        def inner():
            # nonlocal y
            print("before modified :", y)
            # y = 50
            # print("after modified  :", y)
        inner()
        print("outer:", y)
    outer()
    >>>before modified : 40
    >>>outer: 40
    

        仔细观察,我只是注释了部分的代码,此时inner()函数访问(调用)了变量y,此时对于inner()函数来说,y仍然是它的Non-Local Variable

        那么现在一个明显的问题就是,既然Non-Local Variable不依赖于nonlocal关键字来定义,那这个关键字存在的意义是什么?继续看下面的代码:

    # Version 3
    def outer():
        y = 40
        def inner():
            # nonlocal y
            print("before modified :", y)
            y = 50
            print("after modified  :", y)
        inner()
        print("outer:", y)
    outer()
    >>>Error
    

        上面Version 3代码只是注释掉了Version 1中的关键字部分,然而运行却是报错的。然后你在看看Version 2和Version 3的区别,就会发现,Version 3试图在没有关键字nonlocal的声明前提下去修改Non-Local Variable,所以它报错了。

    总结一下

        Non-Local Variable依赖于嵌套函数中存在,并且有两种定义方式——显式定义和隐式定义。显示定义要用nonlocal关键字声明,此时内部函数不仅可以访问还可以修改Non-Local Variable;隐式定义无须声明,但是此时内部函数只有访问而没有修改Non-Local Variable的权利。


    2. Python Closure

        Closure(闭包),这个概念与上面的Non-Local Variable一样,它依赖于Nested Function而存在。一句话说什么是Closure

    外部函数返回内部函数的嵌套函数,就叫闭包。

        观察上面代码,发现外部函数outer()只是调用了内部函数inner(),而并没有将其return出来,所以这个嵌套函数不是闭包。下面是一个闭包的例子:

    def print_msg(msg):
        def printer():
            print(msg)
        return printer
    
    another = print_msg("Hello")
    another()
    >>>"Hello"
    

        因为Everything in Python is Object,所以函数的返回值是另一个函数完全没问题。

        闭包的概念就是这么简洁,用法在下面:

    del print_msg
    another()
    >>>Hello
    
    
    print_msg("Hello")
    >>>Traceback (most recent call last):
    >>>...
    >>>NameError: name 'print_msg' is not defined
    

        也很好懂,我们已经把外部函数print_msg()杀死了,但是内部函数printer()却活了下来,因为在杀死print_msg()之前,another = print_msg("Hello"),相当于another()继承了内部函数的地址,所以它仍然存在着。

    总结一下:

    什么时候需要用到Closure?

    1. 必须要写嵌套函数的时候
    2. 嵌套函数的内部函数需要访问非局部变量的时候
    3. 外部函数需要将内部函数作为返回值

    其实我感觉说了跟没说一样。。。目前觉得闭包就是用在下面要说的装饰器中。


    3. Decorator

        Decorator来源于现实需求,现有的代码功能有短缺,需要添加新功能,但是我想在不大刀改动原有代码的前提下,添加新功能并且新写完的代码具有向后兼容性。

        直接用例子说明:

    # Version 1
    # 阶乘函数
    def count(number):
    	mul = 1
    	for item in range(1, number):
    		mul *= item
    	print(mul)
    
    def cost(func, number):
    	start = time.time()
    	func(number)
    	end = time.time()
    	cost_time = end - start
    	return cost_time
    	
    time = cost(count, 6)
    print(time)
    >>>6
    >>>1s
    

        上面代码的需求是要计算阶乘函数耗费的时间,一个很直观的想法就是把阶乘函数作为参数传给cost()函数,这样运行cost()函数即可。这个做法可以但是并不完美,下面看看装饰器怎么做:

    # Version 2
    # 这是一个Closure
    def cost(func):
        def _measure_time(*args, **kwargs):
            start = time.time()
            func(*args, **kwargs)
            elapse = time.time() - start
            print("takes {} seconds.".format(elapse))
        return _measure_time
    
    @cost
    def count(number):
    	mul = 1
    	for item in range(number):
    		mul *= item
    	print(mul)
    
    count(4)
    >>>6
    >>>takes 1 seconds.
    

        Version 2就是一个装饰器,它没有改动原始的阶乘函数,只是新写了一个闭包cost,并且在阶乘函数头上添加了@cost,使用的时候,发现只运行count()函数,还输出了消耗的时间,这就叫代码的向后兼容性。

        再仔细看一下闭包的写法,用到了两种参数*args, **kwargs,这样,无论要装饰或者称为包起来的函数func()需要什么类型的参数,闭包都可以兼容。

        再提一句,Decorator可以叠加,如下:

    def star(func):
        def inner(*args, **kwargs):
            print("*" * 30)
            func(*args, **kwargs)
            print("*" * 30)
        return inner
    
    def percent(func):
        def inner(*args, **kwargs):
            print("%" * 30)
            func(*args, **kwargs)
            print("%" * 30)
        return inner
    
    @star
    @percent
    def printer(msg):
        print(msg)
    printer("Hello")
    
    >>>
    ******************************
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    Hello
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    ******************************
    

        观察输出,又可以对闭包和装饰器有一个直观的理解。

    总结一下:

        装饰器就是在代码中很少改动的前提条件下,添加新功能,并且新写完的代码具有向后兼容性。

  • 相关阅读:
    函数详解
    print()函数知识点总结
    python基本数据类型-字符串常用操作
    2020.7.17第十二天
    2020.7.16第十一天
    2020.7.15第十天
    2020.7.14第九天
    2020.7.13第八天
    2020.7.12第七天
    2020.7.11第六天
  • 原文地址:https://www.cnblogs.com/machine-lyc/p/11247462.html
Copyright © 2011-2022 走看看