引言

提到面向对象,总是离不开几个重要的术语:多态(Polymorphism),继承(Inheritance)和封装(Encapsulation)。Python也是一种支持OOP的动态语言,本文将简单阐述python对面向对象的支持。

在讨论PythonOOP之前,先看几个OOP术语的定义:

  • 类:对具有相同数据和方法的一组对象的描述或定义。

  • 对象:对象是一个类的实例。

  • 实例(instance):一个对象的实例化实现。

  • 标识(identity):每个对象的实例都需要一个可以唯一标识这个实例的标记。

  • 实例属性(instance attribute):一个对象就是一组属性的集合。

  • 实例方法(instance method):所有存取或者更新对象某个实例一条或者多条属性的函数的集合。

  • 类属性(classattribute):属于一个类中所有对象的属性,不会只在某个实例上发生变化

  • 类方法(classmethod):那些无须特定的对性实例就能够工作的从属于类的函数。

1.Python中的类与对象

Python中定义类的方式比较简单:

class 类名:

类变量

def __init__(self,paramers):

def 函数(self,...)

…...

其中直接定义在类体中的变量叫类变量,而在类的方法中定义的变量叫实例变量。类的属性包括成员变量和方法,其中方法的定义和普通函数的定义非常类似,但方法必须以self作为第一个参数。

举例:

>>>class MyFirstTestClass:

classSpec="itis a test class"

def__init__(self,word):

print"say "+word

defhello(self,name):

print"hello "+name


Python类中定义的方法通常有三种:实例方法,类方法以及静态方法。这三者之间的区别是实例方法一般都以self作为第一个参数,必须和具体的对象实例进行绑定才能访问,而类方法以cls作为第一个参数,cls表示类本身,定义时使用@classmethod;而静态方法不需要默认的任何参数,跟一般的普通函数类似.定义的时候使用@staticmethod

>>>class MethodTest():

count= 0

defaddCount(self):

MethodTest.count+=1

print"I am an instance method,my count is"+str(MethodTest.count),self

@staticmethod

defstaticMethodAdd():

MethodTest.count+=1

print"I am a static methond,my count is"+str(MethodTest.count)

@classmethod

defclassMethodAdd(cls):

MethodTest.count+=1

print"I am a class method,my count is"+str(MethodTest.count),cls

>>>

>>>a=MethodTest()

>>>a.addCount()

Iam an instance method,my count is 1 <__main__.MethodTest instanceat 0x011EC990>

>>>MethodTest.addCount()


Traceback(most recent call last):

File"", line 1, in

MethodTest.addCount()

TypeError:unbound method addCount() must be called with MethodTest instance asfirst argument (got nothing instead)

>>>a.staticMethodAdd()

Iam a static methond,my count is2

>>>MethodTest.staticMethodAdd()

Iam a static methond,my count is3

>>>a.classMethodAdd()

Iam a class method,my count is4 __main__.MethodTest

>>>MethodTest.classMethodAdd()

Iam a class method,my count is5 __main__.MethodTest


从上面的例子来看,静态方法和类方法基本上区别不大,特别是有Java编程基础的人会简单的认为静态方法和类方法就是一回事,可是在Python中事实是这样的吗?看下面的例子:

>>>MethodTest.classMethodAdd()

Iam a class method,my count is5 __main__.MethodTest

>>>class subMethodTest(MethodTest):

pass

>>>b=subMethodTest()

>>>b.staticMethodAdd()

Iam a static methond,my count is6

>>>b.classMethodAdd()

Iam a class method,my count is7 __main__.subMethodTest

>>>a.classMethodAdd()

Iam a class method,my count is8 __main__.MethodTest

>>>




如果父类中定义有静态方法a(),在子类中没有覆盖该方法的话,Sub.a()仍然指的是父类的a()方法。而如果a()是类方法的情况下,Sub.a()指向的是子类。@staticmethod只适用于不想定义全局函数的情况。

看看两者的具体定义:

@staticmethod function is nothing morethan a function defined inside a class. It is callable withoutinstantiating the class first. It’s definition is immutable viainheritance.


@classmethod function also callablewithout instantiating the class, but its definition follows Subclass, not Parent class, via inheritance. That’s because the firstargument for @classmethod function must always be cls (class).

  • 封装和访问控制

Java不同,Python的访问控制相对简单,没有publicprivateprotected等属性,python认为用户在访问对象的属性的时候是明确自己在做什么的,因此认为私有数据不是必须的,但是如果你必须实现数据隐藏,也是可以的,具体方法就是在变量名前加双下划线。__privatedata=0,定义私有方法则是在方法名称前加上__下划线。但即使对于隐藏的数据,也是有一定的方法可以访问的。方法就是__className__attrNamePython对于私有变量会进行NamemanglingPython中为了方便定义私有的变量和方法,防止和继承类以及其他外部的变量或者方法冲突而采取的一种机制。在python中通过__spam定义的私有变量为最终被翻译成_classname__spam,其中classname为类名,当类名是以_开头的时候则不会发生NamemanglingNamemangling 存在的一个问题是当字符串长度超过255的时候则会发生截断。

>>>class PrivateTest:

__myownedata=12

def__myownmethod(self):

print"can you see me?"

defsayhi(self):

print"say hi"

>>>class subPrivateTest(PrivateTest):

pass

>>>

>>>subPrivateTest.__myownedata


Traceback(most recent call last):

File"", line 1, in

subPrivateTest.__myownedata

AttributeError:class subPrivateTest has no attribute '__myownedata'

>>>

>>>subPrivateTest._PrivateTest__myownedata

  • 构造函数和析构函数

Python的构造函数有两种,__init____new__,__init__的调用不会返回任何值,在继承关系中,为了保证父类实例正确的初始化,最好显示的调用父类的__init__方法。与__init__不同,__new__实际是个类方法,以cls作为第一个参数。


如果类中同时定义了__init____new__方法,则在创建对象的时候会优先使用__new__.

class A(object):

def __init__(self):

print("in init")

def __new__(self):

print("in new")


A()


如果__new__需要返回对象,则会默认调用__init__方法。利用new创建一个类的对象的最常用的方法为:super(currentclass,cls).__new__(cls[, ...])

class A(object):

def __new__(cls):

Object = super(A,cls).__new__(cls)

print "in New"

return Object

def __init__(self):

print "in init"



class B(A):

def __init__(self):

print "in B's init"


B()

__new__构造函数会可变类的定制的时候非常有用,后面的小节中会体现。

Python由于具有垃圾回收机制,通常不需要用户显示的去调用析构函数,即使调用,实例也不会立即释放,而是到该实例对象所有的引用都被清除掉后才会执行。

>>>class P:

def__del__(self):

print"deleted"


>>>class S(P):

def__init__(self):

print'initialized'

def__del__(self):

P.__del__(self)

print"child deleted"


>>>a=S()

initialized

>>>b=a

>>>c=a

>>>id(a),id(b),id(c)

(18765704,18765704, 18765704)

>>>del a

>>>del b

>>>del c

deleted

childdeleted

>>>


  • 绑定与非绑定

在前面的例子中我们讨论过类的实例方法必须通过实例调用,如果直接通过类去访问会抛出异常,这种通过实例来访问方法就叫绑定,调用的时候不需要显示传入self参数,而调用非绑定方法需要显示传入self参数,比如当子类继承父类定义构造函数的时候,需要显示调用父类的构造函数,但此时该方法并未与任何实例绑定,调用的时候需要使用superclassName.__init_(self)

静态方法可以直接被类或类实例调用。它没有常规方法那样的特殊行为(绑定、非绑定、默认的第一个参数规则等等)。完全可以将静态方法当成一个用属性引用方式调用的普通函数来看待。任何时候定义静态方法都不是必须的(静态方法能实现的功能都可以通过定义一个普通函数来实现)

3. Python中的继承

  • 继承

Python同时支持单继承与多继承,继承的基本语法为class新类名(父类1,父类2,..,当只有一个父类时为单继承,当存在多个父类时为多继承。子类会继承父类的所有的属性和方法,子类也可以覆盖父类同名的变量和方法。在传统类中,如果子类和父类中同名的方法或者属性,在查找的时候基本遵循自左到右,深度优先的原则。如下列:

>>>class A:

defsayhi(self):

print'I am A hi'

>>>class B:

defsayhi(self):

print'I am B Hi'

>>>class C(A,B):

pass

>>>d=C()

>>>d.sayhi()

Iam A hi

>>>B.sayhi(d)

Iam B Hi

如果想调用父类Bsayhi方法则需要使用B.sayhi(d).而在python引入新式类后,在继承关系中,方法和属性的搜索有所改变,使用C3算法。具体将在MRO中详细讨论。


关于继承的构造函数:

  1. 如果子类没有定义自己的构造函数,父类的构造函数会被默认调用,但是此时如果要实例化子类的对象,则只能传入父类的构造函数对应的参数,否则会出错

classAddrBookEntry(object):

'addressbook entry class'

def__init__(self, nm, ph):

self.name= nm

self.phone= ph

print'Created instance for:', self.name

defupdatePhone(self, newph):

self.phone = newph

print'Updated phone# for:', self.name



classEmplAddrBookEntry(AddrBookEntry):

'EmployeeAddress Book Entry class'

defupdateEmail(self, newem):

self.email= newem

print'Updated e-mail address for:', self.name


john= EmplAddrBookEntry('John Doe', '408-555-1212')

printjohn.name




  1. 如果子类定义了自己的构造函数,而没有显示调用父类的构造函数,则父类的属性不会被初始化


classAddrBookEntry(object):

'addressbook entry class'

def__init__(self, nm, ph):

self.name= nm

self.phone= ph

print'Created instance for:', self.name

defupdatePhone(self, newph):

self.phone = newph

print'Updated phone# for:', self.name



classEmplAddrBookEntry(AddrBookEntry):

'EmployeeAddress Book Entry class'

def__init__(self, nm, ph, id, em):

#AddrBookEntry.__init__(self, nm,ph)

self.empid= id

self.email= em


defupdateEmail(self, newem):

self.email= newem

print'Updated e-mail address for:', self.name


john= EmplAddrBookEntry('John Doe', '408-555-1212',42, '[email protected]')

printjohn.email

printjohn.empid



输出:

[email protected]

42

Traceback(most recent call last):


printjohn.name

AttributeError:'EmplAddrBookEntry' object has no attribute 'name'


  1. 如果子类定义了自己的构造函数,显示调用父类,子类和父类的属性都会被初始化


classAddrBookEntry(object):

'addressbook entry class'

def__init__(self, nm, ph):

self.name= nm

self.phone= ph

print'Created instance for:', self.name

defupdatePhone(self, newph):

self.phone = newph

print'Updated phone# for:', self.name



classEmplAddrBookEntry(AddrBookEntry):

'EmployeeAddress Book Entry class'

def__init__(self, nm, ph, id, em):

AddrBookEntry.__init__(self, nm,ph)

self.empid= id

self.email= em


defupdateEmail(self, newem):

self.email= newem

print'Updated e-mail address for:', self.name


john= EmplAddrBookEntry('John Doe', '408-555-1212',42, '[email protected]')

printjohn.email

printjohn.empid

printjohn.name

  • MRO

MRO:即methodresolutionorder.简单的说就是python针对多继承查找一个属性或者方法的一种算法。在引入新型类之前,MRO比较简单,采取自左到右,深度优先的原则。比如有如下关系的类和属性:


要查找对象xattr属性,其根据自左到右,深度优先的原则,其搜索顺序为D,B,A,C,位于树结构底层的节点具有较高的level,当从高的level向低的level查找的时候遇到第一个属性则不再继续查找,因此上面的例子x的属性值为1.


>>> classA: attr=1


>>> classB(A):pass


>>> classC(A):attr=2

>>> classD(B,C):pass

>>> x=D()

>>> printx.attr

1

>>>

但按照多继承的理解,level高的属性应该覆盖了level低的属性,D同时继承于B,C,而CA的子类,那么D的实例的属性值理应为attr=2而不是1,产生这个问题的主要原因是在继承关系中产生了菱形,针对经典类的MRO算法有一定的局限性,特别是在python2.2中加入了新型类后,由于object是所有对象之母,很容易形成菱形。因此python2.2采用改进的C3MRO算法进行搜索。

算法描述:

假设C1C2..CN表示类节点[C1,C2,...CN)

head=C1

tail=C2...CN

C+(C1 C2..CN)=C C1C2...CN

c继承于B1B2..BN,那么在节点C的搜索顺序L[C]=C加上其所有父节点的搜索顺序和各个父节点的列表之和,也即

L[C(B1, ... , BN)]= C + merge(L[B1], ... ,L[BN], B1 ... BN)

其中merge的计算方法为:

如果B1不在其它列表的tail中,则将其并入C的搜索列表中,同时将其从merge列表中移除,否则跳过改节点,继续B2.。。如此重复知道merge为空。

如果Cobject对象或者没有其他的父节点,则L[object]= object.

对于单继承,则L[C(B)]= C + merge(L[B],B) = C + L[B]


假设有如下继承关系:



则:

L[O]= O

L[D]= D O

L[E]= E O

L[F]= F O


L[B]= B + merge(DO, EO, DE)

= B+D+merge(O,EO,E)

=B+D+merge(O,EO,E)

=B+D+E+merge(O,O)

=B D E O



L[A]= A + merge(BDEO,CDFO,BC)

=A + B + merge(DEO,CDFO,C)

=A + B + C + merge(DEO,DFO)

=A + B + C + D + merge(EO,FO)

=A + B + C + D + E + merge(O,FO)

=A + B + C + D + E + F + merge(O,O)

=A B C D E F O

针对上面的计算方法,利用Pythonmro函数也可以说明该搜索顺序:


>>>class F(object):pass


>>>class E(object):pass

>>>class D(object):pass

>>>class C(D,F):pass

>>>class B(D,E):pass

>>>class A(B,C): pass

>>>A.mro()

[, , , , , , ]

>>>B.mro()

[, , , ]

>>>


对于C3MRO算法也可以简单的理解为:深度优先,从左到右遍历基类,先遍历高level的,再遍历低level的,如果任何类在搜索中是重复的,只有最后一个出现的位置被保留,其余会从MROlist中删除。也就是说类的共同的祖先只有在其所有的子类都已经被check之后才会check。对于A,其搜索顺序应该是AB (D) (O) C D (O) E (O) F O


当然即使C3MRO,也有其无法处理的情况,看下面的例子:

>>>class X(object):pass


>>>class Y(object):pass


>>>class A(X,Y):pass


>>>class B(Y,X):pass


>>>class C(A,B):

pass



Traceback(most recent call last):

File"", line 1, in

classC(A,B):

TypeError:Error when calling the metaclass bases

Cannotcreate a consistent method resolution

order(MRO) for bases X, Y