Python闭包函数和函数装饰器笔记

2019-11-03  本文已影响0人  枯藤疯鸦

小白笔记
仅记录常规操作中较为不熟悉的操作类型

1、作用域
作用域是程序运行时变量可被访问的范围,定义在函数内的变量是局部变量,局部变量的作用范围只能是函数内部范围内,它不能在函数外引用。
定义在模块最外层的变量是全局变量,它是全局范围内可见的,当然在函数里面也可以读取到全局变量的。例如:

num=10  # 全局作用域变量

def foo():
    print(num)  # 10

而在函数外部则不可以访问局部变量。例如:

def foo():
    num=10

print(num)  # NameError: name 'num' is not defined

2、嵌套函数
函数不仅可以定义在模块的最外层,还可以定义在另外一个函数的内部,像这种定义在函数里面的函数称之为嵌套函数(nested function)例如:

def print_msg():
    # print_msg 是外围函数
    msg="zen of python"
    def printer():
        # printer是嵌套函数
        print(msg)
    printer()

# 输出 zen of python
print_msg()

对于嵌套函数,它可以访问到其外层作用域中声明的非局部(non-local)变量,比如代码示例中的变量 msg 可以被嵌套函数 printer 正常访问。
那么有没有一种可能即使脱离了函数本身的作用范围,局部变量还可以被访问得到呢?答案是闭包

3、什么是闭包
函数身为第一类对象,它可以作为函数的返回值返回,现在我们来考虑如下的例子:

def print_msg():
    # print_msg 是外围函数
    msg="zen of python"
    def printer():
        # printer 是嵌套函数
        print(msg)
        return printer

another=print_msg()

# 输出 zen of python
another()

这段代码和前面例子的效果完全一样,同样输出 "zen of python"。不同的地方在于内部函数 printer 直接作为返回值返回了。

一般情况下,函数中的局部变量仅在函数的执行期间可用,一旦 print_msg() 执行过后,我们会认为 msg变量将不再可用。然而,在这里我们发现 print_msg 执行完之后,在调用 another 的时候 msg 变量的值正常输出了,这就是闭包的作用。闭包使得局部变量在函数外被访问成为可能

这里的 another 就是一个闭包,闭包本质上是一个函数,它有两部分组成,printer 函数和变量 msg。闭包使得这些变量的值始终保存在内存中。
函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数,如上例中的 another()。

闭包,顾名思义,就是一个封闭的包裹,里面包裹着自由变量,就像在类里面定义的属性值一样,自由变量的可见范围随同包裹,哪里可以访问到这个包裹,哪里就可以访问到这个自由变量。

4、带参数的闭包

def adder(x):
    def wrapper(y):
        return x + y
    return wrapper

adder5 = adder(5)    # 执行完毕后,变量adder5指向了函数wrapper
# 输出 15
adder5(10)  # 执行是调用函数wrapper,传入参数 10,输出: 5(adder(5)中参数)+10 
# 输出 11
adder5(6)    # 5 + 6

闭包,可以理解为嵌套函数的特殊形式,函数返回值为子函数,且子函数调用的上级函数变量,不被释放,可在函数外被多次调用.
返回闭包时牢记一点:返回函数不要引用任何循环变量,否则后续会发生变化的变量

def count():
    fs= []
    for i in range(1,4):
        def f():
            return i*i
        fs.append(f)

    '''
    返回函数引用变量i,但它并非立刻执行。
    等到3个函数都返回时,它们所引用的变量i已经变成了3
    因此最终结果全部为9
    '''

    return fs

f1, f2, f3= count()
print(f1(),f2(),f3())

如果一定要引用循环变量怎么办?方法是再创建一个函数 g(j) ,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

def count():
    def g(j):
        def f():
            return j*j
        return f
    fs= []
    for i in range(1,4):
        fs.append(g(i))     # f(i)立刻被执行,因此i的当前值被传入f()
    return fs

f1, f2, f3= count()
print(f1(),f2(),f3())

5、装饰器的概念
对函数(或方法)进行装饰,可以在函数运行前,增强函数的功能
严格来讲,装饰器就是一个闭包函数,它的参数是:要增强功能的函数

import time

def timer(func):
    def inner():
        start_time= time.time()
        func()    # 被增强的函数在这里执行
        end_time= time.time()
        print('程序运行时间:', end_time- start_time)
    return inner

@timer
def test():
    time.sleep(3)
    print('test!')

if __name__== '__main__':
    test()

对函数使用装饰器后,主函数中 test() 实际执行的是:timer(test)

6、装饰器的运行顺序
装饰器在被装饰的函数定义之后立即运行,通常是在导入时(即python加载模块时)

def outer(func):
    print("%s outer running" % func)
    def inner():
        print("%s inner running" % func)
        return func()  # 如果return func 则不会运行func函数
    return inner

@outer
def test1():
    print("test1 runing")

@outer
def test2():
    print("test2 runing")

def test3():
    print("test3 runing")

def main():
    print("main runing")
    test1()
    test2()
    test3()

if __name__ == "__main__":
    main()

运行结果如下:

# 装饰器运行结果
<function test1 at 0x1040cc730> outer running    # 返回了函数 inner()
<function test2 at 0x1040cc840> outer running

# 主函数运行结果
main runing
<function test1 at 0x1040cc730> inner running    # 运行test1() 时,继续执行装饰器返回的 inner() 函数
test1 runing
<function test2 at 0x1040cc840> inner running
test2 runing
test3 runing

可以看出,在函数test1、test2定义后,装饰器outer立即被执行。

7、带参数的装饰器

如果函数要增强的功能,需要按不同的定位(参数),输出不同的增强信息,就需要建立一个带参数的装饰器
要实现带参数的装饰器,就要用两层的嵌套函数来实现

def say_hello(country):
    def wrapper(func):
        def deco(*args,**kwargs):        # 最终的返回函数
            if country== "china":
                print("你好!")
            elif country== "america":
                print('hello.')
            else:
                return
            # 真正执行函数的地方
            func(*args,**kwargs)
        return deco
    return wrapper

@say_hello("china")
def chinese():
    print("我来自中国。")

@say_hello("america")
def american():
    print("I am from America.")

if __name__== '__main__':
    chinese()
    american()

运行结果:

你好!
我来自中国。

hello.
I am from America.

注:修饰器修饰过的函数,会把被修饰函数的‘ __ name __ ’变为返回函数‘inner’ 或 ‘deco’ ,为防止有些依赖函数签名的代码执行不出错,需要将原始函数的属性复制到返回函数中。具体实现在定义的修饰器的返回函数前,增加一个 @functools.wraps(func) 内部修饰器,如下:

def outer(func):
    print("%s outer running" % func)

    @functools.wraps(func)      # 函数前需要导入 import functools 库  
    def inner():
        print("%s inner running" % func)
        return func()  # 如果return func 则不会运行func函数
    return inner

同理,带参数的装饰器进行同样的处理即可

参考链接:
https://zhuanlan.zhihu.com/p/26934085
https://zhuanlan.zhihu.com/p/68810314
https://zhuanlan.zhihu.com/p/65968462
https://www.liaoxuefeng.com/wiki/1016959663602400/1017451662295584

上一篇下一篇

猜你喜欢

热点阅读