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       #
foo.func_name = 'abc'
print foo       

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    # (,)
print g.func_closure    # (,)

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)

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


['__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               # 
print Foo.__call__      # 
print Foo()             # <__main__.Foo object at 0x02AAEED0>

def func():
    pass

print func.__call__     # 

类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日于杭州家中

你可能感兴趣的:(Python进阶(五))