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
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************

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

总结一下:

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

posted @ 2019-07-25 22:37  JeffreyLee  阅读(302)  评论(0编辑  收藏  举报