Python 闭包函数和装饰器

Life consists not in holding good cards but in playing those you hold well.

一、闭包函数

装饰器的本质是闭包,我们先了解下闭包函数:

  • 1.在函数内部定义的函数
  • 2.引用了外部变量(但非全局变量)

也就是说,引用了外部变量(非全局变量)的这个嵌套内部函数被称为闭包函数。

判断是否是闭包函数

__closure__ 属性,用来判断该函数是否是闭包函数。如果是,返回 cell;否则,返回 None。
代码示例:

global_param = 100  # 全局变量


def outFunc(x):
    temp0 = 1 + x

    # 引用外部变量(非全局变量),inFunc1是闭包函数
    def inFunc1():
        temp1 = 2
        return temp0 + temp1

    # 引用外部的"全局变量",inFunc2不是闭包函数
    def inFunc2():
        temp2 = 3
        return temp2 + global_param

    # 不引用外部的变量,inFunc3不是闭包函数
    def inFunc3():
        temp3 = 4
        return temp3

    # 函数名.__closure__属性,用来判断函数是否是闭包
    print("inFunc1是否是闭包:", inFunc1.__closure__)  # 是闭包,返回cell
    print("inFunc2是否是闭包:", inFunc2.__closure__)  # 不是闭包,返回None
    print("inFunc3是否是闭包:", inFunc3.__closure__)  # 不是闭包,返回None

    return inFunc1() + inFunc2() + inFunc3()


print(outFunc(5))
print("outFunc是否是闭包:", outFunc.__closure__)

运行结果:

inFunc1是否是闭包: (<cell at 0x10617c690: int object at 0x105a06520>,)
inFunc2是否是闭包: None
inFunc3是否是闭包: None
115
outFunc是否是闭包: None

分析:

  • inFunc1 引用了外部变量(且非全局变量)temp0,是闭包函数。
  • inFunc2 引用了外部变量(但却是全局变量)global_param,不是闭包函数。
  • inFunc3 没有引用外部变量,不是闭包函数。
  • outFunc 不是内嵌函数,不是闭包函数。
通过闭包函数改变外部变量
def outer_func():
    loc_list = []

    def inner_func(name):
        loc_list.append(len(loc_list) + 1)
        print('name=%s, loc_list=%s' % (name, loc_list))

    return inner_func


# 获取闭包函数
wrapper_func = outer_func()
print(wrapper_func.__name__, wrapper_func.__closure__)
# 通过多次调用改变外部变量
wrapper_func("func1")
wrapper_func("func2")
wrapper_func("func3")
print("------")
outer_func()('func1')
outer_func()('func2')
outer_func()('func3')

运行结果:

inner_func (<cell at 0x1076f0350: list object at 0x1070f61e0>,)
name=func1, loc_list=[1]
name=func2, loc_list=[1, 2]
name=func3, loc_list=[1, 2, 3]
------
name=func1, loc_list=[1]
name=func2, loc_list=[1]
name=func3, loc_list=[1]
闭包陷阱

注意,外部变量的取值,是调用闭包函数的那一时刻所对应的值,这里容易混淆。
通过下面两个示例,有助于我们进一步理解闭包陷阱。
1)是闭包函数:

def my_func(*args):
    fs = []
    # i是外部变量,执行完for循环后i=2
    for i in range(3):
        # func引用外部的i变量,是闭包函数
        def func():
            return i * i

        print(id(func), func.__closure__)
        fs.append(func)

    # 到这里,i=2
    return fs


# 执行完后,返回三个函数,并且外部变量i=2
fs1, fs2, fs3 = my_func(3)
# i是调用函数这一时刻的外部变量i值:由于i=2,所以i*i=4
print(fs1())  # 4
print(fs2())  # 4
print(fs3())  # 4

运行结果:

4330552352 (<cell at 0x102271050: int object at 0x101afb460>,)
4330917616 (<cell at 0x102271050: int object at 0x101afb480>,)
4330941184 (<cell at 0x102271050: int object at 0x101afb4a0>,)
4
4
4

2)不是闭包函数:

def my_func(*args):
    fs = []
    for i in range(3):
        # func内部没有引用外部的变量,不是闭包函数
        def func(_i=i):
            return _i * _i

        print(id(func), func.__closure__)
        fs.append(func)
    return fs


# 执行到这里,返回三个函数及对应的默认参数值
fs1, fs2, fs3 = my_func()
print(fs1())
print(fs2())
print(fs3())

运行结果:

4330549760 None
4330941040 None
4330940608 None
0
1
4

二、Python 装饰器

装饰器(Decorators)是 Python 的一个重要部分,用来给其它函数添加额外功能(在不改变原函数代码的情况下)的函数。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等。

它还可以使代码变得更简洁。比如,如果要给某几个函数添加“计时”功能,你无需同时修改这几个函数的代码,只需定义一个计时函数,通过@符号添加装饰器即可。

另外,对于一个函数有多个装饰器的情况,通过调整装饰器的顺序,并设置断点查看运行过程,得到结论:

  • 1.装饰器的加载顺序是:从下到上,装饰器的执行顺序是:从上到下。
  • 2.更具体一点,多个装饰器之间是嵌套关系,按就近原则从里到外依次嵌套
    @transform_input
    @cal_time
    def func(*args):
        ...
    
    等价于
    
    transform_input_wrapper(*args) {
        result = cal_time_wrapper(*args) {
            result = func(*args) {
                ...
            }
            return result
        }
        return result
    }
    

代码示例:

from time import time, sleep


# 功能1.定义一个函数,用来计算函数func的时间
def cal_time(func):
    print("自定义{统计时间}装饰器...")

    # 引用外部的func变量,cal_time_wrapper是闭包函数
    def cal_time_wrapper(*args):
        print("{统计时间}装饰器的代码 before")
        start_time = time()  # time()获取系统当前时间
        print("调用函数:", func.__name__)
        result = func(*args)
        end_time = time()  # time()获取系统当前时间
        print("{统计时间}装饰器的代码 after")
        print("装饰器:{},{}".format(func.__name__, end_time - start_time))
        return result

    print("cal_time_wrapper()是否是闭包:", cal_time_wrapper.__closure__)

    return cal_time_wrapper


# 功能2.定义一个函数,对输入参数进行转换,如果参数是float就转换为int
def transform_input(func):
    print("自定义{转换参数}装饰器...")

    # 引用外部的func变量,transform_input_wrapper是闭包函数
    def transform_input_wrapper(*args):
        print("{转换参数}装饰器的代码 before")
        # 转换参数
        args = tuple(int(param) if type(param) == float else param for param in args)
        print("调用函数:", func.__name__)
        result = func(*args)
        print("{转换参数}装饰器的代码 after")

        return result

    print("transform_input_wrapper()是否是闭包:", transform_input_wrapper.__closure__)

    return transform_input_wrapper


@transform_input
@cal_time
def test1(a, b, c, d):
    # 睡眠1秒
    sleep(0.1)
    return (a + b + c + d) / 2


@cal_time
@transform_input
def test2(a, b, c):
    sleep(0.1)
    return (a + b + c) / 2


@cal_time
def test3(a, b):
    sleep(0.1)
    return (a + b) / 2


@cal_time
def test4(a):
    sleep(0.1)
    return a / 2


print("------")
print(test1(1, 2, 3.5, 4))
print("------")
print(test2(1, 2, 3))
print("------")
print(test3(1, 2))
print("------")
print(test4(1))

运行结果:

自定义{统计时间}装饰器...
cal_time_wrapper()是否是闭包: (<cell at 0x1054fa3d0: function object at 0x1054d78c0>,)
自定义{转换参数}装饰器...
transform_input_wrapper()是否是闭包: (<cell at 0x1054fa410: function object at 0x1054d7830>,)
自定义{转换参数}装饰器...
transform_input_wrapper()是否是闭包: (<cell at 0x1054fa510: function object at 0x1054cc200>,)
自定义{统计时间}装饰器...
cal_time_wrapper()是否是闭包: (<cell at 0x1054faf90: function object at 0x1054cc0e0>,)
自定义{统计时间}装饰器...
cal_time_wrapper()是否是闭包: (<cell at 0x1054fafd0: function object at 0x1054c7dd0>,)
自定义{统计时间}装饰器...
cal_time_wrapper()是否是闭包: (<cell at 0x1054fc050: function object at 0x1054c7cb0>,)
------
{转换参数}装饰器的代码 before
调用函数: cal_time_wrapper
{统计时间}装饰器的代码 before
调用函数: test1
{统计时间}装饰器的代码 after
装饰器:test1,0.10467410087585449
{转换参数}装饰器的代码 after
5.0
------
{统计时间}装饰器的代码 before
调用函数: transform_input_wrapper
{转换参数}装饰器的代码 before
调用函数: test2
{转换参数}装饰器的代码 after
{统计时间}装饰器的代码 after
装饰器:transform_input_wrapper,0.10127425193786621
3.0
------
{统计时间}装饰器的代码 before
调用函数: test3
{统计时间}装饰器的代码 after
装饰器:test3,0.10306096076965332
1.5
------
{统计时间}装饰器的代码 before
调用函数: test4
{统计时间}装饰器的代码 after
装饰器:test4,0.10345077514648438
0.5
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容