Python进阶(五)

博客链接:http://inarrater.com/2016/07/10/pythonadvance5/

7. Function

作为Callable部分第一个别提到,但是最后来分析它。原因其实很简单,我们已经发现前面的很多内容,包括bound method也好,static method也好,甚至operators,本质上都是function。
那么,如何去探究一个Python的Function的属性呢,阅读过前面内容的读者应该很明白了,通过一些简单的代码加上print就可以看到不少东西了。
我们先用dirf来看下一个function对象身上有什么。

def foo(a, b = 10):
    print a + b

print dir(foo)

输出结果:

['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']

不管双下划线开头的部分属性,我们只看对外开放的部分,先来看一看func_name属性吧。

print foo       #<function foo at 0x0235A2B0>
foo.func_name = 'abc'
print foo       <function abc at 0x0235A2B0>

func_name看上只是用来记录信息的一个名称而已,修改它并不会影响函数对象的调用。
我们再来看下func_defaults属性,看名称它是和默认值相关。

foo(1)          #11
print foo.func_defaults     #(10,)
foo.func_defaults = (100, )
foo(1)          #101

依然是把输出的结果放在代码行的后面以注释的形式给出,我们看到函数对象的func_defaults属性是一个元组,依次列出了所有默认参数。我们可以通过改变这个属性来改变已经被定义了的函数的默认参数。
为了可以看到func_closture的内容,我们构建一个闭包:

def bar(n):
    def f(x):
        return x + n
        
    return f
    
f = bar(1)
g = bar("abc")
print f(2)      # 3
print g("def")  # defabc
print foo.func_closure  # None
print f.func_closure    # (<cell at 0x029655F0: int object at 0x029378E0>,)
print g.func_closure    # (<cell at 0x02A31130: str object at 0x02972AE8>,)

f和g是两个闭包对象,可以看到foo这个函数对象身上的func_closure属性为None,f和g身上分别是两个cell对象,这部分内容和闭包中讲的部分就契合在了一起。
func_code属性我们也来看一下

def bar(a, b):
    print a * b

print bar.func_code
print dir(bar.func_code)

foo(6, 2)
foo.func_code = bar.func_code
foo(6, 2)

输出的结果由于比较长,写在了下面:

<code object bar at 02516C80, file "C:\Users\David-PC\Desktop\Advanced Course on Python 2016\a014.py", line 27>
['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']
8
12

我们可以看到,func_code是一个code object,它有的属性有很多,基本以co_开头,如果有兴趣,可以自己一点点去把他们print出来看,也可以发现很多有趣的东西,这里暂时不进行展开。例子的另外一部分展示了func_code是可以被替换的。

扩展:关于code对象,可以阅读Exploring Python Code Objects,了解怎样通过compile方法来生成code object,以及code类的一些属性。

属性名称 描述
func_name 函数名称
func_defaults 函数的默认值列表
func_code 函数体的code对象
func_globals 函数的全局命名空间
func_closure 函数的cell对象

关于function中比较重要的几个属性,以表格的形式总结出来。

属性名称 描述
func_name 函数名称
func_defaults 函数的默认值列表
func_code 函数体的code对象
func_globals 函数的全局命名空间
func_closure 函数的cell对象

看了function的这些属性,对于之前讨论过的hotfix是不是有了更深的了解呢?只需要替换一个函数对象的func_code,func_defaults,func_closure等属性,这个函数的行为就可以被改变了,结合上bound method和unbound method的动态生成的特性,对Python语言的动态性的原理的理解是否有更深入了一步呢?

8. Classes

关于类,其实有很多可以讨论的内容,从生命周期的角度来看,一个C++类的对象包含如下四个生命周期:

  1. 内存分配,malloc
  2. 调用构造函数初始化对象,A::A()
  3. 调用析构函数清理对象,A::~A()
  4. 释放内存,free

不同的编译器在内存分配或者释放的时候具体使用的函数可能不同,但这四个步骤是都有的,而且在C++中,1和4两个步骤是隐式的,即开发者通常不需要去关心(当然也有可以去操作的方法),它们分别隐含在了构造函数和析构函数当中。
对于Python的对象来说,其生命周期包含如下三个部分:

  1. 内存分配,__new__方法;
  2. 调用初始化(initializer),__init__方法;
  3. 调用终结器(finalizer),__del__方法;

对于自定义的类,上述的过程可以通过重载对应的方法来实现对于其过程的控制。可以看到,这里并没有内存释放的过程,也就是说开发者无法主动控制对象所占用过的内存的释放,这一部分是方便开发者不需要进行内存的管理,另外也利于Python语言本身进行对象缓存池的设计与实现。这部分内容在bound method的部分已经看到了缓存池在Python的应用,更多的讨论放在内存管理的部分来进行。

思考:Python中如何实现单例模式?是否可以通过重载__new__方法,在每次分配内存的时候返回同一个对象来实现呢?

答案是否定的,因为Python对于生命周期的控制决定了在__new__方法被调用,内存分配完毕之后会主动调用__init__方法,这样虽然分配的是同一个对象,但是多次__init__方法的调用可能会导致对象的属性被修改,可能会引发意料之外的bug,比如已经被修改过属性又被__init__方法改变等。

说了这些之后,对于Class我们返回Callable的主题,Python中的Class也是一个Callable的对象,调用一个类的结果很简单,就是获得一个类的实例化对象,我们来看一个简单的例子。

class Foo(object):
    pass

print Foo               # <class '__main__.Foo'>
print Foo.__call__      # <method-wrapper '__call__' of type object at 0x029D33E0>
print Foo()             # <__main__.Foo object at 0x02AAEED0>

def func():
    pass

print func.__call__     # <method-wrapper '__call__' of function object at 0x029C77F0>

类Foo是一个class对象,它是可以访问到__call__属性的,它是一个id为0x029D33E0的type对象的'call'方法的method-wrapper,如果打印print id(Foo)的话,你可以发现它的十六进制结果就是0x029D33E0。这容易理解,而对于method-wrapper,我把它理解为一个方法的封装。查了一些资料,但是没有找到官方的精准答案,这里我猜测对象的创建、函数的执行等过程,在Python中可能是一种C的实现,因此在Python层给出的是一个wrapper,可以让他们的行为像一个Python的函数一样。当然这是我的猜测,如果有知道准确答案的朋友欢迎指导。

思考:Class作为一个Callable的类型,对于我们开发中有什么好处吗?

想象一下工厂方法在C++中的实现,通常需要通过一个函数来封装一个对象的创建过程,而在Python中,我们可以把对象类型存储在一个字典或者列表中,通过映射关系,可以直接生成对象。某种程度上说,这也是一种“反射”机制。具体的代码由于比较简单,此处就不列举了。

总结:关于Callable的部分已经聊得差不多了,从我们分析的过程看,我们通常使用dir和print在加上一些分析能力就可以看出Python语言设计上的一些原理和思路,一切皆对象的理念被应用的淋漓尽致,而为了实现其动态特性,Python语言做了很多特殊的设计和方法。

2016年7月10日于杭州家中

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,690评论 0 9
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,637评论 18 139
  • 两本不错的书: 《Python参考手册》:对Python各个标准模块,特性介绍的比较详细。 《Python核心编程...
    静熙老师哈哈哈阅读 3,359评论 0 80
  • 教程总纲:http://www.runoob.com/python/python-tutorial.html 进阶...
    健康哥哥阅读 2,017评论 1 3
  • 凌晨3点半,未眠。不知为何,丝毫没有睡意。躺在床上,回想起了自己从初中以来到现在所经历的一些事,一些对我来说永远在...
    半夜微凉阅读 242评论 0 4