一、简介
类最终解释了面向对象编程思想(OOP),Python中有关派生或子类化及继承机理都比较重要。Python也可以在特定功能方面定制类,例如重载操作符、模拟Python类型等。
二、详解
1、面向对象编程主题
(1)类与实例
类与实例相互关联着:类是对象的定义;而实例是"真正的实物",它存放了类中所定义的对象的具体信息。
定义类:关键字是class,紧接着是一个类名,随后是定义类的类体代码,可以单继承或多继承。object是“所有类之母”,如果类没有继承任何其他父类,object 将作为默认的父类,它位于所有类继承结构的最上层,如果没有直接或间接的子类化一个对象,那就定义了一个经典类。
创建一个实例的过程称作实例化,过程:myFirstObject = MyNewObjectType(),注意没有使用new关键字,以“函数调用”的形式出现,然后把这个新建的实例赋给一个变量(如果没有把这个实例保存到一个变量中,它就没用了,会被自动垃圾收集器回收,因为任何引用指向这个实例)。尽管每个实例都有一个基本的结构,但各自的属性像颜色或尺寸可以改变,这就好比实例的属性。
>>> class MyData(object):
... pass
...
>>> obj=MyData()
>>> obj.x=4
>>> obj.y=5
>>> obj.x*obj.y
20
定义的类没有任何方法或属性,创建了一个实例。它只使用类作为名称空间容器,实例名字obj将obj.x和obj.y
关联起来,obj.x和obj.y是实例属性是实例对象obj的独有属性,而不是类MyData的属性。
实例属性实质上是动态的,不需要在构造器中或其它任何地方为它们预先声明或者赋值。
(2)方法
在Python中,方法定义在类定义中,但只能被实例所调用,即,调用一个方法的最终途径必须是:先定义类(和方法)、然后创建一个实例、最后用这个实例调用方法。
在其它语言中,self 称为“this”。一般的方法会需要这个实例(self),而静态方法或类方法不会,其中类方法需要类而不是实例。
特殊的方法__init__(),它类似于类构造器,它在创建一个新的对象时被调用。 在Python中,__init__()实际上不是一个构造器,因为没有调用“new”来创建一个新对象( Python无new 关键字)。取而代之,Python创建实例后,在实例化过程中调用__init__()方法,当一个类被实例化时,就可以定义额外的行为,比如设定初始值或者运行一些初步诊断代码。主要是在实例被创建后,实例化调用返回这个实例之前,去执行某些特定的任务或设置。
(3)创建一个类(类定义)
>>> class AddrBookEntry(object): #类定义
... 'address book entry class'
... def __init__(self, nm, ph): #定义构造器
... self.name = nm #设置 name
... self.phone = ph #设置phone
... print 'Created instance for:', self.name
... def updatePhone(self, newph): #定义方法
... self.phone = newph
... print 'Updated phone# for:', self.name
...
>>>
在AddrBookEntry类的定义中,定义了两个方法:__init__()和updatePhone()__init__()在实例化时被调用,即在 AddrBookEntry()被调用时。可以认为实例化是对__init__()的一种隐式的调用,因为传给AddrBookEntry()的参数完全与__init__()接收到的参数是一样的(除了self是自动传递的)。
(4)创建实例(实例化)
>>> john = AddrBookEntry('John Doe', '408-555-1212') #为 John Doe 创建实例
Created instance for: John Doe
>>> jane = AddrBookEntry('Jane Doe', '650-555-1212') #为 Jane Doe 创建实例
Created instance for: Jane Doe
实例化调用,它会自动调用__init__(),self 把实例对象自动传入__init__()。可以把方法中的self用实例名替换掉。当对象 john 被实例化后,它的john.name 就被设置了。另外,如果不存在默认的参数,那么传给__init__()的两个参数在实例化时是必须的。
(5)访问实例属性
一旦实例被创建后,实例化过程中实例属性即会被__init__()设置。
(6)方法调用(通过实例)
>>> john.updatePhone('415-555-1212') #更新John Doe的电话
Updated phone# for: John Doe
>>> john.phone
'415-555-1212'
(7)创建子类
靠继承来进行子类化是创建和定制新类类型的一种方式,新的类将保持已存在类所有的特性,而不会改动原来类的定义。对于新的类类型来说,这个新的子类可以定制只属于它的特定功能。除了与父类或基类的关系外,子类与通常的类没有什么区别,也像一般类一样进行实例化。
>>> class EmplAddrBookEntry(AddrBookEntry):
... 'Employee Address Book Entry class'#员工地址本类
... def __init__(self, nm, ph, id, em):
... AddrBookEntry.__init__(self, nm, ph)
... self.empid = id
... self.email = em
... def updateEmail(self, newem):
... self.email = newem
... print 'Updated e-mail address for:', self.name
...
创建子类EmplAddrBookEntry,当一个类被派生出来,子类继承了基类的属性,子类中不仅定义了__init__()、updatEmail()方法,而且还从AddrBookEntry中继承了updatePhone()方法。
如果需要,每个子类最好定义它自己的构造器,不然基类的构造器会被调用。然而,如果子类重写基类的构造器,基类的构造器就不会被自动调用了。这样,基类的构造器就必须显式写出才会被执行,用AddrBookEntry.__init__()设置名字和电话号码。子类在
构造器后面还设置了另外两个实例属性:员工ID和E-mail地址。
注意,这里要显式传递self实例对象给基类构造器,因为不是在其实例中调用那个方法而是在一个子类实例中调用那个方法。不是通过实例来调用它,这种未绑定的方法调用需要传递一个适当的实例(self)给方法。
(8)使用子类
>>> john = EmplAddrBookEntry('John Doe', '408-555-1212',42, 'john@spam.doe')
Created instance for: John Doe
>>> john.email
'john@spam.doe'
>>> john.updatePhone('415-555-1212')
Updated phone# for: John Doe
2、面向对象编程简介
结构化的或过程性的编程可以让我们把程序组织成逻辑块,以便重复或重用。面向对象编程踩上了进化的步伐,增强了结构化编程,实现了数据与动作的融合,数据层和逻辑层现在由一个可用以创建这些对象的简单抽象层来描述。现实世界中的问题和实体完全暴露了本质,从中提供的一种抽象,可以用来进行相似编码或者编入能与系统中对象进行交互的对象中。类提供了这样一些对象的定义,实例即是这些定义的实现。(1)面向对象设计与面向对象编程的关系
面向对象设计(OOD)不会特别要求面向对象编程语言。OOD 可以由纯结构化语言来实现,比如 C。但如果想要构造具备对象性质和特点的数据类型,就需要在程序上作更多的努力。当一门语言内建OO特性,OO编程开发就会更加方便高效。另一方面,一门面向对象的语言不一定会强制你写OO方面的程序。例如 C++可以被认为“更好的C”。在Python中,类和OOP都不是日常编程所必需的。尽管它从一开始设计就是面向对象的,并且结构上支持 OOP,但Python没有限定或要求你在你的应用中写OO的代码。OOP 是一门强大的工具,不管你是准备进入学习、过渡或是转向OOP,都可以任意支配。
(2)常用术语
抽象/实现
抽象指对现实世界问题和实体的本质表现、行为和特征建模,建立一个相关的子集,可以用于描绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户程序应当是透明而且无关的。
封装/接口
封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。
合成
合成扩充了对类的描述,使得多个不同的类合成为一个大的类,来解决现实问题。合成描述了一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为,所有这些合在一起彼此是“有一个”的关系。组件要么通过联合关系组在一块,对子组件的访问是允许的;要么是聚合在一起,封装的组件仅能通过定义好的接口来访问,对于客户程序来说是透明的。Python支持上述两种形式的合成。
派生/继承/继承结构
派生描述了子类的创建,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。继承描述了子类属性从祖先类继承这样一种方式。继承结构表示多“代”派生,可以描述成一个“族谱”,连续的子类与祖先类都有关系。
泛化/特化
泛化表示所有子类与其父类及祖先类有一样的特点,所以子类可以认为同祖先类是“是一个”的关系,因为一个派生对象(实例)是祖先类的一个“例子”。特化描述所有子类的自定义,也就是什么属性让它与其祖先类不同。
多态
多态的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。多态表明了动态(又名运行时)绑定的存在,允计重载及运行时类型确定和验证。
自省/反射
自省表示给予程序员某种能力来进行像“手工类型检查”的工作,它也被称为反射。这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir()和 type()内建函数,将很难正常工作。还有那些特殊属性,像__dict__、__name__及__doc__。
3、类
类是一种数据结构,我们可以用它来定义对象,对象把数据值和行为特性融合在一起。类是现实世界的抽象的实体以编程形式出现,实例是这些对象的具体化。类还可以派生出相似但有差异的子类,编程中类的概念就应用了很多这样的特征。在Python中,类声明与函数声明很相似,头一行用一个相应的关键字class,接下来是一个作为它的定义的代码体。
class ClassName(bases):
'class documentation string' #'类文档字符串'
class_suite #类体
基类是一个或多个用于继承的父类的集合;类体由所有声明语句、类成员定义、数据属性和函数组成。类通常在一个模块的顶层进行定义,以便类实例能够在类所定义的源代码文件中的任何地方被创建。
函数体和类体都允许你在他们的声明中创建函数、闭包或者内部函数(即函数内的函数),还有在类中定义的方法。最大的不同在于运行函数而类会创建一个对象。类就像一个Python容器类型。尽管类是对象(在Python中,一切皆对象),但正被定义时,它们还不是对象的实现。当创建一个类,实际创建了一个自己的数据类型,所以这个类的实例都是相似的,但类之间彼此是有区别的,因此,不同类的实例自然也不可能相同了。可以创建一个子类,它也是类,而且继续了父类所有的特征和属性。
对于Python函数来说,声明与定义类没什么区别,因为他们是同时进行的,定义(类体)紧跟在声明(含class关键字的头行[header line])和可选的文档字符串后面。同时,所有的方法也必须同时被定义。如果对OOP很熟悉,请注意Python并不支持纯虚函数(像 C++),这些都强制程序员在子类中定义方法。
4、类的属性
属性就是属于一个对象的数据或者函数元素,可以通过句点属性标识法来访问。一些Python类型比如复数有数据属性(实部和虚部),而另外一些,像列表和字典,拥有方法(函数属性)。当正访问一个属性时,它同时也是一个对象,拥有它自己的属性,可以访问,这导致了一个属性链。
类的属性仅与其被定义的类相绑定,并且因为实例对象在日常OOP中用得最多,实例数据属性是将会一直用到的主要数据属性。类数据属性仅当需要有更加“静态”数据类型时才变得有用,它和任何实例都无关。通常Python中的所有方法都有一个限制:在调用前,需要创建一个实例。
(1)类的数据属性
数据属性仅仅是所定义的类的变量。它们可以像任何其它变量一样在类创建后被使用,并且要么是由类中的方法来更新,要么是在主程序其它什么地方被更新。
这种属性已为OO程序员所熟悉,即静态变量或者是静态数据。它们表示这些数据是与它们所属的类对象绑定的,不依赖于任何类实例。如果你是一位 Java 或 C++程序员,这种类型的数据相当于在一个变量声明前加上static关键字。
大多数情况下,会采用实例属性而不是类属性, 类属性(静态成员)通常仅用来跟踪与类相关的值。
>>> class C(object):
... foo = 100
...
>>> print C.foo
100
>>> C.foo = C.foo + 1
>>> print C.foo
101
>>>
(2)方法
方法,比如类MyClass中的myNoActionMethod方法,仅仅是一个作为类定义一部分定义的函数,这使得方法成为类的属性。 这表示myNoActionMethod仅应用在MyClass类型的对象 (实例)上。myNoActionMethod是通过句点属性标识法与它的实例绑定的。
绑定(绑定及非绑定方法),为与OOP惯例保持一致,Python严格要求:没有实例,方法是不能被调用的。这种限制即Python所描述的绑定概念(binding),即方法必须绑定(到一个实例)才能直接被调用。非绑定的方法可能可以被调用,但实例对象一定要明确给出,才能确保调用成功。然而,不管是否绑定,方法都是它所在的类的固有属性,即使它们几乎总是通过实例来调用的。
(3)决定类的属性
要知道一个类有哪些属性,有两种方法:最简单的是使用dir()内建函数,另外是通过访问类的字典属性__dict__,这是所有类都具备的特殊属性之一。
>>> class MyClass(object):
... 'MyClass class definition' #MyClass类定义
... myVersion = '1.1' #static data静态数据
... def showMyVersion(self): #method方法
... print MyClass.myVersion
...
>>> dir(MyClass)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', \
'__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', \
'__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'myVersion', 'showMyVersion']
>>> MyClass.__dict__
<dictproxy object at 0x7f77b149e8a0>
>>> print MyClass.__dict__
{'__module__': '__main__', 'showMyVersion': <function showMyVersion at 0x7f77b148bc08>, \
'__dict__': <attribute '__dict__' of 'MyClass' objects>, 'myVersion': '1.1', \
'__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': 'MyClass class definition'}
dir()返回的仅是对象的属性的一个名字列表,而__dict__返回的是一个字典,它的键(keys)是属性名,键值(values)是相应的属性对象的数据值。MyClass中两个熟悉的属性:showMyVersion和myVersion,以及一些新的属性。这些属性__doc__和__module__是所有类都具备的特殊类属性(另外还有__dict__)。内建的vars()函数接受类对象作为参数,返回类的__dict__属性的内容。
(4)特殊的类属性
类C的所有特殊属性:
>>> MyClass.__name__
'MyClass'
>>> MyClass.__doc__
'MyClass class definition'
>>> MyClass.__bases__
(<type 'object'>,)
>>> MyClass.__module__
'__main__'
>>> MyClass.__class__
<type 'type'>
__name__是给定类的字符名字,它适用于那种只需要字符串(类对象的名字),而非类对象本身的情况。type()是一个内建类型,它返回被调用对象的类型,可以使用类型对象的__name__属性来取得相应的字符串名。_doc__是类的文档字符串,与函数及模块的文档字符串相似,必须紧随头行(header line)后的字符串,文档字符串不能被派生类继承,也就是说派生类必须含有它们自己的文档字符串。_bases__用来处理继承,它包含了一个由所有父类组成的元组。__dict__属性包含一个字典,由类的数据属性组成。Python支持模块间的类继承,__module__这样类名就完全由模块名所限定,类C的全名是“__main__.C”,如果类C位于一个导入的模块中,如from mymod import C,C.__module__为'mymod'
由于类型和类的统一性,当访问任何类的__class__属性时,将发现它就是一个类型对象的实例。换句话说,一个类已是一种类型了。因为经典类并不认同这种等价性(一个经典类是一个类对象,一个类型是一个类型对象),对这些对象来说,这个属性并未定义。
5、实例
如果说类是一种数据结构定义类型,那么实例则声明了一个这种类型的变量。实例是那些主要用在运行期时的对象,类被实例化得到实例,该实例的类型就是这个被实例化的类。(1)初始化:通过调用类对象来创建实例
Python类的实例化实现,可以使用函数操作符。当使用函数记法来调用("call")一个类时,解释器就会实例化该对象,并且调用Python所拥有与构造函数最相近的东西来执行最终的定制工作,比如设置实例属性,最后将这个实例返回给你。
(2)__init__()"构造器"方法
当类被调用,实例化的第一步是创建实例对象。一旦对象创建了,Python检查是否实现了__init__()方法。默认情况下,如果没有定义(或覆盖)特殊方法__init__(),对实例不会施加任何特别的操作。任何所需的特定操作都需要程序员实现__init__(),覆盖它的默认行为。如果__init__()没有实现,则返回它的对象,实例化过程完毕。
如果__init__()已经被实现,那么它将被调用,实例对象作为第一个参数(self)被传递进去,像标准方法调用一样。调用类时,传进的任何参数都交给了__init__()。
__init__()是在解释器创建一个实例后调用的第一个方法。__init__()是很多为类定义的特殊方法之一,其中一些特殊方法是预定义的,缺省情况下不进行任何操作,比如__init__(),要定制就必须对它进行重载,还有些方法可能要按需要去实现。
(3)__new__()“构造器”方法
与__init__()相比,__new__()方法更像一个真正的构造器。Python用户可以对内建类型进行派生,因此需要一种途径来实例化不可变对象,比如派生字符串、数字等等。在这种情况下,解释器则调用类的__new__()方法,一个静态方法,并且传入的参数是在类实例化操作时生成的。__new__()会调用父类的__new__()来创建对象(向上代理)。
__new__()比__init__()更像构造器,这是因为__new__()必须返回一个合法的实例,这样解释器在调用__init__()时,就可以把这个实例作为 self 传给它。调用父类的__new__()来创建对象,正像其它语言中使用 new 关键字一样。__new__()和__init__()在类创建时,都传入了(相同)参数。
利用这个方法和类属性的特性可以实现设计模式中的单例模式(指创建唯一对象,单例模式设计的类只能实例化一个对象)。
>>> class Singleton(object):
... __instance = None
... def __init__(self):
... pass
... def __new__(cls, *args, **kwd):
... if Singleton.__instance is None:
... Singleton.__instance = object.__new__(cls, *args, **kwd)
... print "create instance"
... return Singleton.__instance
...
>>> first = Singleton()
create instance
>>> second = Singleton()
>>> first
<__main__.Singleton object at 0x7fb470f76850>
>>> second
<__main__.Singleton object at 0x7fb470f76850>
(4)__del__()"解构器"方法有一个相应的特殊解构器(destructor)方法名为__del__()。然而,由于Python具有垃圾对象回收机制(靠引用计数),这个函数要直到该实例对象所有的引用都被清除掉后才会执行。Python中的解构器是在实例释放前提供特殊处理功能的方法,它们通常没有被实现,因为实例很少被显式释放。
解构器只能被调用一次,一旦引用计数为 0,则对象就被清除了,因为系统中任何对象都只被分配及解构一次。
总结:a、不要忘记首先调用父类的__del__()。b、调用del x不表示调用了 x.__del__(),它仅仅是减少 x 的引用计数。c、如果有一个循环引用或其它的原因,让一个实例的引用逗留不去,该对象的__del__()可能永远不会被执行。d、__del__()未捕获的异常会被忽略掉(因为一些在__del__()用到的变量或许已经被删除了),不要在__del__()中干与实例没任何关系的事情。e、除非你知道你正在干什么,否则不要去实现__del__()。f、如果你定义了__del__,并且实例是某个循环的一部分,垃圾回收器将不会终止这个循环,需要自已显式调用 del。
Python没有提供任何内部机制来跟踪一个类有多少个实例被创建了,或者记录这些实例是些什么东西。如果需要这些功能,你可以显式加入一些代码到类定义或者__init__()和__del__()中去。最好的方式是使用一个静态成员来记录实例的个数,靠保存它们的引用来跟踪实例对象是很危险的,因为你必须合理管理这些引用,不然,你的引用可能没办法释放。
>>> class InstCt(object):
... count = 0 # count is class attr count 是一个类属性
... def __init__(self): # increment count 增加 count
... InstCt.count += 1
... def __del__(self): # decrement count 减少 count
... InstCt.count -= 1
... def howMany(self): # return count 返回 count
... return InstCt.count
...
>>> a = InstCt()
>>> b = InstCt()
>>> b.howMany()
2
>>> del b
>>> a.howMany
<bound method InstCt.howMany of <__main__.InstCt object at 0x7f23f0b0f650>>
>>> a.howMany()
1
>>> del a
>>> InstCt.count
0
6、实例属性
实例仅拥有数据属性(方法严格来说是类属性), 数据属性只是与某个类的实例相关联的数据值,并且可以通过句点属性标识法来访问,这些值独立于其它实例或类。当一个实例被释放后,它的属性同时也被清除了。(1)“实例化”实例属性(或创建一个更好的构造器)
设置实例的属性可以在实例创建后任意时间进行,也可以在能够访问实例的代码中进行。构造器__init()__是设置这些属性的关键点之一。能够在“运行时”创建实例属性,是 Python 类的优秀特性之一。Python不仅是动态类型,而且在运行时,允许这些对象属性的动态创建。一个缺陷是,属性在条件语句中创建,如果该条件语句块并未被执行,属性也就不存在;而在后面的代码中试着访问这些属性,就会有错误发生;Python让你体验从未用过的特性,但如果你使用它了,还是要小心为好。
构造器是最早可以设置实例属性的地方,因为__init__()是实例创建后第一个被调用的方法。再没有比这更早的可以设置实例属性的机会了。一旦__init__()执行完毕,返回实例对象,即完成了实例化过程。
带默认参数的__init__()提供一个有效的方式来初始化实例。在很多情况下,默认值表示设置实例属性的最常见的情况,如果提供了默认值,就没必要显式给构造器传值了。默认参数应当是不变的对象,像列表(list)和字典(dictionary)这样的可变对象就不用设置成默认参数,可以定义为静态数据,然后在每个方法调用中来维护它们的内容。
__init__()应当返回None,采用函数操作符调用类对象会创建一个类实例,也就是说这样一种调用过程返回的对象就是实例。如果定义了构造器,它不应当返回任何对象,因为实例对象是自动在实例化调用后返回的。相应地__init__()就不应当返回任何对象(应当为 None);否则,就可能出现冲突,因为只能返回实例;试着返回非None的任何其它对象都会导致TypeError异常。
(2) 查看实例属性
内建函数dir()可以显示类属性,同样还可以打印所有实例属性。实例也有一个__dict__特殊属性(可以调用 vars()并传入一个实例来获取),它是实例属性构成的一个字典。
实例仅有两个特殊属性,对于任何对象m:m.__class__(实例化m的类)和m.__dict__(m的属性)。__dict__属性由一个字典组成,包含一个实例的所有属性;键是属性名,值是属性相应的数据值;字典中仅有实例属性,没有类属性或特殊属性。
(3)内建类型属性
对内建类型也可以使用dir(),与任何其它对象一样,可以得到一个包含它属性名字的列表。查询到了一个复数有什么样的属性,就可以访问它的数据属性(访问__dict__会失败,因为在内建类型中不存在这个属性),调用它的方法了。如:
>>> x = 3+0.14j
>>> x.__class__
<type 'complex'>
>>> x.imag
0.14000000000000001
>>> x.real
3.0
>>> x.conjugate()
(3-0.14000000000000001j)
(4)实例属性 vs 类属性
类属性仅是与类相关的数据值,和实例属性不同,类属性和实例无关。这些值像静态成员那样被引用,即使在多次实例化中调用类,它们的值都保持不变。不管如何,静态成员不会因为实例而改变它们的值,除非实例中显式改变它们的值。实例属性与类属性的比较,类似于自动变量和静态变量,但这只是笼统的类推。
类和实例都是名字空间,类是类属性的名字空间,实例则是实例属性的。可采用类来访问类属性,如果实例没有同名的属性的话,也可以用实例来访问。
访问类属性:类属性可通过类或实例来访问。
>>> class C(object): #define class
... version = 1.2 #static member
...
>>> c = C() #instantiation实例化
>>> C.version #access via class
1.2
>>> c.version
1.2
>>> C.version += 0.1
>>> C.version
1.3
>>> c.version
1.3
当实例c被创建后,访问c.version, Python首先会在实例中搜索名字version,然后是类,再就是继承树中的基类,都没找到报出AttributeError异常。然而只有当使用类引用version时,才能更新它的值,像C.version递增语句。如果在实例中设定或更新类属性会创建一个实例属性c.version,后者会阻止对类属性C.versioin的访问,因为第一个访问的就是c.version,这样可以对实例有效地“遮蔽”类属性C.version,直到c.version被清除掉。
>>> c.version += 0.1
>>> c.version
1.4000000000000001
>>> C.version
1.3
从实例中访问类属性须谨慎,任何对实例属性的赋值都会创建一个实例属性(如果不存在的话)并且对其赋值。如果类属性中存在同名的属性,有趣的副作用即产生(经典类和新式类都存在)。
>>> class Foo(object):
... x = 1.5
...
>>> foo = Foo()
>>> foo.x
1.5
>>> foo.x=1.7
>>> foo.x
1.7
>>> Foo.x
1.5
>>> del foo.x # delete instance attribute
>>> foo.x # can now access class attr again
1.5
实例属性foo.x覆盖了对类属性Foo.x的引用,而类属性本身并没有受到伤害,仍然存在于类域中,还可以通过类属性来访问它。使用del语句删除
实例属性foo.x,那通过foo.x又可以访问到类属性了。所以给一个与类属性同名的实例属性赋值,会有效地“隐藏”类属性,但一旦删除了这个实例属性,类属性又重见天日。但在类属性可变(如列表和字典)的情况下,一切都不同了。
>>> class Foo(object):
... x = {2003: 'poe2'}
...
>>> foo = Foo()
>>> foo.x
{2003: 'poe2'}
>>> foo.x[2004] = 'valid path'
>>> foo.x
{2003: 'poe2', 2004: 'valid path'}
>>> Foo.x # it works
{2003: 'poe2', 2004: 'valid path'}
>>> del foo.x # no shadow so cannot delete
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object attribute 'x' is read-only
>>>
类属性(
静态成员)持久性,任凭整个实例(及其属性)的如何进展,它都独立于实例。同时,当一个实例在类属性被修改后才创建,那么更新的值就将生效。类属性的修改会影响到所有的实例。
使用实例属性来试着修改类属性是很危险的,
原因在于实例拥有它们自已的属性集,在Python中没有明确的方法来指示想要修改同名的类属性(无global关键字可以用来在一个函数中设置一个全局变),因此修改类属性需要使用类名而不使用实例名。
7、绑定和方法调用
绑定(binding)的概念,它主要与方法调用相关连。首先方法仅仅是类内部定义的函数(意味着方法是类属性而不是实例属性);其次,方法只有在其所属的类拥有实例时,才能被调用,当存在一个实例时,方法才被认为是绑定到那个实例了,没有实例时方法就是未绑定的;最后,任何一个方法定义中的第一个参数都是变量self,它表示调用此方法的实例对象。self变量用于在类实例方法中引用方法所绑定的实例,因为方法的实例在任何方法调用中总是作为第一个参数传递的,self 被选中用来代表实例。必须在方法声明中放上self,但可以在方法中不使用实例(self)。如果方法中没有用到 self,那么请考虑创建一个常规函数,除非有特别原因。在其它面向对象语言中,self可能被称为this指针。
(1)调用绑定方法
方法不管绑定与否,都是由相同的代码组成的,唯一的不同在于是否存在一个实例可以调用此方法。在很多情况下,程序员调用的都是一个绑定的方法。假定现在有一个MyClass类和此类的一个实例mc,而想调用MyClass的foo()方法,因为已经有一个实例,只需要调用mc.foo()就可以了,在实例中调用一个绑定的方法时,self不需要明确地传入了。当还没有一个实例并且需要调用一个非绑定方法的时候必须传递 self 参数。
(2)调用非绑定方法
调用非绑定方法并不经常用到,需要调用一个还没有任何实例的类中的方法的一个主要的场景是:在派生一个子类,而且要覆盖父类的方法,这时需要调用那个父类中想要覆盖掉的构造方法。
>>> class AddrBookEntry(object):
... def __init__(self, nm, ph):
... self.name = nm
... self.phone = ph
... print 'Created instance for:', self.name
...
>>> class EmplAddrBookEntry(AddrBookEntry):
... def __init__(self, nm, ph, em):
... AddrBookEntry.__init__(self, nm, ph)
... self.empid = id
... self.email = em
...
>>>
EmplAddrBookEntry中重载了构造器__init__()。尽可能多地重用代码,而不是去从父类构造器中剪切粘贴代码,这样做还可以避免BUG传播,只需要能够调用父类的构造器即可。当一个EmplAddrBookEntry被实例化,并且调用__init__()时,其父类只有很少的差别,主要是在子类中自定义一些特性。这是调用非绑定方法的最佳地方了,在子类构造器中调用父类的构造器并且明确地传递
(父类)构造器所需要的 self 参数(因为我们没有一个父类的实例),子类中__init__()的第一行就是对父类__init__()的调用。通过父类名来调用它,并且传递给它self和其他所需要的参数。一旦调用返回,就能定义那些与父类不同的仅存在子类中的实例定制。
8、静态方法和类方法
静态方法和类方法在Python2.2中引入,经典类及新式类中都可以使用它,一对内建函数被引入,用于将作为类定义的一部分的某一方法声明“标记”(tag)、“强制类型转换”(cast)或者“转换”(convert)为这两种类型的方法之一。
通常的方法需要一个实例(self)作为第一个参数,对于(绑定的)方法调用来说,self是自动传递给这个方法的。而对于类方法而言,需要类而不是实例作为第一个参数,它是由解释器传给方法。类不需要特别地命名,类似self,不过很多人使用 cls 作为变量名字。
(1)staticmethod()和classmethod()内建函数
类方法的使用和静态方法是十分相似的,如果某个方法需要被其他实例共享,同时又需要使用当前实例的属性,可将其定义为类方法;若不需要使用当前实例的属性,可定义为静态方法。在经典类中创建静态方法和类方法的一些例子:
>>> class TestStaticMethod:
... def foo():
... print 'calling static method foo()'
... foo = staticmethod(foo)
...
>>> class TestClassMethod:
... def foo(cls):
... print 'calling class method foo()'
... print 'foo() is part of class:', cls.__name__
... foo = classmethod(foo)
...
>>> tsm = TestStaticMethod()
>>> TestStaticMethod.foo()
calling static method foo()
>>> tsm.foo()
calling static method foo()
>>> tcm = TestClassMethod()
>>> tcm.foo()
calling class method foo()
foo() is part of class: TestClassMethod
>>> TestClassMethod.foo()
calling class method foo()
foo() is part of class: TestClassMethod
>>>
(2)使用函数修饰符实现
静态方法和类方法
函数修饰符是在Python2.4中加入的新特征,可以用它把一个函数应用到另个函数对象上,而且新函数对象依然绑定在原来的变量。通过使用decorators,可以避免像上面那样的重新赋值。
>>> class TestStaticMethod:
... @staticmethod
... def foo():
... print 'calling static method foo()'
...
>>> class TestClassMethod:
... @classmethod
... def foo(cls):
... print 'calling class method foo()'
... print 'foo() is part of class:', cls.__name__
...
>>>
9、组合
一个类被定义后,目标就是要把它当成一个模块来使用,并把这些对象嵌入到代码中去,同其它数据类型及逻辑执行流混合使用。有两种方法可以在代码中利用类:第一种是组合(composition),就是让不同的类混合并加入到其它类中,来增加功能和代码重用性;可以在一个大点的类中创建自已的类的实例,实现一些其它属性和方法来增强对原来的类对象。另一种方法是通过派生。>>> class NewAddrBookEntry(object):
... def __init__(self, nm, ph): # define constructor
... self.name = Name(nm) # create Name instance
... self.phone = Phone(ph)
... print 'Created instance for:', self.name
...
如果在设计的过程中,为names、addresses等等创建了单独的类。那么最后可能想把这些工作组合到AddrBookEntry类中去,而不是重新设计每一个需要的类。这样就节省了时间和精力,而且最后的结果是容易维护的代码。NewAddrBookEntry类由它自身和其它类组合而成,这就在一个类和其它组成类之间定义了一种组合关系。
创建复合对象就可以实现这些附加的功能,并且很有意义,因为这些类都不相同。每一个类管理它们自己的名字空间和行为。不过当对象之间有更接近的关系时,派生的概念可能更有意义,特别是当你需要一些相似的对象,但却有少许不同功能的时候。
10、子类和派生
当类之间有显著的不同,并且较小的类是较大的类所需要的组件时,组合表现得很好,但当设计“相同的类但有一些不同的功能”时,派生就是一个更加合理的选择了。OOP的更强大方面之一是能够使用一个已经定义好的类,扩展它或者对其进行修改,而不会影响系统中使用现存类的其它代码片段。 允许类特征在子孙类或子类中进行继承。OOD这些子类从基类(或称祖先类、超类)继承它们的核心属性。而且,这些派生可能会扩展到多代。在一个层次的派生关系中的相关类(或者是在类树图中垂直相邻)是父类和子类关系。从同一个父类派生出来的这些类
(或者是在类树图中水平相邻)是同胞关系。父类和所有高层类都被认为是祖先。
创建子类的语法看起来与普通(新式)类没有区别:一个类名,后跟一个或多个需要从其中派生的父类。
class SubClassName (ParentClass1[, ParentClass2, ...]):
'optional class documentation string'
class_suite
如果你的类没有从任何祖先类派生,可以使用object作为父类的名字。经典类的声明唯一不同之处在于其没有从祖先类派生,此时,没有圆括号。
11、继承
继承描述了基类的属性如何“遗传”给派生类,一个子类可以继承它的基类的任何属性,不管是数据属性还是方法。>>> class P: # parent class
... 'P class'
... def __init__(self):
... print 'created an instance of', self.__class__.__name__
...
>>> class C(P): # child class
... pass
...
>>> p = P() # parent instance
created an instance of P
>>> p.__class__ # class that created us
<class __main__.P at 0x7f23f0b026b0>
>>> P.__doc__ # parent's doc string
'P class'
>>> c = C() # child instance
created an instance of C
>>> c.__class__ # class that created us
<class __main__.C at 0x7f23f0b02950>
>>> P.__bases__ # parent's parent class(es)
()
>>> C.__bases__ # child's parent class(es)
(<class __main__.P at 0x7f23f0b026b0>,)
>>> C.__doc__
>>>
C没有声明__init__()方法,然而在类 C 的实例c被创建时,还是会有输出信息,原因在于C继承了P的__init__(),若C中重写
__init__()方法则不会执行父类的
__init__()。__bases__元组列出了其父类 P。需要注意的是文档字符串对类、函数/方法、还有模块来说都是唯一的,,所以特殊属性__doc__不会从基类中继承过来。
(1)__bases__类属性
对任何(子)类,它是一个包含其父类(parent)的集合的元组。“父类”是相对所有基类(它包括了所有祖先类)而言的。那些没有父类的类,它们的__bases__属性为空。
>>> class A(object): pass
...
>>> class B: pass
...
>>> class C(B): pass
...
>>> class D(A, C): pass
...
>>> A.__bases__
(<type 'object'>,)
>>> B.__bases__
()
>>> C.__bases__
(<class __main__.B at 0x7f23f0b02a10>,)
>>> D.__bases__
(<class '__main__.A'>, <class __main__.C at 0x7f23f0b029b0>)
>>>
(2)通过继承覆盖方法
>>> class P(object):
... def foo(self):
... print 'Hi, I am P-foo()'
...
>>> class C(P):
... def foo(self):
... print 'Hi, I am C-foo()'
...
>>> p = P()
>>> c = C()
>>> p.foo()
Hi, I am P-foo()
>>> c.foo()
Hi, I am C-foo()
>>> P.foo(c)
Hi, I am P-foo()
>>>
尽管C继承了P的 foo()方法,但因为C定义了它自已的 foo()方法,所以P中的foo()方法被覆盖。覆盖方法的原因之一是,子类可能需要这个方法具有特定或不同的功能。若需要去调用一个未绑定的基类方法,明确给出子类的实例:P.foo(c)。实际情况下,不需要P的实例调用P的方法,因为已经有一个P的子类的实例c可用,可以在子类的重写方法里显式地调用基类方法。
>>> class C(P):
... def foo(self):
... P.foo(self)
... print 'Hi, I am C-foo()'
...
>>>
在这个(未绑定)方法调用中我们显式地传递了self,一个更好的办法是使用super()内建方法,super()不但能找到基类方法,而且还为我们传进self,这样我们就不需要做这些事了。
>>> class C(P):
... def foo(self):
... super(C, self).foo()
... print 'Hi, I am C-foo()'
...
>>>
注意:子类重写__init__不会自动调用基类的__init__,即当从一个带构造器 __init()__的类派生,如果不去覆盖__init__(),它将会被继承并自动调用;但如果在子类中覆盖了__init__(),子类被实例化时,基类的__init__()就不会被自动调用。如果还想调用基类的__init__(),使用一个子类的实例去调用基类(未绑定)方法,如P.__init__(self)。子类的__init__()方法首先调用了基类的的__init__()方法,这是相当普遍的做法,用来设置初始化基类,然后可以执行子类内部的设置。这个规则之所以有意义的原因是:希望被继承的类的对象在子类构造器运行前能够很好地被初始化或作好准备工作,因为子类可能需要或设置继承属性。也可以使用super(P, self).__init__()调用基类的__init__,使用super()的重点是不需要明确提供父类,这意味着如果改变了类继承关系,只需要改一行代码(class 语句本身)而不必在大量代码中去查找所有被修改的那个类的名字。
(3)从标准类型派生
经典类中,一个最大的问题是不能对标准类型进行子类化,Python 2.2后随着类型(types)和类(class)的统一和新式类的引入,这一点已经被修正。标准类型中有可变类型和不可变类型。
从不可变类型派生,处理浮点数的子类:
>>> class RoundFloat(float):
... def __new__(cls, val):
... return super(RoundFloat, cls).__new__(cls, round(val, 2))
...
覆盖了__new__()特殊方法来定制自己的对象,使之和标准Python浮点数(float)有一些区别,round()内建函数对原浮点数进行舍入操作。然后实例化float、RoundFloat。通过调用父类的构造器来创建真实的对象float.__new__()。使用 super()内建函数去捕获对应的父类以调用它的__new()__方法。从可变类型派生,子类化一个可变类型与此类似,可能不需要使用__new__() (或甚至__init__()),因为通常设置不多。一般情况下,继承到的类型的默认行为就是想要的。创建一个新的字典类型,它的 keys()方法会自动排序结果:
>>> class SortedKeyDict(dict):
... def keys(self):
... return sorted(super( SortedKeyDict, self).keys())
...
>>> d = SortedKeyDict((('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2)))
>>> print 'By iterator:'.ljust(12), [key for key in d]
By iterator: ['zheng-cai', 'xin-yi', 'hui-jun']
>>> print 'By keys():'.ljust(12), d.keys()
By keys(): ['hui-jun', 'xin-yi', 'zheng-cai']
通过keys迭代过程是以散列顺序的形式,而使用我们(重写的)keys()方法则将keys变为字母排序方式了。(4)多重继承 同C++一样,Python允许子类继承多个基类,这种特性就是通常所说的多重继承。当使用多重继承时,有两个不同的方面要记住。首先,要找到合适的属性;另一个就是当重写方法时,如何调用对应父类方法以“发挥他们的作用”,同时,在子类中处理好自己的义务。
方法解释顺序(MRO),Python 2.2前,算法非常简单:深度优先,从左至右进行搜索,取得在子类中使用的属性。其它Python算法只是覆盖被找到的名字,多重继承则取找到的第一个名字。由于类、类型和内建类型的子类,都经过全新改造,有了新的结构,这种算法不再可行, 这样一种新的MRO算法被开发出来,新的查询方法是采用广度优先,而不是深度优先(精确顺序解释很复杂,可参考其他资料)。
简单属性查找示例:
>>> class P1(object):
... def foo(self):
... print 'called P1-foo()'
...
>>> class P2(object):
... def foo(self):
... print 'called P2-foo()'
...
>>> class P2(object):
... def foo(self):
... print 'called P2-foo()'
... def bar(self):
... print 'called P2-bar()'
...
>>> class C1(P1, P2):
... pass
...
>>> class C2(P1, P2):
... def bar(self):
... print 'called C2-bar()'
...
>>> class GC(C1, C2):
... pass
上述由一组父类、一组子类、还有一个子孙类组成,
P1中定义了foo(),
P2定义了foo()和bar(),C2 定义了 bar()。父类、子类及子孙类的关系图如下:
>>> gc = GC()
>>> gc.foo() # GC ==> C1 ==> P1
called P1-foo()
>>> gc.bar() # GC ==> C1 ==> P1 ==> P2
called P2-bar()
当调用 foo()时,它首先在当前类(GC)中查找。如果没找到,就向上查找最亲的父类C1。查找未遂,就继续沿树上访到父类P1,foo()被找到。同样,
对bar()来说,它通过搜索GC、C1、P1,然后在P2中找到。因为使用这种解释顺序的缘故,C2.bar()根本就不会被搜索了。调用C2的 bar()方法,必须调用它的合法的全名,采用典型的非绑定方式去调用,如C2.bar(gc)。
使用新式类,取消类P1和类P2声明中的对(object)的注释,重新执行一下。新式方法的查询有一些不同:
>>> gc = GC()
>>> gc.foo() # GC ==> C1 ==> C2 ==> P1
called P1-foo()
>>> gc.bar() # GC ==> C1 ==> C2
called C2-bar()
与沿着继承树一步一步上溯不同,它首先查找同胞兄弟,采用一种广度优先的方式。当查找foo(),它检查 GC,然后是C1和C2,然后在P1中找到。如果P1中没有,查找将会到达P2。foo()的底线是包括经典类和新式类都会在P1中找到它,然而它们虽然是同归,但殊途。然而,bar()的结果是不同的,它搜索GC和C1,紧接着在C2中找到了。这样,就不会再继续搜索到祖父P1和P2。这种情况下,新的解释方式更适合需要查找GC更亲近的bar()的方案。还需要调用上一级的话使用非绑定的方式去做:P2.bar(gc)。
新式类有一个__mro__属性,告诉查找顺序是怎样的:
>>> GC.__mro__
(<class '__main__.GC'>, <class '__main__.C1'>, <class
'__main__.C2'>, <class '__main__.P1'>, <class
'__main__.P2'>, <type 'object'>)
(5)内部类的使用
Python也可以在类的内部定义类,内部类中可以有两种方法调用:第一、直接使用外部类调用内部类,生成内部类的实例,再调用内部类的方法。第二、先对外部类进行实例化,然后再实例化内部类,最后调用内部类的方法。
>>> class Car:
... class Door:
... def open(self):
... print "open door"
... class Wheel:
... def run(self):
... print "car run"
...
>>> car = Car()
>>> backDoor = Car.Door() #外部类调用内部类,生成内部类的实例
>>> backDoor.open()
open door
>>> frontDoor = car.Door() #外部类进行实例化,然后再实例化内部类
>>> frontDoor.open()
open door
(6)抽象类的模拟
使用继承之后,子类可以重用父类中的属性和方法,并且可以对继承的方法进行重写。抽象类是对一类事物的特征和行为的抽象,抽象类由抽像方法组成,Python2.5并没有提供抽象类的语法,抽 象 类的特征是不能被实例化,可以通过Python的NotImplementedError类(继承自Python运行时错误类RuntimeError)模拟抽象类。
>>> def abstract():
... raise NotImplementedError("abstract")
...
>>> class Fruit:
... def __init__(self):
... if self.__class__ is Fruit:
... abstract()
... print "Fruit"
...
>>> class Apple(Fruit):
... def __init__(self):
... Fruit.__init__(self)
... print "Apple"
...
>>> apple = Apple()
Fruit
Apple
>>> fruit = Fruit()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in __init__
File "<stdin>", line 2, in abstract
NotImplementedError: abstract
同样,Python也没有提供对接口的支持,接口是特殊的抽象类,接口没有数据成员,而是一组未实现的方法的集合。Python去掉了面向对象中很对复杂的特性,例如类的属性和方法的限制符号(public、protected、private)。
(7)多态性
多态性是指发出同样的消息被不同类型的对象接收时,有可能导致完全不同的行为。即用户不干预,类的成员函数的行为能根据调用它的对象类型自动作出适应性调整,而且调整是发生在程序运行时。由于Python的动态类型,决定了Python的多态性。
>>> class Fruit:
... def __init__(self, color = None):
... self.color = color
...
>>> class Apple(Fruit):
... def __init__(self, color = "red"):
... Fruit.__init__(self, color)
...
>>> class Banana(Fruit):
... def __init__(self, color = "yellow"):
... Fruit.__init__(self, color)
...
>>> class FruitShop:
... def sellFruit(self, fruit):
... if isinstance(fruit, Apple):
... print "sell apple"
... if isinstance(fruit, Banana):
... print "sell banana"
... if isinstance(fruit, Fruit):
... print "sell fruit"
...
>>> shop = FruitShop()
>>> apple = Apple()
>>> banana = Banana()
>>> shop.sellFruit(apple)
sell apple
sell fruit
>>> shop.sellFruit(banana)
sell banana
sell fruit
sellFruit()根据不同的水果类型返回不同的结果,即sellFruit()根据多态性的特性,作出不同的处理。
12、类、实例和其他对象的内建函数
(1)issubclass()issubclass()布尔函数判断一个类是另一个类的子类或子孙类,它有如下语法:issubclass(sub, sup)。返回True的情况:给出的子类sub确实是父类sup的一个子类(反之则为 False)。这个函数也允许“不严格”的子类,意味着一个类可视为其自身的子类,所以这个函数如果当sub就是sup或者从sup派生而来,则返回True(一个“严格的”子类是严格意义上的从一个类派生而来的子类)。
从Python2.3开始,issubclass()的第二个参数可以是可能的父类组成的tuple(元组), 这时只要第一个参数是给定元组中任何一个候选类的子类时,就会返回True。
(2)isinstance()
isinstance()布尔函数在判定一个对象是否是另一个给定类的实例时,非常有用。它有如下语法:isinstance(obj1, obj2)。isinstance()在obj1是类obj2的一个实例,或者是obj2的子类的一个实例时,返回True。
第二个参数应当是类,不然会得到一个TypeError。但如果第二个参数是一个类型对象,这是允许的,,因为可以使用isinstance()来检查一个对象obj1是否是obj2 的类型,比如:
>>> isinstance(4, int)
True
>>> isinstance('4', str)
True
同issubclass()一样,isinstance()也可以使用一个元组(tuple)作为第二个参数,如果第一个参数是第二个参数中给定元组的任何一个候选类型或类的实例时,就会返回 True。
(3)hasattr(), getattr(),setattr(), delattr()
*attr()系列函数可以在各种对象下工作,不限于类(class)和实例(instances)。需要说明的是,当使用这些函数时传入正在处理的对象作为第一个参数,但属性名,也就是这些函数的第二个参数,是这些属性的字符串名字。换句话说,在操作obj.attr时,就相当于调用*attr(obj,'attr'....)系列函数。
hasattr()函数是Boolean型的,它的目的就是为了决定一个对象是否有一个特定的属性,一般用于访问某属性前先作一下检查。getattr()和setattr()函数相应地取得和赋值给对象的属性,getattr()会在试图读取一个不存在的属性时,会引发AttributeError异常,除非给出那个可选的默认参数。setattr()将要么加入一个新的属性,要么取代一个已存在的属性。而delattr()函数会从一个对象中删除属性。
>>> class myClass(object):
... def __init__(self):
... self.foo = 100
...
>>> myInst = myClass()
>>> hasattr(myInst, 'foo')
True
>>> getattr(myInst, 'foo')
100
>>> getattr(myInst, 'bar', 'not have!')
'not have!'
>>> setattr(myInst, 'bar', 'my attr')
>>> dir(myInst)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', \
'__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', \
'__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bar', 'foo']
>>> getattr(myInst, 'bar')
'my attr'
>>> delattr(myInst, 'foo')
>>> hasattr(myInst, 'foo')
False
(4)dir()
dir()列出一个模块所有属性的信息,还可以用在对象上。
dir()作用在实例上(经典类或新式类)时,显示实例变量,还有在实例所在的类及所有它的基类中定义的方法和类属性。dir()作用在类上(经典类或新式类) 时,则显示类以及它的所有基类的__dict__中的内容,但它不会显示定义在元类(metaclass)中的类属性。dir()作用在模块上时,则显示模块的__dict__的内容。dir()不带参数时,则显示调用者的局部变量。
(5)super()
super()函数目的就是帮助程序员找出相应的父类,然后方便调用相关的属性。一般情况下,程序员可能仅仅采用非绑定方式调用祖先类方法。使用super()可以简化搜索一个合适祖先的任务,并且在调用它时,可以传入实例或类型对象。
语法:super(type[, obj]),给出type和super()“返回此type的父类”。如果希望父类被绑定,可以传入obj参数(obj必须是type类型的),否则父类不会被绑定。obj参数也可以是一个类型,但它应当是type的一个子类。通常当给出obj时:如果obj是一个实例,isinstance(obj,type)就必须返回True;如果obj是一个类或类型,issubclass(obj,type)就必须返回True。
事实上,super()是一个工厂函数,它创造了一个super object,为一个给定的类使用__mro__去查找相应的父类。很明显,它从当前所找到的类开始搜索MRO。super()的主要用途,是来查找父类的属性,比如:super(MyClass,self).__init__()。
(6)vars()
vars()内建函数与dir()相似,只是给定的对象参数都必须有一个__dict__属性。vars()返回一个字典,它包含了对象存储于其__dict__中的属性(键)及值。如果提供的对象没有这样一个属性,则会引发一个TypeError异常。如果没有提供对象作为 vars()的一个参数,它将显示一个包含本地名字空间的属性(键)及其值的字典,即locals()。
>>> class C(object):
... pass
...
>>> c = C()
>>> c.foo = 100
>>> c.bar = 'Python'
>>> c.__dict__
{'foo': 100, 'bar': 'Python'}
>>> vars(c)
{'foo': 100, 'bar': 'Python'}
13、私有化
默认情况下,属性在Python中都是“public”,类所在模块和导入了类所在模块的其他模块的代码都可以访问到。很多OO语言
提供“访问控制符”来限定成员函数的访问,给数据加上一些可见性,只提供访问函数来访问其值,这就是熟知的实现隐藏,是对象封装中的一个关键部分。
双下划线(__),是Python为类元素(属性和方法)的私有性提供初步的形式。由双下划线开始的属性在运行时被“混淆”,所以直接访问是不允许的。实际上,会在名字前面加上下划线和类名。比如以例self.__num属性为例,被“混淆”后,用于访问这个数据值的标识就变成了self._NumStr__num。把类名加上后形成的新的“混淆”结果将可以防止在祖先类或子孙类中的同名冲突。尽管这样做提供了某种层次上的私有化,但算法处于公共域中并且很容易被“击败”。这种名字混淆的另一个目的,是为了保护__XXX 变量不与父类名字空间相冲突。如果在类中有一个__XXX 属性,它将不会被其子类中的__XXX 属性覆盖。如果父类仅有一个 XXX 属性,子类也定义了这个,这时子类的XXX就覆盖了父类的XXX。而使用__XXX,子类的代码就可以安全地使用__XXX,而不必担心它会影响到父类中的__XXX。
>>> class Secretive:
... def __inaccessible(self):
... print "Bet you can't see me..."
...
>>> s = Secretive()
>>> s.__inaccessible()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: Secretive instance has no attribute '__inaccessible'
>>> s._Secretive__inaccessible()
Bet you can't see me...
单下划线(_),简单的模块级私有化只需要在属性名前使用一个单下划线字符。这就防止模块的属性用“from mymodule import *来加载。这是严格基于作用域的,所以这同样适合于函数。尽管Python没有在语法上把private、protected、friend或protected等特征内建于语言中,但是可以按需要严格地定制访问权。
14、运算符的重载
运算符用于表达式的计算,但对于自定义的对象并不能进行计算。运算符的重载可以实现对象之间的运算,Python把运算符和类的内置方法关联起来,每个运算符对应一个函数。
实现对运算符"+"(__add__()表示运算符加)和运算符">"(__gt__()表示运算符大于)的重载。
>>> class Fruit:
... def __init__(self, price = 0):
... self.price = price
... def __add__(self, other): #重载+运算符
... return self.price + other.price
... def __gt__(self, other):
... if self.price > other.price #重载>运算符
File "<stdin>", line 7
if self.price > other.price #重载>运算符
^
SyntaxError: invalid syntax
>>> class Fruit:
... def __init__(self, price = 0):
... self.price = price
... def __add__(self, other): #重载+运算符
... return self.price + other.price
... def __gt__(self, other): #重载>运算符
... if self.price > other.price:
... flag = True
... else:
... flag = False
... return flag
...
>>> class Apple(Fruit):pass
...
>>> class Banana(Fruit):pass
...
>>> apple = Apple(3)
>>> print "the price of apple:", apple.price
the price of apple: 3
>>> banana = Banana(4)
>>> print "the price of banana:", banana.price
the price of banana: 4
>>> total = apple + banana
>>> print "total:", total
total: 7
>>> print apple > banana
False
C++支持<<、>>等流操作符,Python可以对流操作符进行重载,实现类似C++的流操作。如下实现对运算符<< 的重载:
>>> import sys
>>> class Stream:
... def __init__(self, file):
... self.file = file
... def __lshift__(self, obj):
... self.file.write(str(obj))
... return self
...
>>> class Fruit(Stream):
... def __init__(self, price = 0, file = None):
... Stream.__init__(self, file)
... self.price = price
...
>>> class Apple(Fruit):pass
...
>>> class Banana(Fruit):pass
...
>>> apple = Apple(3, sys.stdout)
>>> banana = Banana(4, sys.stdout)
>>> endl = "\n"
>>> apple << apple.price <<endl
3
<__main__.Apple instance at 0x7fb81551ea28>
>>> banana << banana.price <<endl
4
<__main__.Banana instance at 0x7fb81551e998>
15、Python与设计模式
设计模式是面向对象程序设计的解决方案,是复用性程序设计的经验总结,设计模式是与语言无关的,任何语言都可以实现设计模式。设计模式根据使用目的的不同而分为创建型模式、结构性模式和行为性模式。Python是一种简单、灵活的动态语言,简化了面向对象语法的复杂性,却没有降低面向对象的特性,使用Python同样可以实现各种设计模式。
Python实现工厂方法:在工厂方法模式中,工厂方法用于创建产品,并隐藏产品对象实例化的过程,根据不同的参数生成不同的对象。
>>> class Factory:
... def createFruit(self, fruit):
... if fruit == "apple":
... return Apple()
... if fruit == "banana":
... return Banana()
...
>>> class Fruit:
... def __str__(self):
... return "fruit"
...
>>> class Apple(Fruit):
... def __str(self):
... return "apple"
...
>>> class Banana(Fruit):
... def __str__(self):
... return "banana"
...
>>> factory = Factory()
>>> print factory.createFruit("apple")
fruit
>>> print factory.createFruit("banana")
banana
三、总结
(1)Python面向对象与UML(UML表述软件开发过程的图形化标记,使用若干中模型图来描述软件开发中的每个重要步骤)建模语言相互协助完成建模和开发。(2)Python支持很多运算符的重载,并且23种设计模式同样适用于Python。
(3)若有不足,请留言,在此先感谢!