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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容