Python进阶(二)

博客链接:http://inarrater.com/2016/07/03/pythonadvance2/

这一部分是关于Python的Callable。在Stackoverflow上有一个专门的问题叫做“What is a "callable" in Python”,高票回答中说:

A callable is anything that can be called.

这个回答很抽象,大雄从更具体的角度来阐述Callable这个概念——在Python中哪些是callable的?

  • function
  • closure
  • bound method
  • unbound method
  • class method
  • static method
  • functor
  • operator
  • class

先说答案,很明显,列出的这些都是callable的。这些概念中的大部分我在工作中都有使用,包括比如closure的坑也帮助新同学调试bug的时候看到新入职的同学自己踩到过,但是对于bound methodunbound method这些概念还不是很清晰。我们也一个个来看。

3. Closure

Closure,闭包,在Python中本质上是一个函数,或者更具体来说它和Function的区别是它包含了Code和Environment,而Python中Environment又可以分为globals、locals和cells三部分。
globals和locals比较容易理解,其实就是两个dict,分别保存了全局变量和局部变量,那这个cells是什么?我们先来看一个非常经典的例子:

def foo():
    logout_lst = []

    for i in xrange(5):
        def logout():
            print i
        logout_lst.append(logout)

    for l in logout_lst:
        l()

foo()

思考:这段代码的输出是什么?

分析一下这段代码,虽然这里为了方便演示,构造了一个只有print的逻辑,你可能会质疑它的作用,但是在我们开发的过程中,就有同学在循环内部定义了类似的闭包用于引擎回调的调用,引用了外部了一个类似i的变量。例子中,在foo的函数内部,代码def logout()定义了一个闭包(写到这里让我想起了遥远的过去写JAVA代码时使用的Inner Class),然后我们想使用外部变量i的值,这里只是把它输出出来,通常我们想要输出的结果是打印0、1、2、3、4这几个数字,当然中间有换行,但是最终的输出结果是什么呢?
5个4!
为什么呢?我们来添加一些输出日志来查看一下,为了方便看输出,我们只循环两次来看,修改后的代码如下:

def foo():
    logout_lst = []

    for i in xrange(2):
        def logout():
            print "i:", i, id(i)
            print "globals:", globals()
            print "locals:", locals()
        logout_lst.append(logout)

    for l in logout_lst:
        l()
        print "Cells:", l.__closure__, id(l.__closure__[0].cell_contents)
        print ''

foo()

输出的结果如下:

i: 1 35882616
globals: {'__builtins__': <module '__builtin__' (built-in)>, '__file__': 'F:\\David\\narrator.py', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x022C72B0>, '__doc__': None}
locals: {'i': 1}
Cells: (<cell at 0x02354570: int object at 0x02238678>,) 35882616

i: 1 35882616
globals: {'__builtins__': <module '__builtin__' (built-in)>, '__file__': 'F:\\David\\narrator.py', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x022C72B0>, '__doc__': None}
locals: {'i': 1}
Cells: (<cell at 0x02354570: int object at 0x02238678>,) 35882616

首先打印一下i的值与i这个变量的id,你可以认为这是i在Python虚拟机中的唯一编号,两次输出它的值都是1,id也都是一个35882616,然后输出一下globals和locals看一下,这两个很简单,不做分析了。最后通过__closure属性来看下闭包的内容:

Cells: (<cell at 0x02354570: int object at 0x02238678>,)

这就是前面说的cells,它是一个cell对象,里面的内容有一个int对象,通过cell_contents属性可以查看到它的id是35882616,和i是一样的。
可以看出,cells就是对于up-values的引用(references)注意引用
那之前的输出就很容易理解了,引用,当后面调用闭包执行的时候,i变量值已经变成了4,那输出i自然每次都是4。
最后,如何修改可以让你的代码可以按照之前的计划正常执行呢?很简单,不要直接使用cells中的值,而是用一个参数来让它变成参数,就是定义这个闭包的时刻的值了。

def foo():
    logout_lst = []

    for i in xrange(2):
        def logout(x = i):
            print "x:", x, id(x)
            print "globals:", globals()
            print "locals:", locals()
        logout_lst.append(logout)

    for l in logout_lst:
        l()
        print "Cells:", l.__closure__
        print ''

foo()

输出结果:

x: 0 37062276
globals: {'__builtins__': <module '__builtin__' (built-in)>, '__file__': 'F:\\David\\narrator.py', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x023E72B0>, '__doc__': None}
locals: {'x': 0}
Cells: None

x: 1 37062264
globals: {'__builtins__': <module '__builtin__' (built-in)>, '__file__': 'F:\\David\\narrator.py', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x023E72B0>, '__doc__': None}
locals: {'x': 1}
Cells: None

此处,cells的内容变为了None,输出的结果也是0和1,它们的id自然也不同。其实参数也可以写成def logout(i = i):,内部可以使用i,但是这会造成一些困扰,个人不推荐这么写。

思考:那么你以为这个坑就踩完了吗?有没有哪里还可能存在问题?

def logout(x = i):这种定义虽然用在闭包里,但是其实是函数的默认参数,那么默认参数如果使用list、dict或者python object等这样mutable的值会怎样?这自然是另外一个入门级的坑:

背景: 不建议在函数默认参数中使用mutable value,而保证只使用immutable value。

但有时候为了解决一个坑,可能不小心踩入另外一个坑。如果这里使用了,比如一个list对象作为参数,那么创建出来的这几个闭包中的x都引用的会是同一个对象,而且,在任何一个闭包多次调用的时候,x的值都是同一个对象的引用。如果像例子中是只读的逻辑的话,可能没有问题,如果后面有人添加了修改的逻辑,那就呵呵呵呵了。可能会乱成一锅粥,出现各种神奇的现象,写这样逻辑的人自求多福吧。

总结:理解闭包的概念,理解引用的概念,编写代码保持思路清晰,明确自己使用的变量存在在哪里,是一件非常非常重要的事情,对团队开发中避免匪夷所思令人抓狂的Bug很有帮助!

这一部分只讲闭包这一个点,其实关于闭包还有很多知识点,有兴趣的可以自己查阅相关资料。第三部分讲解bound method和unbound method,这是我这次课程最喜欢的部分。

PS: 很多坑,你看过文章介绍,或者听同事讲过,但是写代码的时候有时还是会由于当时思路的混乱而饶进去,重新踩一遍,这往往难以避免,不亲身经历的坑思维上很难那么敏感。经验学习和知识积累的作用,是让你从坑中往外爬的时候更快一些,回头看那些坑印象更深刻一些。

2016年7月2日于杭州网易大厦

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

推荐阅读更多精彩内容

  • 个人笔记,方便自己查阅使用 Py.LangSpec.Contents Refs Built-in Closure ...
    freenik阅读 67,696评论 0 5
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • 以下翻译自Apple官方文档,结合自己的理解记录下来。翻译基于 swift 3.0.1 原文地址 Closure...
    艺术农阅读 1,534评论 0 3
  • 顾客永远是对的,学员也是我们的顾客,但是我们要有这么一个心态:错的,永远是学员。 学员和我们一样!都...
    蜀通驾校张国安阅读 644评论 0 0
  • 1.MRC环境下桥接 - (void)MRC{ //MRC下桥接 //Foundation到CoreFoundat...
    流氓兔刘阅读 223评论 0 0