python:14:类(2)-- 多态和继承

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/genome_denovo/article/details/79835091

第一章:python

第14节:类

1,类的特性

类的三大特性包括,封装、多态和继承
类的多态是指同一个方法在不同类当中会有不同的功能
下面着重讲一下类的继承

2,子类和派生

创建子类的语法为:一个类名,后跟一个或多个需要从其中派生的父类:
>>> class SubClassName(ParentClass1 [, ParentClass2, ...]):
... ····class_suite
这个SubClassName类被称为子类,而ParentClass1被称为父类,而子类是从父类中派生出来
如果该类没有从任何祖先类派生,则可以使用object作为父类的名字,当然也可以不用写括号。

3,继承

继承的写法同上
>>> class SubClassName(ParentClass1 [, ParentClass2, ...]):
... ····class_suite
这里SubClassName继承了ParentClass1这个父类(如果有ParentClass1和ParentClass2则继承了两个父类)
比如:
>>> class P:
... ····pass
>>> class C(P):
... ····pass
如此操作表示类C从类P继承而来,可以说类C继承类P,C是P的子类,而P是C的父类
子类可继承父类的所有内容,主要继承父类的公有属性和公有方法,子类不能继承父类的私有属性和私有方法,同时也不能够继承父类的文档字符串,因为文档字符串对类和方法来说都是唯一的
通过__bases__这个类属性可以看该类是否存在父类;
例如:
>>> C.__bases__
(<class __main__.P at 0x02D3A308>,) #类C继承了一个叫做P的父类
>>> P.__bases__ #P不存在其父类
()

子类在继承父类的时候__init__() 构造函数也同样继承
所以当一个类继承其父类时,如果子类不覆盖父类的__init__() 构造函数,则子类在继承该父类时__init__() 构造函数自动调用,但如果你在子类中覆盖了__init__() 构造函数,当子类在被实例化时,其父类的__init__() 构造函数就不会被调用了。(但可以通过super函数调用父类的__init__() 构造函数使用方法为:super(C, self).__init__()

一个子类父类的例子:
>>> class Father: #定义一个父类
... ····money=1000 #公有属性
... ····__money=10000 #私有属性
... ····def drive(self): #公有方法
... ········print 'I can drive a car!'
... ····def __name(self): #私有方法
... ········print 'my name is tom'
...
>>> class Son(Father): #继承父类
... ····def pay(self): #子类的共有方法,该方法只有子类当中有,而父类当中没有
... ········print self.money #输出父类的公有属性
... ········print self.__money #输出父类的私有属性
...
>>> tom=Father() #实例化父类
>>> print tom.money #可以输出父类的公有属性
1000
>>> tom.drive() #可以输出父类的公有方法
I can drive a car!
>>> tom.__name() #外部无法输出类的私有方法
Traceback (most recent call last):
·File "<pyshell#148>", line 1, in <module>
··tom.__name()
AttributeError: Father instance has no attribute '__name'
>>> jerry=Son() #实例化子类
>>> print jerry.money #可以输出子类的属性,该属性继承于其父类,子类继承了父类的公有属性
1000
>>> print jerry.__money #子类无法继承父类的私有属性
Traceback (most recent call last):
·File "<pyshell#151>", line 1, in <module>
··print jerry.__money
AttributeError: Son instance has no attribute '__money'
>>> jerry.drive() #子类继承了父类的公有方法
I can drive a car!
>>> jerry.pay() #子类本身的不同于父类的公有方法
1000
Traceback (most recent call last):
·File "<pyshell#153>", line 1, in <module>
··jerry.pay()
·File "<pyshell#144>", line 4, in pay
··print self.__money #但无法继承父类的私有方法,所以显示无对象
AttributeError: Son instance has no attribute '_Son__money'

4,多重继承后的属性和方法选择

一个子类不仅可以继承一个父类,也可以继承多个父类
多重继承后的属性和方法选择原则:
如果两个父类之间有同名的属性和方法,则子类在继承时只遵从第一个父类的同名属性和方法
>>> class Father:
... ····money=10000 #第一个父类的money属性值为10000
...
>>> class Mother:
... ····mm=100
... ····money=1000 #第二个父类的money属性值为1000
...
>>> class Son(Father, Mother): #当子类继承两个过多个父类时,在括号中写入父类的名称,并且前后名称中间用“,”分隔开
... ····def pay(self):
... ········print self.money #当然这里money属性在两个父类中均存在且名称相同
... ········print self.mm
...
>>> tom=Son() #Son这个类进行实例化
>>> tom.pay() #实例对象tom独有的方法,输出父类当中的money属性,这时输出的结果遵从第一个父类的属性值,结果是10000
10000
100 #这个值是父类Mother的mm属性的值

5,方法重载

方法重载即重载父类的方法;当然可通过继承覆盖原方法,即在子类当中写一个方法与其继承的父类当中的方法同名,则子类在实例化时使用的该同名方法是子类当中的方法而非其继承的父类当中的方法;当然,也可以通过super函数或直接调用父类的方式实现在子类中对父类的方法调用。
下面分别举例说明,方法重载的两种方法:
先看覆盖继承方法的例子:
>>> class Father:
... ····def engineer(self):
... ········print "I am a engineer"
...
>>> class Son(Father):
... ····def engineer(self): #这里子类与父类的方法同名,方法中的内容不同,
... ········print "I am a driver"
...
>>> sundy=Son()
>>> sundy.engineer() #这里子类覆盖了原父类当中的方法,进行了方法重载,输出的是子类当中的方法内容
I am a driver
1,通过直接调用父类的方式实现在子类中对父类的方法调用
原父类的内容不变我们重新写一下子类的内容,并重新输出结果
>>> class Son(Father):
... ····def engineer(self):
... ········print "I am a driver" #这里是原有的子类当中的方法的内容
... ········Father.engineer(self) #这里调用父类当中的engineer的方法,即父类的原有方法,但在类当中调用方法,或使用方法必须加入self
...
>>> sundy=Son()
>>> sundy.engineer()
I am a driver #这里输出的是子类方法当中的print语句内容
I am a engineer #这里输出的是父类当中的engineer的方法的内容
父类的原有方法,但在其他类当中调用方法,或使用方法必须加入self:
| Father.engineer(self)|
| 父类名称.父类方法(传参给自己) |
2,通过super函数方式实现在子类中对父类的方法调用
父类及子类当中的内容我们都重新修改一下:

【那么我们为什么要重新写一下父类的内容呢,这里要提到一个新概念了,就是经典类和新式类的概念;在python2当中class Myclass: #表示的是经典类;而class Myclass(object): #表示的是新式类,而super()函数仅仅在新式类(父类是新式类其继承了object这个超类,子类继承父类同样也是新式类)当中可用,如果在经典类当中使用会出现如下报错信息:TypeError: super() argument 1 must be type, not classobj 显示的是类型错误,当下次遇到同样的报错信息编者就会知道可能是在经典类当中使用了super的方法】【同样__init__() 构造器也可以使用super()函数调用父类的构造器方法及其中内容,super(当前类的名称, self).__init__()

>>> class Father(object): #重写父类是让该经典类变成一个新式类
... ····def engineer(self):
... ········print "I am a engineer"
...
>>> class Son(Father):
... ····def engineer(self):
... ········print "I am a driver"
... ········super(Son, self).engineer() #super()函数的好处在于,其通过在括号当中输入当前类的名称;而后super()函数会自动寻找当前类的父类并调用父类的点后面的方法。
...
>>> sundy=Son()
>>> sundy.engineer() #输出内容同上,即达到了相同的目的
I am a driver
I am a engineer

6,经典类与新式类,调用父类方法的顺序关系

首先讲一下经典类与新式类的区别,主要体现在写法上面的区别:
在python2的版本中出现经典类和新式类;
class MyData: #python2定义经典类的方式
class MyData(object): #python2定义新式类的方式,括号当中的object可以成为一种超类,也是继承的一种方式
class MyData: #python3默认定义的都是新式类
经典类和新式类调用方法上面的区别:
经典类只有一种调用父类方法的方式,即:
父类.方法(self)
新式类可以有两种调用父类方法的方式,即:
1,父类.方法(self)
2,super(当前类, self).方法()

那么super()函数和父类.方法(self)在调用父类方法的时候有何区别呢?
这就是下面要讲到的新式类和经典类在调用父类方法时的搜寻规则,即多重继承属性和方法搜索的顺序规则:
例图:
_______object
______/_______\
_____/_________A
_____|________/__\
____B-1_____C-2___D-2
_____\____/______/
______E-1_______/
________\______/
____________F

在经典类当中父类属性搜索的顺序原则是:先深入继承树左侧,再返回到最开始,然后再沿着右侧继续寻找
图解规律是:
F –> E-1 –> B-1 –> object (再返回)
F –> E-1 –> C-2 –> A (再返回)
F –> D-2 –> A –> object (结束)
代码实例如下:
>>> class A:
... ····def __init__(self):
... ········print 'enter A'
... ········print 'leave A'
>>> class B(A):
... ····def __init__(self):
... ········print 'enter B'
... ········A.__init__(self)
... ········print 'leave B'
>>> class C(A):
... ····def __init__(self):
... ········print 'enter C'
... ········A.__init__(self)
... ········print 'leave C'
>>> class D(B, C):
... ····def __init__(self):
... ········print 'enter D'
... ········B.__init__(self)
... ········C.__init__(self)
... ········print 'leave D'
>>> d=D()
enter D
enter B
enter A
leave A
leave B
enter C
enter A
leave A
leave C
leave D

而在新式类当中父类属性搜索的顺序原则是:先水平搜索人后再向上移动
图解规律是:
F –> E-1 –> B-1 –> C-2 –> D-2 –> A –> object (结束)
代码实例如下:
>>> class A(object):
... ····def __init__(self):
... ········print 'enter A'
... ········print 'leave A'
>>> class B(A):
... ····def __init__(self):
... ········print 'enter B'
... ········super(B, self).__init__()
... ········print 'leave B'
>>> class C(A):
... ····def __init__(self):
... ········print 'enter C'
... ········super(C, self).__init__()
... ········print 'leave C'
>>> class D(B ,C):
... ····def __init__(self):
... ········print 'enter D'
... ········super(D, self).__init__()
... ········print 'leave D'
>>> d=D()
enter D
enter B
enter C
enter A
leave A
leave C
leave B
leave D
通过上面两个例子说明经典类和新式类的多继承搜索顺序不同;水平搜索和向上搜索的区别;但对于新式类的好处在于,super()函数不需要每次调用父类的名字,而且在父类当中的报错可以直接通过父类进行修改解决这个问题,不需要再修改子类当中的内容了。至于检索顺序不同导致的搜索速度问题,要根据具体的实际内容来做调整。

7,类、实例和其他对象的内建函数

7.1 issubclass()

issubclass()是一个布尔函数,来判断一个类是另一个类的子类或子孙类。
issubclass(sub, sup)
issubclass()当子类sub父类sup的一个子类则返回True,否则返回False
>>> class A(object): pass
>>> class B(A): pass
>>> issubclass(B, A) #B是A的子类
True
>>> issubclass(A, A) #自己和自己比较同样返回True
True
>>> issubclass(A, B) #A不是B的子类
False

7.2 isinstance()

isinstance()同样是一个布尔型函数,来判断一个对象是否是另一个给定的类的事例。
isinstance(obj, class)
如果obj作为一个事例,是类class的事例或class的子类的事例时返回True,否则返回False
>>> class Obj(object): pass
>>> class Class(object): pass
>>> o=Obj()
>>> c=Class()
>>> isinstance(o, Class) #o不是Class类的实例对象
False
>>> isinstance(o, Obj) #o是Obj类的实例对象
True
>>> isinstance(o, c) #第二个参数一定是类,而不是实例对象,否则会报如下的错误信息
TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types
同时也可以看obj是否是class的类型
例如:
>>> isinstance(4, int)
True
>>> isinstance({1:2}, dict)
True
>>> isinstance(4, str)
False

7.4 hasattr()

hasattr()是一个布尔型函数,用于确定一个类的实例化对象是否有一个特定的属性,一般用于访问某属性前先作一个检查。

7.5 getattr()

getattr()当读取一个类的实例化对象中不存在的属性时引发AttributeError异常,除非给出那个可选的默认参数。

7.6 setattr()

setattr()可以给一个类的实例化对象中加入一个新属性,并对新属性进行赋值。

7.7 delattr()

delattr()从实例化对象中删除某个属性
例举代码实例说明上述内建函数的用法:
>>> class Myclass(object):
... ····def __init__(self):
... ········self.foo=100
>>> myls=Myclass()
>>> myls.__dict__ #只有一个类属性
{'foo': 100}
>>> hasattr(myls, 'foo') #hasattr()判断myls这个实例化对象是否存在’foo’这个属性,存在返回True不存在返回False
True
>>> hasattr(myls, 'bar')
False
>>> getattr(myls, 'foo') #getattr()得到myls这个实例化对象的’foo’这个属性的值,有值则输出,没有值则返回AttributeError
100
>>> getattr(myls, 'bar')
AttributeError: 'Myclass' object has no attribute 'bar'
>>> getattr(myls, 'bar', 'oops!') #但可以在没值的情况下输出函数的第三个参数’oops!’,而不至于报错
'oops!'
>>> setattr(myls, 'bar', 'my attr') #setattr()在myls这个实例化对象中加入’bar’这个属性,其值为’my attr’
>>> myls.__dict__ #加入后显示上面添加的属性存在并且也知道该属性的值,即’bar’: ‘my attr’
{'foo': 100, 'bar': 'my attr'}
>>> setattr(myls, 'foo', 'my attr') #同时也可以修改对象中已有的属性的值
>>> myls.__dict__
{'foo': 'my attr', 'bar': 'my attr'}
>>> delattr(myls, 'foo') #delattr()删除对象中的某个属性,如果对象中没有’foo’这个属性同样会报AttributeError的错
>>> myls.__dict__
{'bar': 'my attr'}

8,用特殊方法定制类

前面提到类有两个特殊方法可以分别作为构造器和解构器的功能,分别命名为__init__()__del__()
当然python当中存在一些用来定制类的特殊方法,这些特殊方法允许类通过重载标准操作符+,*,等其他操作符来模拟标准类型。
这些方法都是以下划线(__)开始及结尾的:
《Pyhton核心编程(第二版)》书中第367页,表13.4当中罗列了一些用来定制类的特殊方法:
例如:
C.__str__(self) 表示可打印的字符输出;内建str()及print语句
C.__add__(self, obj) 表示加;+操作符
下面通过重载操作符的例子说明定制类的特殊方法

9,重载运算符

定义一个类能够实现如下内容:
1,类的对象为一个列表
2,对象可以进行加减乘除,其中每个元素分别运算
解析思路为,定义一个运算加法的方法只用来返回值,当要显示数据时,也定义一个类,专门用来显示运算结果;这里只显示加法运算。
下面为代码实例:

>>> class Mylist:
... ····__mylist=[] #定义一个私有属性,一个空列表,在下面方法中用于存入传值
... ····def __init__(self, *args):
... ········self.__mylist=[] #初始化定义一个列表,用于收入外部传值,接收进来的元素,放入这个空列表中
... ········for arg in args:
... ············self.__mylist.append(arg)
... ····def __add__(self, x): #这里则利用了用来定制类的特殊方法,“+”号对用的特殊方法是__add__,想输入l+10的时候这个“+”号能够对应这个特殊方法,而x则表示传值,表示都与该指定的值相加
... ········for i in range(len(self.__mylist)):
... ············self.__mylist[i]+=x
... ········return self.__mylist
... ····def __sub__(self, x):
... ········pass
... ····def __mul__(self, x):
... ········pass
... ····def show(self): #也定义一个展示方法
... ········print self.__mylist #将执行的“+”的结果展示
...
>>> l=Mylist(1,2,3,4,5)
>>> l+10 # “+”对应类的__add__方法,10是对方法中x的传值,当然__add__ 方法中也存在print的结果
[11, 12, 13, 14, 15]
>>> l.show()
[11, 12, 13, 14, 15]
所以在思考类似问题时,主要是先写流程,把思路想明白,然后利用代码桩pass来站位实现思路搭建

如果想开发成模块,可以在末尾加入:
if __name__ == '__main__':

猜你喜欢

转载自blog.csdn.net/genome_denovo/article/details/79835091