day7
Python 类与对象
引言
提到面向对象,总是离不开几个重要的术语:多态(Polymorphism),继承(Inheritance)和封装(Encapsulation)。Python也是一种支持OOP的动态语言,本文将简单阐述Python对面向对象的支持。
在讨论Python的OOP之前,先看几个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"<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的访问控制相对简单,没有public,private,protected等属性,python认为用户在访问对象的属性的时候是明确自己在做什么的,因此认为私有数据不是必须的,但是如果你必须实现数据隐藏,也是可以的,具体方法就是在变量名前加双下划线。如__privatedata=0,定义私有方法则是在方法名称前加上__下划线。但即使对于隐藏的数据,也是有一定的方法可以访问的。方法就是__className__attrName。Python对于私有变量会进行Namemangling是Python中为了方便定义私有的变量和方法,防止和继承类以及其他外部的变量或者方法冲突而采取的一种机制。在python中通过__spam定义的私有变量为最终被翻译成_classname__spam,其中classname为类名,当类名是以_开头的时候则不会发生Namemangling。Namemangling 存在的一个问题是当字符串长度超过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
如果想调用父类B的sayhi方法则需要使用B.sayhi(d).而在python引入新式类后,在继承关系中,方法和属性的搜索有所改变,使用C3算法。具体将在MRO中详细讨论。
关于继承的构造函数:
-
如果子类没有定义自己的构造函数,父类的构造函数会被默认调用,但是此时如果要实例化子类的对象,则只能传入父类的构造函数对应的参数,否则会出错
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
-
如果子类定义了自己的构造函数,而没有显示调用父类的构造函数,则父类的属性不会被初始化
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'
-
如果子类定义了自己的构造函数,显示调用父类,子类和父类的属性都会被初始化
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比较简单,采取自左到右,深度优先的原则。比如有如下关系的类和属性:
要查找对象x的attr属性,其根据自左到右,深度优先的原则,其搜索顺序为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,而C是A的子类,那么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继承于B1,B2..BN,那么在节点C的搜索顺序L[C]=C加上其所有父节点的搜索顺序和各个父节点的列表之和,也即
L[C(B1, ... , BN)]= C + merge(L[B1], ... ,L[BN], B1 ... BN)
其中merge的计算方法为:
如果B1不在其它列表的tail中,则将其并入C的搜索列表中,同时将其从merge列表中移除,否则跳过改节点,继续B2.。。如此重复知道merge为空。
如果C是object对象或者没有其他的父节点,则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
针对上面的计算方法,利用Python的mro函数也可以说明该搜索顺序:
>>>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'>]
>>>
对于C3的MRO算法也可以简单的理解为:深度优先,从左到右遍历基类,先遍历高level的,再遍历低level的,如果任何类在搜索中是重复的,只有最后一个出现的位置被保留,其余会从MROlist中删除。也就是说类的共同的祖先只有在其所有的子类都已经被check之后才会check。对于A,其搜索顺序应该是AB (D) (O) C D (O) E (O) F O
当然即使C3的MRO,也有其无法处理的情况,看下面的例子:
>>>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
4.新型类与元类
对于新型类来说,其默认的元类为type,而对于传统的类,其默认类型为types.ClassType
>>>class Classic: pass
>>>class Newstyle(object): pass
>>>print type(Classic)
<type'classobj'>
>>>print type(Newstyle)
<type 'type'>
比如>>>class TypeTestClass:
pass
>>>TypeTestClass =type('TypeTestClass',(),{})
>>>print TypeTestClass
<class'__main__.TypeTestClass'>
>>>print TypeTestClass()
<__main__.TypeTestClassobject at 0x011F3A30>
>>>
新型类是在Python2.2中引入的,其在语法和行为上基本和经典类兼容,主要差别在于所有的新式类必须继承至少一个父类,Object是所有类之母,如果类没有继承任何其他父类,则object将作为默认的父类,新型类还支持从内置类型如list,dict, file等创建子类。
新型类的实例在具有传统类实例的特性,但在__init__的基础上加入的新的构造函数__new__,同时支持静态方法@staticmethod和类方法@classmethod.(上面的章节已经阐述),同时增加了Property和__slot__,__getattribute_ _等属性,。Python对Property的定义如下Aproperty is an attribute that is defined by get/set methods.其对应的内建函数有四个参数:property(fget=None,fset=None, fdel=None,doc=None),其中fget,fset,fdel必须有一个方法被申明,否则进行对应的操作会产生AttributeError异常。如果只需要定义只读属性,则只需要实现fget方法,而不实现fset方法即可。
-
>>>class PropertyTest(object):
-
def__setProperty(self,value):
-
self.__property=value
-
print"setting property"
-
def__getProperty(self):
-
print"getting property"
-
returnself.__property
-
def__delProperty(self):
-
print"del proerty"
-
delself.__property
-
TestProperty=property(fget=__getProperty,fset=__setProperty,fdel=__delProperty,doc="propertytest")
-
-
>>>
-
>>>p=PropertyTest()
-
>>>p.TestProperty=1
-
settingproperty
-
>>>
-
>>>p.TestProperty
-
gettingproperty
-
1
-
>>>del p.TestProperty
-
delproerty
Property提供灵活的机制来读取、编写或计算私有字段的值。可以像使用公共数据成员一样使用属性,但实际上它们是称作“访问器”的特殊方法。这使得可以轻松访问数据,此外还有助于提高方法的安全性和灵活性。
__slots__类属性:
在Python中可以用__dict__属性来跟踪所有实例属性,而事实上__dict__会占用大量的内存,从Python2.2开始可以用类变量__slots__代替__dict__.,它是一个由具有合法标识的实例属性构成的集合。在定义了__slots__属性的类中不会在存在__dict__,因此可以节约内存,同时它能防止动态增加实例属性,从某种程度上讲更为安全。
>>>class SlotTest(object):
__slots__=('name','age')
>>>class Test(object):
pass
>>>s=SlotTest()
>>>s.name="carol"
>>>s.age="12"
>>>s.score="64"
Traceback(most recent call last):
File"<pyshell#49>", line 1, in <module>
s.score="64"
AttributeError:'SlotTest' object has no attribute 'score'
>>>
>>>dir(s)
['__class__','__delattr__', '__doc__', '__format__', '__getattribute__','__hash__', '__init__', '__module__', '__new__', '__reduce__','__reduce_ex__', '__repr__', '__setattr__', '__sizeof__','__slots__', '__str__', '__subclasshook__', 'age', 'name']
>>>
>>>s1=test()
>>>s1.score="65"
>>>dir(s1)
['__class__','__delattr__', '__dict__', '__doc__', '__format__','__getattribute__', '__hash__', '__init__', '__module__', '__new__','__reduce__', '__reduce_ex__', '__repr__', '__setattr__','__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'score']
>>>
元类:
对于万物皆是对象的Python,对于定义类的一段代码其本身也是对象,那么这个类对象的类型就是元类,它用来描述类的类。在元类用于创建类的时候,解释器先查找__metaclass__属性,该属性的值便是类的元类,如果没有找到该属性的定义,则会查找其父类的__metaclass__.如果仍然没有找到,对于新型类则会以type(object)作为其元类,如果当前模块有全局变量名为metaclass,则将其值作为其元类,而对于传统的类,其元类类型为types.ClassType.
可以有多种方法来创建一个元类,如利用type函数,类工厂模式,或者设置__metaclass__属性等
1利用传统工厂函数返回类:
>>>def class_creator(func):
classinternal:pass
setattr(internal,func.__name__,func)
returninternal
>>>def alive(self):print 'Hi,I am here'
>>>ChildClass = class_creator(alive)
>>>cc=ChildClass()
>>>cc.alive()
Hi,Iam here
>>>
2通过type创建类
>>>def __init__(self):
self.message='metaclass test'
>>>def alive(self):
printself.message
>>>attrs={'__init__':__init__,'alive':alive}
>>>bases=()
>>>
>>>ClassTep=type('MetaTestClass',bases,attrs)
>>>
>>>t=ClassTep()
>>>t.alive()
metaclasstest
>>>
3设置__metaclass__属性:只要在类定义中把__metaclass__设置为任意有着与type相同参数的可调用对象,就能够提供自定义的元类。通常继承type
>>>class Meta(type):
def__init__(cls,name,bases,attrs):
print"I am a meta class templete,Class will be created by me"
super(Meta,cls).__init__(name,bases,attrs)
defWelcome(cls): print "welcome",cls.__name__
>>>class MetaTest(object):
__metaclass__=Meta
defsayhi(self):
print"Hi"
Iam a meta class templete,Class will be created by me
>>>MetaTest.Welcome()
welcomeMetaTest
>>>MetaTest().sayhi()
Hi
4利用new模块中的类工厂:
new.classobj(name,baseclasses, dict)
Thisfunction returns a new class object, with name name, derived frombaseclasses (which should be a tuple of classes) and with namespacedict.
>>>from new import classobj
>>>metatest=classobj('Meta',(object,),{'Hello':lambda self:'hello'})
>>>metatest().Hello()
'hello'
>>>
在元类中也可以定义类方法,一般叫做元方法,元方法和普通的类方法在使用上存在一定的区别,元方法能够被元类或者元类对象(类)直接调用,但不能没类的实例调用,而类方法可以被类或者类的实例直接调用。
>>>class Meta(type):
def__init__(cls,name,bases,attrs):
super(Meta,cls).__init__(name,bases,attrs)
defWelcome(cls): print "welcome",cls.__name__#元方法
>>>class MetaTest(object):
__metaclass__=Meta
defsayhi(self):
print"Hi"
defgetName(cls):#类方法
print"what is your name"
>>>d=MetaTest()
>>>Meta.Welcome(MetaTest)
welcomeMetaTest
>>>MetaTest.Welcome()
welcomeMetaTest
>>>d.getName()
whatis your name
>>>MetaTest.getName(d)
whatis your name
>>>d.Welcome()
Traceback(most recent call last):
File"<pyshell#9>", line 1, in <module>
d.Welcome()
AttributeError:'MetaTest' object has no attribute 'Welcome'
>>>
在继承关系中,元类和普通的超类存在一定的区别,元类属性的可用性是不会传递的,也就是说,元类的属性是对它的实例是可用的,但是对它的实例的实例是不可用的。
>>>class SuperC(object):
attr='name','age'
>>>class Child(SuperC):
pass
>>>Child.attr
('name','age')
>>>
>>>a=Child()
>>>a.attr
('name','age')
>>>
>>>class SuperMeta(type):
attr=('name','age')
>>>class ChildMeta(object):
__metaclass__=SuperMeta
>>>class Child2Meta(ChildMeta):pass
>>>SuperMeta.attr
('name','age')
>>>ChildMeta.attr
('name','age')
>>>Child2Meta.attr
('name','age')
>>>b=Child2Meta()
>>>b.attr
Traceback(most recent call last):
File"<pyshell#19>", line 1, in <module>
b.attr
AttributeError:'Child2Meta' object has no attribute 'attr'
在元类的多继承中,通常会产生冲突,比如A,B都是带有元类的类,C多继承于A和B时会产生冲突。如下例:
>>>class MetaA(type):pass
>>>class MetaB(type):pass
>>>class A(object):
__metaclass__=MetaA
>>>class B(object):
__metaclass__=MetaB
>>>class C(A,B):pass
Traceback(most recent call last):
File"<pyshell#42>", line 1, in <module>
classC(A,B):pass
TypeError:Error when calling the metaclass bases
metaclassconflict: the metaclass of a derived class must be a (non-strict)subclass of the metaclasses of all its bases
>>>
解决冲突的方法从利用type重新定义一个中间的元类。
AB=type("AB",(MetaA,MetaB),{})
classC(A,B):__metaclass__=AB