zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Python入门篇之面向对象

Python 面向对象 入门篇
2023-06-13 09:15:29 时间

面向对象设计与面向对象编程的关系
 
面向对象设计(OOD)不会特别要求面向对象编程语言。事实上,OOD可以由纯结构化语言来实现,比如C,但如果想要构造具备对象性质和特点的数据类型,就需要在程序上作更多的努力。当一门语言内建OO特性,OO编程开发就会更加方便高效。另一方面,一门面向对象的语言不一定会强制你写OO方面的程序。例如C++可以被认为“更好的C”;而Java,则要求万物皆类,此外还规定,一个源文件对应一个类定义。然而,在Python中,类和OOP都不是日常编程所必需的。尽管它从一开始设计就是面向对象的,并且结构上支持OOP,但Python没有限定或要求你在你的应用中写OO的代码。OOP是一门强大的工具,不管你是准备进入,学习,过渡,或是转向OOP,都可以任意支配。考虑用OOD来工作的一个最重要的原因,在于它直接提供建模和解决现实世界问题和情形的途径。
 

 
类是一种数据结构,我们可以用它来定义对象,后者把数据值和行为特性融合在一起。类是现实世界的抽象的实体以编程形式出现。实例是这些对象的具体化。可以类比一下,类是蓝图或者模型,用来产生真实的物体(实例)。类还可以派生出相似但有差异的子类。编程中类的概念就应用了很多这样的特征。在Python中,类声明与函数声明很相似,头一行用一个相应的关键字,接下来是一个作为它的定义的代码体,如下所示:

复制代码代码如下:


deffunctionName(args):
   "functiondocumentationstring" #函数文档字符串
    function_suite #函数体
classClassName(object):
   "classdocumentationstring" #类文档字符串
    class_suite  #类体 

二者都允许你在他们的声明中创建函数,闭包或者内部函数(即函数内的函数),还有在类中定义的方法。最大的不同在于你运行函数,而类会创建一个对象。类就像一个Python容器类型。尽管类是对象(在Python中,一切皆对象),但正被定义时,它们还不是对象的实现。
 
创建类
 
Python类使用class关键字来创建。简单的类的声明可以是关键字后紧跟类名:

复制代码代码如下:


classClassName(bases):
   "classdocumentationstring"#"类文档字符串"
   class_suite#类体 

基类是一个或多个用于继承的父类的集合;类体由所有声明语句,类成员定义,数据属性和函数组成。类通常在一个模块的顶层进行定义,以便类实例能够在类所定义
 
的源代码文件中的任何地方被创建。
 
声明与定义
对于Python函数来说,声明与定义类没什么区别,因为他们是同时进行的,定义(类体)紧跟在声明(含class关键字的头行[headerline])和可选的文档字符串后面。同时,所有的方法也必须同时被定义。如果对OOP很熟悉,请注意Python并不支持纯虚函数(像C++)或者抽象方法(如在JAVA中),这些都强制程序员在子类中定义方法。作为替代方法,你可以简单地在基类方法中引发NotImplementedError异常,这样可以获得类似的效果。
 
类属性
 
属性就是属于另一个对象的数据或者函数元素,可以通过我们熟悉的句点属性标识法来访问。一些Python类型比如复数有数据属性(实部和虚部),而另外一些,像列表和字典,拥有方法(函数属性)。

有关属性的一个有趣的地方是,当你正访问一个属性时,它同时也是一个对象,拥有它自己的属性,可以访问,这导致了一个属性链,比如,myThing,subThing,subSubThing.等等
 
类的数据属性
 
数据属性仅仅是所定义的类的变量。它们可以像任何其它变量一样在类创建后被使用,并且,要么是由类中的方法来更新,要么是在主程序其它什么地方被更新。
这种属性已为OO程序员所熟悉,即静态变量,或者是静态数据。它们表示这些数据是与它们所属的类对象绑定的,不依赖于任何类实例。如果你是一位Java或C++程序员,这种类型的数据相当于在一个变量声明前加上static关键字。静态成员通常仅用来跟踪与类相关的值。
 
看下面的例子,使用类数据属性(foo):

复制代码代码如下:
>>>classc(object):
   foo=100
>>>printc.foo
100
>>>c.foo+=1
>>>c.foo
101

方法
 

复制代码代码如下:
>>>classMyClass(object):
       defmyNoActionMethod(self):
       pass
>>>mc=MyClass()
>>>mc.myNoActionMethod()         

任何像函数一样对myNoActionMethod自身的调用都将失败:

复制代码代码如下:
>>>myNoActionMethod()Traceback(innermostlast):
File"<stdin>",line1,in?
myNoActionMethod()NameError:myNoActionMethod

甚至由类对象调用此方法也失败了。

复制代码代码如下:
>>>MyClass.myNoActionMethod()Traceback(innermostlast):
File"<stdin>",line1,in?
MyClass.myNoActionMethod()
TypeError:unboundmethodmustbecalledwithclass
instance1stargument

绑定(绑定及非绑定方法)
为与OOP惯例保持一致,Python严格要求,没有实例,方法是不能被调用的。这种限制即Python所描述的绑定概念(binding),在此,方法必须绑定(到一个实例)才能直接被调用。非绑定的方法可能可以被调用,但实例对象一定要明确给出,才能确保调用成功。然而,不管是否绑定,方法都是它所在的类的固有属性,即使它们几乎总是通过实例来调用的。
 
决定类的属性
 
要知道一个类有哪些属性,有两种方法。最简单的是使用dir()内建函数。另外是通过访问类的字典属性__dict__,这是所有类都具备的特殊属性之一。
 
看一下下面的例子:
 

复制代码代码如下:
>>>classmyclass(object):
   "myclassclassdefinition"#类定义
   myVersion="1.1"         #静态数据
   defshowVesion(self):     #方法
       printmyclass.myVersion
       
>>>dir(myclass)

运行结果:

复制代码代码如下:
["__class__","__delattr__","__dict__","__doc__","__format__","__getattribute__","__hash__","__init__","__module__","__new__","__reduce__","__reduce_ex__","__repr__","__setattr__","__sizeof__","__str__","__subclasshook__","__weakref__","myVersion","showVesion"]

使用:

复制代码代码如下:
>>>myclass.__dict__
dict_proxy({"__module__":"__main__","showVesion":<functionshowVesionat0x0134C9B0>,"__dict__":<attribute"__dict__"of"myclass"objects>,"myVersion":"1.1","__weakref__":<attribute"__weakref__"of"myclass"objects>,"__doc__":"myclassclassdefinition"})

从上面可以看到,dir()返回的仅是对象的属性的一个名字列表,而__dict__返回的是一个字典,它的键(keys)是属性名,键值(values)是相应的属性对象的数据值。
结果还显示了MyClass类中两个熟悉的属性,showMyVersion和myVersion,以及一些新的属性。这些属性,__doc__及__module__,是所有类都具备的特殊类属性(另外还有__dict__)。。内建的vars()函数接受类对象作为参数,返回类的__dict__属性的内容。

特殊的类属性
 
对任何类C,表显示了类C的所有特殊属性: 
C.__name__       类C的名字(字符串)
C.__doc__        类C的文档字符串
C.__bases__      类C的所有父类构成的元组
C.__dict__       类C的属性
C.__module__     类C定义所在的模块(1.5版本新增)
C.__class__      实例C对应的类(仅新式类中)

复制代码代码如下:
>>>myclass.__name__
"myclass"
>>>myclass.__doc__
"myclassclassdefinition"
>>>myclass.__bases__
(<type"object">,)
>>>printmyclass.__dict__
{"__module__":"__main__","showVesion":<functionshowVesionat0x0134C9B0>,"__dict__":<attribute"__dict__"of"myclass"objects>,"myVersion":"1.1","__weakref__":<attribute"__weakref__"of"myclass"objects>,"__doc__":"myclassclassdefinition"}
>>>myclass.__module__
"__main__"
>>>myclass.__class__
<type"type">

实例
 
如果说类是一种数据结构定义类型,那么实例则声明了一个这种类型的变量。实例是那些主要用在运行期时的对象,类被实例化得到实例,该实例的类型就是这个被实例化的类。
 
初始化:通过调用类对象来创建实例
 
Python的方式更加简单。一旦定义了一个类,创建实例比调用一个函数还容易------不费吹灰之力。实例化的实现,可以使用函数操作符,如下示:
 
>>>classMyClass(object):#defineclass定义类
       pass
>>>mc=MyClass()#instantiateclass初始化类    
__init__()"构造器"方法
 
当类被调用,实例化的第一步是创建实例对象。一旦对象创建了,Python检查是否实现了__init__()方法。默认情况下,如果没有定义(或覆盖)特殊方法__init__(),对实例不会施加任何特别的操作.任何所需的特定操作,都需要程序员实现__init__(),覆盖它的默认行为。
 
如果__init__()没有实现,则返回它的对象,实例化过程完毕。
 
如果__init__()已经被实现,那么它将被调用,实例对象作为第一个参数(self)被传递进去,像标准方法调用一样。调用类时,传进的任何参数都交给了__init__()。实际中,你可以想像成这样:把创建实例的调用当成是对构造器的调用。
 
__new__()“构造器”方法
 
与__init__()相比,__new__()方法更像一个真正的构造器。需要一种途径来实例化不可变对象,比如,派生字符串,数字,等等。在这种情况下,解释器则调用类的__new__()方法,一个静态方法,并且传入的参数是在类实例化操作时生成的。__new__()会调用父类的__new__()来创建对象(向上代理)。__new__()必须返回一个合法的实例。
 
__del__()"解构器"方法
 
同样,有一个相应的特殊解构器(destructor)方法名为__del__()。然而,由于Python具有垃圾对象回收机制(靠引用计数),这个函数要直到该实例对象所有的引用都被清除掉后才会执行。Python中的解构器是在实例释放前提供特殊处理功能的方法,它们通常没有被实现,因为实例很少被显式释放。
 
注意:Python没有提供任何内部机制来跟踪一个类有多少个实例被创建了,或者记录这些实例是些什么东西。如果需要这些功能,你可以显式加入一些代码到类定义或者__init__()和__del__()中去。最好的方式是使用一个静态成员来记录实例的个数。靠保存它们的引用来跟踪实例对象是很危险的,因为你必须合理管理这些引用,不然,你的引用可能没办法释放(因为还有其它的引用)!看下面一个例子:
 

复制代码代码如下:
>>>classinstCt(object):
   count=0
   def__init__(self):
       instCt.count+=1
   def__del__(self):
       instCt.count-=1
   defhowMany(self):
       returninstCt.count
   
>>>a=instCt()
>>>b=instCt()
>>>b.howMany()
2
>>>a.howMany()
2
>>>delb
>>>a.howMany()
1
>>>dela
>>>instCt.count
0

实例属性
 
设置实例的属性可以在实例创建后任意时间进行,也可以在能够访问实例的代码中进行。构造器__init()__是设置这些属性的关键点之一
 
能够在“运行时”创建实例属性,是Python类的优秀特性之一,Python不仅是动态类型,而且在运行时,允许这些对象属性的动态创建。这种特性让人爱不释
手。当然,创建这样的属性时,必须谨慎。一个缺陷是,属性在条件语句中创建,如果该条件语句块并未被执行,属性也就不存在,而你在后面的代码中试着去访问这些属性,就会有错误发生。
 
默认参数提供默认的实例安装
在实际应用中,带默认参数的__init__()提供一个有效的方式来初始化实例。在很多情况下,默认值表示设置实例属性的最常见的情况,如果提供了默认值,我们就没必要显式给构造器传值了。
 

复制代码代码如下:
>>classHotelRoomCalc(object):
   "hotelroomratecalculate"
   def__init__(self,rt,sales=0.085,rm=0.1):
       """HotelRoomCalcdefaultarguments:
                salestax==8.5%androomtax==10%"""
       self.salesTax=sales
       self.roomTax=rm
       self.roomRate=rt
   defcalcTotal(self,days=1):
       "Calculatetotal:defaulttodailyrate"
       daily=round((self.roomRate*14*(1+self.roomTax+self.salesTax)),2)
       returnfloat(days)*daily

>>>sfo=HotelRoomCalc(299)
>>>sfo.calcTotal()
4960.41
>>>sfo.calcTotal(2)
9920.82
>>>sea=HotelRoomCalc(189,0.086,0.085)
>>>sea.calcTotal()
3098.47
>>>sea.calcTotal(4)
12393.88

函数所有的灵活性,比如默认参数,也可以应用到方法中去。在实例化时,可变长度参数也是一个好的特性
 
__init__()应当返回None
采用函数操作符调用类对象会创建一个类实例,也就是说这样一种调用过程返回的对象就是实例,下面示例可以看出:

复制代码代码如下:
>>>classMyClass(object):
   pass

>>>mc=MyClass()
>>>mc
<__main__.MyClassobjectat0x0134E610>

如果定义了构造器,它不应当返回任何对象,因为实例对象是自动在实例化调用后返回的。相应地,__init__()就不应当返回任何对象(应当为None);否则,就可能出现冲突,因为只能返回实例。试着返回非None的任何其它对象都会导致TypeError异常:
 

复制代码代码如下:
>>>classMyClass:
   def__init__(self):
       print"initialized"
       return1

>>>mc=MyClass()
initialized
Traceback(mostrecentcalllast):
 File"<pyshell#86>",line1,in<module>
   mc=MyClass()
TypeError:__init__()shouldreturnNone

查看实例属性
 
内建函数dir()可以显示类属性,同样还可以打印所有实例属性:

复制代码代码如下:
>>>c=C()
>>>c.foo="he"
>>>c.bar="isa"
>>>dir(c)
["__class__","__delattr__","__dict__","__doc__","__format__","__getattribute__","__hash__","__init__","__module__","__new__","__reduce__","__reduce_ex__","__repr__","__setattr__","__sizeof__","__str__","__subclasshook__","__weakref__","bar","foo"]

与类相似,实例也有一个__dict__特殊属性(可以调用vars()并传入一个实例来获取),它是实例属性构成的一个字典:

复制代码代码如下:
>>>c.__dict__
{"foo":"he","bar":"isa"}

特殊的实例属性
 
实例仅有两个特殊属性。对于任意对象I:
I.__class__     实例化I的类
I.__dict__      I的属性

复制代码代码如下:
>>>classC(object):
     pass
>>>c=C()
>>>dir(c)
["__class__","__delattr__","__dict__","__doc__","__format__","__getattribute__","__hash__","__init__","__module__","__new__","__reduce__","__reduce_ex__","__repr__","__setattr__","__sizeof__","__str__","__subclasshook__","__weakref__"]
>>>c.__dict__
{}
>>>c.__class__
<class"__main__.C">>>>#可以看到,c还没有属性
>>>c.foo=1
>>>c.bar="ewe"
>>>"%dcanof%splease"%(c.foo,c.bar)
"1canofeweplease"
>>>c.__dict__
{"foo":1,"bar":"ewe"}

内建类型属性
 
内建类型也是类,对内建类型也可以使用dir(),与任何其它对象一样,可以得到一个包含它属性名字的列表:
 

复制代码代码如下:
>>>x=2+2.4j
>>>x.__class__
<type"complex">
>>>dir(x)
["__abs__","__add__","__class__","__coerce__","__delattr__","__div__","__divmod__","__doc__","__eq__","__float__","__floordiv__","__format__","__ge__","__getattribute__","__getnewargs__","__gt__","__hash__","__init__","__int__","__le__","__long__","__lt__","__mod__","__mul__","__ne__","__neg__","__new__","__nonzero__","__pos__","__pow__","__radd__","__rdiv__","__rdivmod__","__reduce__","__reduce_ex__","__repr__","__rfloordiv__","__rmod__","__rmul__","__rpow__","__rsub__","__rtruediv__","__setattr__","__sizeof__","__str__","__sub__","__subclasshook__","__truediv__","conjugate","imag","real"]

试着访问__dict__会失败,因为在内建类型中,不存在这个属性
 
实例属性vs类属性
 
类属性仅是与类相关的数据值,和实例属性不同,类属性和实例无关。这些值像静态成员那样被引用,即使在多次实例化中调用类,它们的值都保持不变。不管如何,静态成员不会因为实例而改变它们的值,除非实例中显式改变它们的值。类和实例都是名字空间。类是类属性的名字空间,实例则是实例属性的。
关于类属性和实例属性,还有一些方面需要指出。可采用类来访问类属性,如果实例没有同名的属性的话,你也可以用实例来访问。

访问类属性
类属性可通过类或实例来访问。下面的示例中,类C在创建时,带一个version属性,这样通过类对象来访问它是很自然的了,比如,C.version
 

复制代码代码如下:
>>>classC(object):
   version=2

>>>c=C()
>>>C.version
2
>>>c.version
2
>>>C.version+=2
>>>C.version
4
>>>c.version
4

从实例中访问类属性须谨慎
 
与通常Python变量一样,任何对实例属性的赋值都会创建一个实例属性(如果不存在的话)并且对其赋值。如果类属性中存在同名的属性,副作用即产生。
 

复制代码代码如下:
>>>classFoo(object):
   x=1

>>>foo=Foo()
>>>foo.x
1
>>>foo.x=2
>>>Foo.x
1

使用del后
 

复制代码代码如下:
>>>delfoo.x
>>>foo.x
1

静态成员,如其名所言,任凭整个实例(及其属性)的如何进展,它都不理不采(因此独立于实例)。同时,当一个实例在类属性被修改后才创建,那么更新的值就将生效。类属性的修改会影响到所有的实例:
 

复制代码代码如下:
>>>classC(object):
   spam=11
   
>>>c1=C()
>>>c1.spam
11
>>>C.spam+=2
>>>C.spam
13
>>>c1.spam
13
>>>c2=C()
>>>c2.spam
13
>>>delc1
>>>C.spam+=3
>>>c2.spam
16

正如上面所看到的那样,使用实例属性来试着修改类属性是很危险的。原因在于实例拥有它们自已的属性集,在Python中没有明确的方法来指示你想要修改同名的类属性,修改类属性需要使用类名,而不是实例名。
 
静态方法和类方法
 
静态方法和类方法在Python2.2中引入。经典类及新式(new-style)类中都可以使用它。一对内建函数被引入,用于将作为类定义的一部分的某一方法声明“标记”(tag),“强制类型转换”(cast)或者“转换”(convert)为这两种类型的方法之一。
 
现在让我们看一下在经典类中创建静态方法和类方法的一些例子:
 

复制代码代码如下:
>>>classTestStaticMethod:
   deffoo():
       print"callingstaticmethodfoo()"
   foo=staticmethod(foo)

>>>classTestClassMethod:
   deffoo(cls):
       print"callingclassmethodfoo()"
       print"foo()ispartofclass:",cls.__name__
   foo=classmethod(foo)

对应的内建函数被转换成它们相应的类型,并且重新赋值给了相同的变量名。如果没有调用这两个函数,二者都会在Python编译器中产生错误,显示需要带self的常规方法声明。
 

复制代码代码如下:
>>>tsm=TestStaticMethod()
>>>TestStaticMethod.foo()
callingstaticmethodfoo()
>>>tsm.foo()
callingstaticmethodfoo()
>>>tcm=TestClassMethod()
>>>TestClassMethod.foo()
callingclassmethodfoo()
foo()ispartofclass:TestClassMethod
>>>tcm.foo()
callingclassmethodfoo()
foo()ispartofclass:TestClassMethod

使用函数修饰符:
 
在Python2.4中加入的新特征。你可以用它把一个函数应用到另个函数对象上,而且新函数对象依然绑定在原来的变量。我们正是需要它来整理语法。通过使用decorators,我们可以避免像上面那样的重新赋值:
 

复制代码代码如下:
>>>classTestStaticMethod:
   @staticmethod
   deffoo():
       print"callingstaticmethodfoo()"

>>>classTestClassMethod:
   @classmethod
   deffoo(cls):
       print"callingclassmethodfoo()"
       print"foo()ispartofclass:",cls.__name__