[Code] 优雅地使用python闭包

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。运行时,一旦外部的 函数被执行,一个闭包就形成了,闭包中包含了内部函数的代码,以及所需外部函数中的变量的引用。其中所引用的变量称作上值(upvalue)。

————摘自 维基百科

一、局部/全局变量

定义在方法内的变量是局部变量,不能在方法外做引用;定义在模块最外层的变量是全局变量,它是全局范围内可见的。对于作用域的概念,相信大家都理解,这里就不做赘述了。
那么问题来了,当方法内部有嵌套方法时,嵌套方法能否使用父方法中定义的局部变量呢?答案是可以,上栗子:

def foo():
    a = 10
    def sub():
       return a
    return sub()

此时调用foo()的返回值就是10哦,所以说嵌套方法可以用父方法中声明的局部变量。

二、闭包

上面的概念似乎还算比较容易理解,那么什么是闭包呢?我们来看下面一段代码:

def foo():
    a = 10
    def sub():
        return a
    return sub

乍一看这段代码似乎和上面的没有区别,但是注意,最后foo()返回的是sub方法本身而不是sub()哦。我们来尝试着调用一下foo():

>>> foo()
<function sub at 0x1019ab398>

调用foo()返回的是sub()方法,因为foo()返回的就是sub方法本身而不是sub方法的返回值,这里也不难理解,那么我们该如何调用sub()方法呢:

>>> test = foo()     #1
>>> test()
10

这时,sub()就被成功调用了。看到这里一切都好理解对不对?怀疑看了一篇假闭包文对不对?但实际上上面的代码确实构建了一个闭包。下面仔细讲解一下:

a作为foo()方法的局部变量,仅仅在foo()方法执行时才能够使用,一但foo()方法执行过后,我们就认为foo()方法中的内部变量已从内存中释放了。

再回到上述的代码,在#1处我们调用了foo()方法对不对?foo()方法执行完了对不对?这时候foo()方法的局部变量a要从内存中释放了对不对?然鹅。。。并没有,当我们调用test()的时候,局部变量a依旧有效。

这就是闭包的作用,使得局部变量在方法外部能够被访问。上面的test就是一个闭包,它由sub()方法以及a变量组成,这些变量的值也能够始终保存在内存中,不以foo()的终结而释放。

三、闭包作用

那么为什么要使用闭包呢?闭包避免了使用全局变量,此外,它又能够将函数和其操作的某些数据关联起来。见上栗a并不是全局变量,但通过闭包又能让sub()方法和局部变量a关联起来。
或许上面的描述有些抽象,我们上栗子吧,先说一下需求,要求将一段文字头尾的空格去掉。超级简单对不对,我们用my_strip()方法来实现:

def my_strip(text):
    value = text.strip()
    return value

这时候,又有一个需求,将文字外部包裹html标签<p>,你可以这么做:

def my_strip(test):
    value = text.strip()
    return "<p>{}<p>".format(value)

这么做固然可以,但对于变换莫测的需求变更,这么做显然不够合理,如果能将标签元素名作为一个变量传进来岂不是更好?继续改造:

>>>def add_tag(tag):
...    def my_strip(text):
...        value = text.strip()
...        return "<{tag}>{value}<{tag}>".format(tag=tag, value=value)
...    return my_strip
    
>>> adder = add_tag("p")
>>> adder(" cool ")
<p>cool<p>

这样标签名称就可以通过一个参数传入啦,但是这离优雅还差一点点,想要更加优雅就需要引入装饰器。装饰器也是基于闭包的一种应用场景,下面我们来简要介绍一下装饰器,先看下面这段代码:

>>>def decorator(func):
...    name = 'p'
...    def add_tag(text):
...        value = func(text)
...        return "<{name}>{value}<{name}>".format(name=name, value=value)
       return add_tag

>>>def my_strip(text):
...    value = text.strip()
...    return value

>>>my_strip=decorator(my_strip)
>>>my_strip(" cool ")

这段代码将my_strip()方法单独拆出来,作为参数传入decorator()方法。
而在调用时将my_strip作为闭包名称,现在任何对my_strip的调用都不会得到原始的my_strip,我们也可以这么理解my_strip成了一个“被装饰过”的版本,有没有领悟到“被装饰”的思想呢?

在python中,通过在函数定义前加@符号可以实现如上对函数的“装饰”,在上面的用了一个“被装饰过”的函数来替换原来的闭包来实现了“装饰”,上面的代码也等价于:

@decorator
def my_strip(text):
    value = text.strip()
    return value

又离优雅近了一步。但是注意到,这里的标签名是直接定义在decorator中的,可不可以作为参数传入呢?继续往下看:

def tag(name):
    def decorator(func):
        def wrapper(text):
            value = func(text)
            return "<{name}>{value}<{name}>".format(name=name, value=value)
        return wrapper
    return decorator


@tag("p")
def my_strip(text):
    value = text.strip()
    return value

我们将decorator()方法外面又包了一层tag方法,专门用于传标签名,这里也实现了多层闭包,此时将装饰器中传入标签名就可以加上想要的标签。而且这也做到了加标签字符串处理的业务分离,不管是加<p><a>,不管是将字符串首位空格去掉、将字符全转大些,这段代码都能轻松做到。而且它还可以支持加多重标签哦,看下面:

@tag("div")
@tag("p")
def my_strip(text):
    value = text.upper()
    return value

print my_strip("cool")   #打印出<div><p>COOL<p><div>

大写的优雅~

补充:斐波那契数列的优化装饰器写法

from functools import wraps

def memo(func):
    cache={}
    @wraps(func)
    def wrap(*args):
        if args not in cache:
            cache[args]=func(*args)
        return cache[args]
    return wrap

@memo
def fib(i):
    if i<2: return 1
    return fib(i-1)+fib(i-2)

print(fib(10))

再补充:时隔一年半,最近发现类作为装饰器使用,会很优雅

class DBFixture(object):
    '''
    这是一个数据库连接的装饰器代码,具体实现功能就是初始调用某个DAO方法时连接数据库,调用完之后关闭数据库连接
    '''
    def __init__(self, database='test_database'):
        self.database = database

    def __db_conn(self):
        engine = create_engine('mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(CONFIG['DB_LOCAL']['USERNAME'],
                                                                                    CONFIG['DB_LOCAL']['PASSWORD'],
                                                                                    CONFIG['DB_LOCAL']['HOST'],
                                                                                    CONFIG['DB_LOCAL']['PORT'],
                                                                                    self.database), echo=False)
        Session = sessionmaker()
        Session.configure(bind=engine)

        self.session = Session(autocommit=True)
        return self.session

    def __db_finalizer(self):
        self.db_session.flush()
        self.db_session.close()

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            session = self.__db_conn()
            res = func(session=session, *args, **kwargs)
            self.__db_finalizer()
            return res

        return wrapper

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,145评论 0 13
  • 单身的你会不会在某个时刻,冲动的想找一个人谈恋爱,只为脱单而脱单?平静以后,又觉得这个想法荒唐而可笑。 现在的我,...
    Annson是一只想飞的猪阅读 262评论 0 1
  • 今天看了罗辑思维节目,几个重要的知识点,总结一下 首先是知识焦虑,我们处于互联网时代,知识大爆炸,我们学习知识已经...
    LIuydanter阅读 138评论 0 1
  • 文/图:郑灵悦
    郑灵悦阅读 94评论 0 0