转载:http://www.cnblogs.com/btchenguang/archive/2012/09/17/2689146.html
python中基于descriptor的一些概念(上)
1. 前言
python在2.2版本中引入了descriptor功能,也正是基于这个功能实现了新式类(new-styel class)的对象模型,同时解决了之前版本中经典类(classic class)系统中出现的多重继承中的MRO(Method Resolution Order)的问题,同时引入了一些新的概念,比如classmethod, staticmethod, super,Property等,这些新功能都是基于descriptor而实现的。总而言之,通过学习descriptor可以更多地了解python的运行机制。我在这也大概写一个汇总,写一下对这些东西的理解。欢迎大家讨论。 在这里,为文章中使用的词汇做一下说明:函数:指的是第一个参数不是self的函数,不在类中定义的函数方法:指是的第一个参数是self的函数实例:类的对象,instance对象模型:就是实现对象行为的整个框架,这里分为经典和新的两种 使用的python版本为python 2.7.22. 新式类与经典类
首先来了解一下新式类与经典类的区别,从创建方法上可以明显的看出:#新式类class C(object):
pass
#经典类
class B:
pass简单的说,新式类是在创建的时候继承内置object对象(或者是从内置类型,如list,dict等),而经典类是直接声明的。使用dir()方法也可以看出新式类中定义很多新的属性和方法,而经典类好像就2个: 这些新的属性和方法都是从object对象中继承过来的。
2.1 内置的object对象
内置的object对象是所有内置,object对象定义了一系列特殊的方法实现所有对象的默认行为。1. __new__,__init__方法这两个方法是用来创建object的子类对象,静态方法__new__()用来创建类的实例,然后再调用__init__()来初始化实例。 2. __delattr__, __getattribute__, __setattr__方法对象使用这些方法来处理属性的访问 3. __hash__, __repr__, __str__方法print(someobj)会调用someobj.__str__(), 如果__str__没有定义,则会调用someobj.__repr__(), __str__()和__repr__()的区别:- 默认的实现是没有任何作用的
- __repr__的目标是对象信息唯一性
- __str__的目标是对象信息的可读性
- 容器对象的__str__一般使用的是对象元素的__repr__
- 如果重新定义了__repr__,而没有定义__str__,则默认调用__str__时,调用的是__repr__
- 也就是说好的编程习惯是每一个类都需要重写一个__repr__方法,用于提供对象的可读信息,
- 而重写__str__方法是可选的。实现__str__方法,一般是需要更加好看的打印效果,比如你要制作
- 一个报表的时候等。
2.2 类的方法
新的对象模型中提供了两种类级别的方法,静态方法和类方法,在诸多新式类的特性中,也只有类方法这个特性, 和经典对象模型实现的功能一样。2.2.1 静态方法
静态方法可以被类或者实例调用,它没有常规方法的行为(比如绑定,非绑定,默认的第一个self参数),当有一堆函数仅仅是为了一个类写的时候,采用静态方法声明在类的内部,可以提供行为上的一致性。 创建静态方法的代码如下,使用装饰符@staticmethod进行创建 : 可以看出,不管是 类调用,还是实例调用静态方法,都是指向同一个函数对象2.2.2 类方法
也是可以通过类和它的实例进行调用,不过它是有默认第一个参数,叫做是类对象,一般被命名为cls,当然你也可以命名为其它名字,这样就你可以调用类对象的一些操作,代码如下,使用装饰符@classmethod创建:2.3 新式类(new-style class)
新式类除了拥有经典类的全部特性之外,还有一些新的特性。比如__init__发生了变化,新增了静态方法__new__2.3.1 __init__方法
据说在python2.4版本以前,使用新式类时,如果类的初始化方法没有定义,调用的时候写了多余的参数,编译器不会报错。我现在的python 2.7会报错,还是觉得会报错比较好点,下面给出新式类和经典类运行这个例子的情况:2.3.2 __new__静态方法
新式类都有一个__new__的静态方法,它的原型是object.__new__(cls[, ...])cls是一个类对象,当你调用C(*args, **kargs)来创建一个类C的实例时,python的内部调用是C.__new__(C, *args, **kargs),然后返回值是类C的实例c,在确认c是C的实例后,python再调用C.__init__(c, *args, **kargs)来初始化实例c。所以调用一个实例c = C(2),实际执行的代码为:c = C.__new__(C, 2)if isinstance(c, C):
C.__init__(c, 23)#__init__第一个参数要为实例对象object.__new__()创建的是一个新的,没有经过初始化的实例。当你重写__new__方法时,可以不用使用装饰符@staticmethod指明它是静态函数,解释器会自动判断这个方法为静态方法。如果需要重新绑定C.__new__方法时,只要在类外面执行C.__new__ = staticmethod(yourfunc)就可以了。 可以使用__new__来实现Singleton单例模式:class Singleton(object):
_singletons = {}
def __new__(cls):
if not cls._singletons.has_key(cls): #若还没有任何实例
cls._singletons[cls] = object.__new__(cls) #生成一个实例
return cls._singletons[cls] #返回这个实例运行结果如下: 使用id()操作,可以看到两个实例指向同一个内存地址。Singleton的所有子类也有这一特性,只有一个实例对象,如果它的子类定义了__init__()方法,那么必须保证它的__init__方法能够安全的同一个实例进行多次调用。
2.4. 新式类的实例
除了新式类本身具有新的特性外,新式类的实例也具有新的特性。比如它拥有Property功能,该功能会对属性的访问方式产生影响;还有__slots__新属性,该属性会对生成子类实例产生影响;还添加了一个新的方法__getattribute__,比原有的__getattr__更加通用。2.4.1 Property
在介绍完descriptor会回过头来讲这个。2.4.2 __slots__属性
通常每一个实例x都会有一个__dict__属性,用来记录实例中所有的属性和方法,也是通过这个字典,可以让实例绑定任意的属性。而__slots__属性作用就是,当类C有比较少的变量,而且拥有__slots__属性时,类C的实例 就没有__dict__属性,而是把变量的值存在一个固定的地方。如果试图访问一个__slots__中没有的属性,实例就会报错。这样操作有什么好处呢?__slots__属性虽然令实例失去了绑定任意属性的便利,但是因为每一个实例没有__dict__属性,却能有效节省每一个实例的内存消耗,有利于生成小而精干的实例。 为什么需要这样的设计呢?在一个实际的企业级应用中,当一个类生成上百万个实例时,即使一个实例节省几十个字节都可以节省一大笔内存,这种情况就值得使用__slots__属性。 怎么去定义__slots__属性?__slots__是一个类变量,__slots__属性可以赋值一个包含类属性名的字符串元组,或者是可迭代变量,或者是一个字符串,只要在类定义的时候,使用__slots=aTuple来定义该属性就可以了:可以看出实例a中没有__dict__字典,而且不能随意添加新的属性,不定义__slots__是可以随意添加的:使用时__slots__时需要注意的几点:1. 当一个类的父类没有定义__slots__属性,父类中的__dict__属性总是可以访问到的,所以只在子类中定义__slots__属性,而不在父类中定义是没有意义的。 2. 如果定义了__slots属性,还是想在之后添加新的变量,就需要把'__dict__'字符串添加到__slots__的元组里。 3. 定义了__slots__属性,还会消失的一个属性是__weakref__,这样就不支持实例的weak reference,如果还是想用这个功能,同样,可以把'__weakref__'字符串添加到元组里。 4. __slots__功能是通过descriptor实现的,会为每一个变量创建一个descriptor。 5. __slots__的功能只影响定义它的类,因此,子类需要重新定义__slots__才能有它的功能。