面向对象的python(二)

开始学习python的时候,看了一些教程和资料,都觉得在面向对象编程这一方面讲得比较零散,自己也就总觉得不得要领。直到看到了Oreilly出的Python in the Nutshell,英文版,特别是Charpter5: Object-Oriented Python,才开始明白一点点东西。这本书,对章节的编排非常合理,而且不光教你how还教你why,觉得受益匪浅。

看的过程中,自己陆续的记下一些东西,有对书的部分翻译,有自己的体会和测试代码。

翻译中,有少部分是直接翻译,大部分其实只是自己的意译,还添油加醋加上了自己的一些说明。毕竟,我的目的是为了把问题弄懂,不想去做那么多hard translation反让人如坠迷雾。于是想汇成一篇,就有了这篇文章。文章的安排,基本上和Python in the Nutshell的Charpter5相同,但内容要短得多。
Wilson Sun
----------------------------------------------------------------------------------------------------------------


1.1          新型对象(New-Style Classes)
在python的2.2和2.3版中,若对象直接或间接地继承了python的内建类型的对象,那么它就是新型对象。
“经典”往往是对旧事物的尊称,既然从2.2版开始推出新型对象,肯定是有种种好处的,所以该尽量用新型对象。
1.1.1     内建类型:object
从python的2.2开始,object是一种内建类型,它也是所有其它内建类型和新型对象的超类。
继承object的对象,需要重载下面方法:
_ _new_ _
_ _init_ _
_ _delattr_ _
_ _getattribute_ _
_ _setattr_ _
_ _hash_ _
_ _repr_ _
_ _str_ _

1.1.2     类级方法(Class-Level Methods)
类级方法是新型对象的特征之一,有两类:静态方法和对象方法。
1.1.2.1    静态方法(Static methods)
静态方法不存在bound和unbound的问题,可以直接调用。用staticmethod声明一个静态方法,如:
class AClass(object):
    def astatic(  ): print 'a static method'
    astatic = staticmethod(astatic)
anInstance = AClass(  )
AClass.astatic(  )                    

1.1.2.2    类方法(Class methods)
可以在类的内部和类的实例上调用类方法。用classmethod声明,如:
class ABase(object):    def aclassmet(cls): print 'a class method for', cls._ _name_ _aclassmet = classmethod(aclassmet)class ADeriv(ABase): passbInstance = ABase(  )dInstance = ADeriv(  )ABase.aclassmet(  )               # prints: a class method for ABasebInstance.aclassmet(  )           # prints: a class method for ABaseADeriv.aclassmet(  )              # prints: a class method for ADerivdInstance.aclassmet(  )           # prints: a class method for ADeriv
类方法的第一个参数,就是调用这个方法的对象。

1.1.3     新型对象
新型对象也具有所有经典对象的特征,但它们还有更为独特的特征,_ _init_ _和_ _new_ _
1.1.3.1    _ _init_ _
一个新型对象C,它会直接或间接地继承object中的_ _init_ _方法,如果你不在C当中重载_ _init_ _方法的话,你传递给C的_ _init_ _方法任何参数都会被python忽略。
为了避免乱传参数,建议:即使不想在_ _init_ _做任何事,也该重载_ _init_ _方法,如:
class C(object):
       def __init__(self): pass
这样,如果错误地向_ _init_ _传递了参数,python就会抛出一个异常。
1.1.3.2    _ _ new_ _
所有新型对象都有一个静态方法:_ _new_ _。
假设有一个新型对象C,现在你要创建C的一个实例x,那么你会写:
x = C(23)
执行过程是:python首先调用C中的_ _new_ _方法,_ _new_ _会返回一个实例,若检测过后该实例的确是C的实例,那么_ _init_ _会被调用,若_ _new_ _返回的实例不是C的实例,那么这个实例将不会被初始化(_ _init_ _)。这一过程和下面的代码等价:
x = C._ _new_ _(C, 23)
if isinstance(x, C): C._ _init_ _(x, 23)
你也可以自己在C的内部重载它的_ _new_ _方法,这种重载不需要用staticmethod来声明。
_ _new_ _方法可以用来返回不同的实例,可以用这种方式来实现Factory工厂模式,还可以实现singleton单态模式。

1.1.4     新型对象的实例
经典对象的实例中的所有特性,在新型对象的实例中也同样具备,新型对象实例还具备一些与之不同的特性。
1.1.4.1    属性(properties)
这里说的“属性”,是和方法相对的。在对象内部定义属性的时候,用python的内建类型:property。示例如下:
class Rectangle(object):
    def _ _init_ _(self, width, heigth):
        self.width = width
        self.heigth = heigth
    def getArea(self):
        return self.width * self.heigth
    area = property(getArea, doc='area of the rectangle')
上例中,area就是Rectangle的属性,由于只定义了get方法,所以它是只读属性。doc是属性的docstring参数,docstring的作用非常类似于注释,但和注释相比,它可以在运行时被使用,这时它的一大优点。
使用property的语法如下:
attrib = property(fget=None, fset=None, fdel=None, doc=None)

属性操作
代码示例
Python的实现
读取
n = x.attrib
返回property中fget函数值
赋值
x.attrib = 54
将值传入perperty中fset函数
删除
del x.attrib
调用fdel函数

在经典对象模型中,如果要实现上面的代码,要这样写:
class Rectangle:
    def _ _init_ _(self, width, heigth):
        self.width = width
        self.heigth = heigth
    def getArea(self):
        return self.width * self.heigth
    def _ _getattr_ _(self, name):
        if name=  ='area': return self.getArea(  )
        raise AttributeError, name
    def _ _setattr_ _(self, name, value):
        if name=  ='area':
            raise AttributeError, "can't bind attribute"
        self._ _dict_ _[name] = value

1.1.4.2    _ _slots_ _
_ _slots_ _的作用和_ _dict_ _一样,用来存放实例的所有方法和属性。_ _dict_ _是字典(dict)类型,而_ _slots_ _是元组(tuple)类型,所以用_ _slots_ _会更节约内存,这也是使用它的唯一目的。要注意的是,一旦使用_ _slots_ _,那么原先的_ _dict_ _就没有作用了。
如果一个对象会同时产生百万级数目的实例,用_ _slots_ _会起到节约内存的目的,如果只是几千个实例,那么没必要。
还需要注意的是:实例中的_ _slots_ _不会被子类继承。
示例:
class OptimizedRectangle(Rectangle):
    _ _slots_ _ = 'width', 'heigth'
1.1.4.3    _ _getattribute_ _
对实例属性的引用,都通过object中_ _getattribute_ _方法来实现。你也可以重载该方法来获得某些特殊的效果。例如,你不想list中的append方法被调用,就可以这样:
class listNoAppend(list):
    def _ _getattribute_ _(self, name):
        if name =  = 'append': raise AttributeError, name
        return list._ _getattribute_ _(self, name)
那么,每当x.append被调用的时候,都会出现异常。
1.1.4.4    Per-instance methods

1.1.5     新型对象中的继承
在继承这一方面,和经典对象相比,新型对象最大区别就是可以继承自内建类型的对象,而且python中是允许多重继承的。
1.1.5.1    方法的解析顺序
当存在继承关系的时候,python会到一个对象的基类(base class)中去查找方法或属性,特别是当存在多重继承关系的时候,查找的顺序是怎样的呢?这种查找顺序,被称为解析顺序(resolution order)。
假设有类A,它直接继承自B和C(顺序为:B,C),B和C又都继承自D。
经典对象模型和新型模型对象的解析顺序为下图所示:

经典对象模型的解析顺序是:左边优先,再深度优先。所以它的解析顺序是:先A-B-D-C-D。
新型对象模型的即席顺序是:A-B-C-D-object
写程序的时候,经典对象模型的这种解析顺序可能会产生一些问题,所以新型对象中更改了解析顺序,会先解析同级的基类。
1.1.5.2    超类调用
当重载(override)一个方法的时候,我们往往会对超类的同名方法做一些操作,但在多重继承的情况下,python目前的方法解析顺序并不完美。
看下面代码:
class A(object):
    def met(self):
        print 'A.met'
        
class B(A):
    def met(self):
        print 'B.met'
        A.met(self)
        
class C(A):
    def met(self):
        print 'C.met'
        A.met(self)
        
class D(B,C):
    def met(self):
        print 'D.met'
        B.met(self)
        C.met(self)

d=D()
d.met()
代码执行结果如下:
D.met
B.met
A.met
C.met
A.met
可以看到,A中的met被调用了两次。如果才能保证超类中的同名方法只被调用一次呢?这可以用python2.2中的super来解决,super是一种新的内建类型。调用super(aclass,obj)会返回obj的超类。
上面的代码可以做如下的修改:
class A(object):
    def met(self):
        print 'A.met'
class B(A):
    def met(self):
        print 'B.met'
        super(B,self).met(  )
class C(A):
    def met(self):
        print 'C.met'
        super(C,self).met(  )
class D(B,C):
    def met(self):
        print 'D.met'
        super(D,self).met(  )

注:tuple这个词,看到有多种翻译。觉得MS的Sql Server 2000联机手册中,把它译为“元组”,于是自己都采用这种翻译。