python之理解闭包和装饰器

python之理解闭包和装饰器

1、闭包函数

1.1 python中函数都是对象

def shut(word='yes'):
    print(word.capitalize())


shut()
scream = shut
scream()
print(type(shut))
print(id(shut))
print(id(scream))

del shut
scream()
print(id(scream))

结果:

Yes
Yes
<class 'function'>
33454008
33454008
Yes
33454008

上面定义一个shut函数,由type获取其类型可知是class,那它当然是一个对象了(具体可参考理解元类)。

对象的话,我们就知道可以赋值给另外一个变量,可以在其它函数中定义,且函数可以返回另一个函数。

shut()这里是函数调用,scream = shut函数引用,scream()调用,然后下面打印它们的内存地址是一样的,再下面删除del shut变量,调用scream(),内存地址不变,这里可以想到另外一个东西:python的垃圾回收机制,其它理解系列会学习下。

1.2 函数引用与闭包

这个唠叨要一段时间,详细了解引用js的介绍javascript之理解闭包

简单来说,闭包是满足下面几个东西的函数:

  • 嵌套函数,即一个函数内部定义另外一个函数,外层函数称为A,内层函数称为B。
  • 在嵌套函数中的内层函数B在外层函数A中所在的全局作用域范围被调用。
  • 这时调用B函数的作用域有两个:A所在的全局作用域和B所在A中的局部作用域。

再简单来说就是:闭包就是能够读取其它函数内部变量的函数。这里可以理解B是闭包函数,然后A是其它函数。

一个简单例子:

def talk(name='shut'):
    who = 'daocoder'

    def shut(word='yes'):
        print('shut %s to %s' % (word.upper(), who))

    def whisper(word='yes'):
        print('whisper %s to %s' % (word.lower(), who))

    if name == 'shut':
        return shut
    elif name == 'whisper':
        return whisper
    else:
        return shut


print(talk())
talk()(word='mudai')
talk('whisper')(word='mudai')

结果:

<function talk.<locals>.shut at 0x0000000001F27840>
shut MUDAI to daocoder
whisper mudai to daocoder

talk函数内部再定义shutwhisper函数,并且两个函数用到了外边函数的变量whoname,那么这两个函数以及用到的一些变量称之为闭包。

1.3 闭包啥作用呢

def line_conf(a, b):
    def line(x):
        return a * x + b
    return line


line1 = line_conf(2, 5)
line2 = line_conf(1, 3)

print(line1(2))
print(line2(2))

结果:

9
5

上面的例子中,函数line和变量ab就构成了闭包。在创建闭包的时候,我们通过line_conf(a, b)说明了变量的取值,那么这样就确定了最终闭包函数(y=2x+5和y=x+3)。且我们只需要变换不同的参数就可以获取不同的直线函数表达式。这里体现代码的复用性。另外可以因此隐藏内部实现细节。

注意:由于闭包引用了外部函数的局部变量,那么外部函数的局部变量就一直会存在于内存中,内存泄漏是一个隐患。

2、装饰器

提到装饰器,应该提及另一个东西AOP(Aspect Origented Programming)面向切换编程。它的作用很强大,联想实际业务的场景,我们想用户在开始访问时记录一个日志,即整个框架跑起来初始化的时候,解析请求路由之前做这些事情。另外可用的场景有插入日志、性能测试、事务处理(登录)等。

装饰器即基于AOP思想的一个实现,也利用到了上节说的闭包。有了装饰器,我们就可以抽离出大量与函数本身无关的代码并重复加以利用,概况的说:装饰器就是为已存在的对象添加额外的功能。

2.1 python里的装饰器

def makebold(fn):
    def wrapped():
        return '<b>' + fn() + '</b>'
    return wrapped


def makeitalic(fn):
    def wrapped():
        return '<i>' + fn() + '</i>'
    return wrapped


@makebold
@makeitalic
def say():
    return "Hello"


print(say())

结果:

<b><i>Hello</i></b>

效果就如上,那继续讨论轮子是怎么造的。

2.2 手动实现装饰器

def my_decorator(fn):
    def wrapped(*args, **kwargs):
        new_name = kwargs['name'].replace('daocoder', 'mudai')
        new_age = args[0] + 1
        return fn(new_age, name=new_name)
    return wrapped


def say_name(*args, **kwargs):
    return "my name is %s and %d years old" % (kwargs['name'], args[0])


print(say_name(26, name='daocoder'))
print(my_decorator(say_name)(26, name='daocoder'))

结果:

print(say_name(26, name='daocoder'))
print(my_decorator(say_name)(26, name='daocoder'))

看着有些别扭、大体想法希望能表达出来。以上的调用过程如下:

1、say_name作为参数传递给my_decorator后,say_name指向my_decorator返回的wrapped;
2、然后wrapped作为一个新的函数去调用。
3、内部函数wrapped被引用,外部函数的变量fn(即say_name)并没有被释放,它保存的还是原来最初的定义。

2.3 python语法糖@的用法

稍微修改上面的轮子:

def my_decorator(fn):
    def wrapped(*args, **kwargs):
        new_name = kwargs['name'].replace('daocoder', 'mudai')
        new_age = args[0] + 1
        return fn(new_age, name=new_name)
    return wrapped


@my_decorator
def say_name(*args, **kwargs):
    return "my name is %s and %d years old" % (kwargs['name'], args[0])


print(say_name(26, name='daocoder'))

结果:

my name is mudai and 27 years old

那说明这个my_decorator(say_name)@my_decorator这两个的作用就是一样的。一个语法糖包装吧。

@my_decorator == my_decorator(say_name)

2.4 类装饰器

上面的介绍可以看出,装饰器需要callable对象作为参数,然后返回一个callback对象。一般在python中callable对象都是函数,但也有例外,只有某个类中写了__call__方法,那么对象就是callable的。

class MyDecorator(object):
    def __init__(self, func):
        self.func = func
        pass

    def __call__(self, *args, **kwargs):
        # print(args)
        # print(kwargs)
        # print(self.func)
        new_name = kwargs['name'].replace('daocoder', 'mudai')
        new_age = args[0] + 1
        return self.func(new_age, name=new_name)


@MyDecorator
def say_name(*args, **kwargs):
    return "my name is %s and %d years old" % (kwargs['name'], args[0])


print(say_name(26, name='daocoder'))

结果:

my name is mudai and 27 years old

以上大体调用过程如下:

1、MyDecorator作为装饰器对say_name进行装饰的时候,这时先去创建MyDecorator这个实例对象。
2、创建对象初始化时,会把say_name这个函数名当作参数传递到__init__方法中,这里保存say_name作为对象的func属性。
3、这时say_name指向的是MyDecorator的实例对象,然后调用say_name(),即调用MyDecorator的实例对象的call`方法。

这里和之前函数作为装饰器的区别就是第3步:函数作为装饰器时,say_name的指向应该是返回的wrapped方法,而这里say_name的指向是MyDecorator的实例对象。

3、感谢

Python中如何在一个函数中加入多个装饰器

某平台培训资料。

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

推荐阅读更多精彩内容

  • 〇、前言 本文共108张图,流量党请慎重! 历时1个半月,我把自己学习Python基础知识的框架详细梳理了一遍。 ...
    Raxxie阅读 18,916评论 17 410
  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,114评论 0 13
  • 我感叹命运之手,不知何从…… 你,是新中国最早的拿了红本本的赤脚医生,可以靠知识吃饭但最终却昄依佛门。你,有一场轰...
    四字真言阅读 591评论 4 1
  • 本故事并非虚构,如有雷同,请点喜欢。 从来都是我取笑爸妈亲戚朋友被诈骗上当什么的,万万没想到啊自己这次被结结实实弄...
    izan阅读 246评论 0 1
  • 当你对一位貌美如花的女子一见钟情时,二见倾心时,又苦苦暗恋不得,又辗转反侧,夜不能寐时的我想你朝思暮想时,是否想用...
    四夕清禾阅读 6,076评论 3 11