Python面向对象

引言

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

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

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

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

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

  • 标识(identity):每一个对象的实例都须要一个能够惟一标识这个实例的标记。数据结构

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

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

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

  • 类方法(classmethod):那些无须特定的对性实例就可以工做的从属于类的函数。.net

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"<pyshell#5>", line 1, in <module>

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"<pyshell#5>", line 1, in <module>

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, 'john@spam.doe')

printjohn.email

printjohn.empid



输出:

john@spam.doe

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, 'john@spam.doe')

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()

[<class'__main__.A'>, <class '__main__.B'>, <class'__main__.C'>, <class '__main__.D'>, <class'__main__.E'>, <class '__main__.F'>, <type 'object'>]

>>>B.mro()

[<class'__main__.B'>, <class '__main__.D'>, <class'__main__.E'>, <type 'object'>]

>>>


对于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"<pyshell#124>", line 1, in <module>

classC(A,B):

TypeError:Error when calling the metaclass bases

Cannotcreate a consistent method resolution

order(MRO) for bases X, Y

相关文章
相关标签/搜索