《流畅的python》阅读笔记 - 第七章:函数装饰器和闭包

既定一个函数func(),那么如果需要增强一些功能,最简单的方法就是修改这个函数,但是这就需要改动以前的代码, 这可能引入一些新的问题,甚至如果函数不能被修改,这种操作就无法实现。python函数装饰器提供了这样一个功能:在函数外实现对函数功能的修改,请具体参考以下几个示例:

def deco(func):
    def inner():
        print("running inner()")
    return inner

@deco
def target():
    print("running target()")

target()

输出

running inner()

这里,我们使用@deco来装饰target()函数,发现target()原本的print()语句没有打印,反而运行了inner(),这是装饰器的核心功能——它替代了原先的函数,我是这么理解的:第10行调用了target(),由于它被@deco装饰器装饰,所以它其实等于:deco(target) 的返回值,注意是返回值,而不是deco函数本身。

似乎还不知道它有什么作用?现在我们让被装饰的函数运行本声的代码不变的情况下,在某一函数进入和结束的时候分别打印消息提示表示进入和退出。当然我们可以在函数内部最前面,以及return的前面分别加入print(),但是这次用装饰器来实现:

def deco(func):
    def track():
        print("%s Start "%func.__name__)
        rt = func()
        print("%s Eed "%func.__name__)
    return track

@deco
def target():
    print("running target()")

target()

输出

target Start 
running target()
target Eed 

这样我们就打印出了函数运行时候的进入和输出,如果我们另一个函数也要实现这个功能,用deco装饰非常的简单:

def deco(func):
    def track():
        print("%s Start "%func.__name__)
        rt = func()
        print("%s Eed "%func.__name__)
    return track

@deco
def target():
    print("running target()")

#新添加的 target2 函数
@deco
def target2():
    print("running target2()")


target2()

输出

target2 Start 
running target2()
target2 Eed 

这种方法是方便安全的,第一,它对原来的函数是否可以修改没有要求,第二,我们不需要修改原来的函数,这就避免引入其它的问题(不小心修改到其它的代码)。第三,其他函数添加这个功能非常简单。
最后我们注意到,deco函数返回值是track()函数,根据前面的结论,被某一个装饰器装饰的函数,最后会被替换成这个装饰器的返回值,在这个例子中,每次调用traget()其实就是调用track(),下面来看一个示例,它证明了这一点,却又引入了一个新的问题:

def deco(func):
    print("deco running")
    def track():
        print("%s Start "%func.__name__)
        rt = func()
        print("%s Eed "%func.__name__)
    return track

@deco
def target():
    print("running target()")
    

#新添加的 target2 函数
@deco
def target2():
    print("running target2()")


target2()

输出:

deco running
deco running
target2 Start 
running target2()
target2 Eed 

首先会打印出2行deco running,这似乎有点奇怪,原因是在使用装饰时,它就被执行一次了,这段示例中,我们使用了2次,分别是target()target2()

闭包

来看一段关于全局变量的代码:
以下程序正常输出 10

var = 10
def func():
    print(var)
func()

以下程序不仅没有输出11,反而报错了

var = 10
def func():
    var = var+1
    print(var)

func()

这个问题有点奇怪,因为第一段程序似乎表明python知道var是一个外部变量,它在函数外找到它,并正确打印,可是第二段程序只是执行最简单的加1却报错了,好像找不到var。
要想第二段程序正常运行,我们必须在函数内使用global来强调,var 是全局的:

var = 10
def func():
    global var
    var+=1
    print(var)

func()

nonlocal 声明

有时候,global也不可用了,看以下程序

def outer():
    print("outer running")
    var = 12
    def inner():
        print("inner running")
        nonlocal var
        var+=1
        print(var)
        return var

    return inner

func = outer()
func()

这段代码是可以正常运行的,第6行使用了nonlocal来声明var变量,使用global程序会报错,我的理解是,var在函数outer()内,所以它不是全局的变量,所以无法通过global来找到它,nonlocal指的是非inner()内部的变量。

单分派乏函数

在 python 中,我们可以给函数传入任何的值:

def add(n1,n2):
    print(n1+n2)

add(1,1)
add(2.0,3.4)
add("12","34")

在C语言中,这似乎没有这么容易,因为C的函数对参数有类型的要求,在C++中,虽然对参数有要求,但是我们可以通过重载函数来实现一个函数名可以接收不同的参数类型。这点来看,python似乎不需要重载?但是有一种情况,比如对于字符串的相加,我们不要拼接,而是按他们的编码值相加,这样我们就得函数里面去判断,这个值是不是字符串,如果不同的类,都需要不同的操作,那这个add()函数就要一直被修改。这样又出现了上面的问题。

python 内置的@singledispatch实现了这样一个功能,它可以使用@func.register(obj),来装饰类似于C++语言中的重载函数,详见P171示例7-21

你可能感兴趣的:(流畅的python,python,开发语言)