Py修行路 python基础 (十五)面向对象编程 继承 组合 接口和抽象类
一、前提回忆:
1、类是用来描述某一类的事物,类的对象就是这一类事物中的一个个体。是事物就要有属性,属性分为
1:数据属性:就是变量
2:函数属性:就是函数,在面向对象里通常称为方法
注意:类和对象均用点来访问自己的属性
2、查看 类或对象 属性的方法
类名(对象名).__dict__:查出的是一个字典,key为属性名,value为属性值
注意:类的方法,不能通过类名(对象名).__dict__['keys'] = value 的方式添加,否则会报不支持的错误;而对象可以通过此种方式添加。
先明确一个概念:经典类与新式类
1.只有在python2中才分新式类和经典类,python3中统一都是新式类 2.新式类和经典类声明的最大不同在于,所有新式类必须继承至少一个父类 3.所有类甭管是否显式声明父类,都有一个默认继承object父类。 在python2中的区分 经典类: class 类名: pass 新式类: class 类名(父类): pass 在python3中,上述两种定义方式全都是新式类
用类名.__bases__ 查看类的继承
注意:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类。
二、继承与派生:
面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。继承完全可以理解成类之间的类型和子类型关系。
继承是一种创建新的类的方式,在python中,新建的类可以继承自一个或者多个父类,原始类称为基类或超类,新建的类称为派生类或子类。
1、格式如下:
class ParentClass1: #定义父类 pass class ParentClass2: #定义父类 pass class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类 pass class 父类: def 父类中的方法(self): #do something class 子类(父类): 子类继承父类,即拥有了父类中所有方法。 pass c1 = 子类() #创建子类对象 c1.父类中的方法() #执行从父类中继承的方法
2、对于面向对象的继承来说,其实就是将多个类共有的方法提取到父类中,子类仅需继承父类而不必一一实现每个方法。这样子类就会继承父类的所有属性(数据属性和函数属性),实现代重用。
1 class Aniaml:#父类 2 def __init__(self,name,age): 3 self.name=name 4 self.age=age 5 6 def walk(self): 7 print('%s is walking' %self.name) 8 9 def say(self): 10 print('%s is saying' %self.name) 11 12 class People(Aniaml): #定义子类(人)完全继承 13 pass 14 15 class Dog(Aniaml): #定义子类(狗)完全继承 16 pass 17 18 p1=People('obama',50) 19 print(p1.name) 20 print(p1.age) 21 p1.walk() 22 23 #执行结果: 24 obama 25 50 26 obama is walking
3、当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名的话,那么在调用新增的属性时,就以自己定义的为准。
class Hero: #定义英雄Hero父类 def __init__(self, nickname,aggressivity,life_value): self.nickname = nickname self.aggressivity = aggressivity self.life_value = life_value def attack(self, enemy): enemy.life_value -= self.aggressivity class Garen(Hero): #人物类 camp='Demacia' def attack(self, enemy): #重新定义attack print('%s is attack %s'%(self.nickname,enemy.nickname)) def fire(self): print('%s is firing' %self.nickname) class Riven(Hero): #人物类 camp='Noxus' g1=Garen('garen',18,200) r1=Riven('rivren',18,200) # print(g1.camp) # print(r1.camp) g1.fire() g1.attack(r1) #执行自己类内的方法 #执行结果: garen is firing garen is attack rivren
4、在子类中,新建的重名的函数属性,在编辑函数内功能的时候,在增加其他功能的同时,有可能需要重用父类中重名的那个函数功能,此时调用父类的这个功能,就应该在定义的函数下(参数也一样需要定义),用调用普通函数的方式,即:类名.func(参数),此时就与调用普通函数一样,因此即便是self参数也要为其传值。所以这种情况就叫派生!
1 #继承与派生:定一个父类,子类都继承父类的大部分功能。 2 #若子类中有不同于父类的功能,可在自己的类中定义,由此产生的功能叫做派生。 3 #若定义的方法与父类重名,对象会引用自己类中对应的方法,若还需要引用父类的方法,就需要用函数调用的方法去引用,然后添加其他的功能。 4 class Hero: 5 def __init__(self,nickname,aggressivity,life_value): 6 self.nickname=nickname 7 self.aggressivity=aggressivity 8 self.life_value=life_value 9 def attack(self,enemy): 10 enemy.life_value-=self.aggressivity 11 12 class Garen(Hero): 13 camp = 'Demacia' 14 def __init__(self,nickname,aggressivity,life_value,script): 15 Hero.__init__(self,nickname,aggressivity,life_value) #借用父类的方法,同时添加新的属性 16 self.script = script 17 def attack(self,enemy): 18 Hero.attack(self,enemy) #借用父类的方法,同时添加新的属性 19 print('from garen attack') 20 def fire(self,enemy): #添加自己不同的方法 21 print('%s is firing %s'%(self.nickname,enemy.nickname)) 22 23 class Riven(Hero): 24 pass 25 26 g1 = Garen('harrn',18,200,'renzaitazai') 27 r1 = Riven('roose',16,200) 28 29 g1.attack(r1) 30 g1.fire(r1) 31 print(g1.script) #打印属性 32 print(Hero.__dict__) #查看父类方法 33 print(g1.__dict__)#查看对象的方法 34 35 #执行结果: 36 from garen attack 37 harrn is firing roose 38 renzaitazai 39 {'__module__': '__main__', '__init__': <function Hero.__init__ at 0x00000000028FC9D8>, 'attack': <function Hero.attack at 0x00000000028FCA60>, '__dict__': <attribute '__dict__' of 'Hero' objects>, '__weakref__': <attribute '__weakref__' of 'Hero' objects>, '__doc__': None} 40 {'nickname': 'harrn', 'aggressivity': 18, 'life_value': 200, 'script': 'renzaitazai'}
5、在python中继承中的一些特点:
1:在继承中基类的构造(__init__()方法)不会被自动调用,它需要在其派生类的构造中亲自专门调用。有别于C#
2:在调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。区别于在类中调用普通函数时并不需要带上self参数
3:Python总是首先查找对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找调用的方法,找不到才去基类中找)。
6、何时使用继承:
假如我需要定义几个类,而类与类之间有一些公共的属性和方法,这时我就可以把相同的属性和方法作为基类的成员,而特殊的方法及属性则在本类中定义,这样只需要继承基类这个动作,就可以访问到基类的属性和方法了,它提高了代码的可扩展性。
三、组合:
1、组合认知:
软件重用的重要方式除了继承之外还有另外一种方式,即:组合。
组合指的是:在一个类中,部分数据属性是以其他类实例化的对象作为数据属性,称为类的组合
简单理解就是:大类中包含小类,就是组合。
组合的用于:1、做关联;2、由小的组成大的
2、组合和继承的区别:
组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同。
1、继承:
通过继承建立了派生类与基类之间的关系,它是一种 '是' 的关系,例如:人是动物。当类之间有很多相同的功能,提取这些共同的功能做成基类,子类实现调用这些功能,还是用继承比较好,比如盖伦是英雄等……
2、组合:
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好。例如:学生、老师有生日,学生有课程等……
1 #组合:指的是一个类产生的对象,作为另一个类的数据属性进行引用 2 3 class Course: 4 def __init__(self,name,price,period): 5 self.name = name 6 self.price = price 7 self.period = period 8 9 class Brith: 10 def __init__(self,years,months,days): 11 self.year = years 12 self.month = months 13 self.day = days 14 15 class Teacher: 16 def __init__(self,name,sex,birthady,course): 17 self.name = name 18 self.sex = sex 19 self.course = course 20 self.brithday = birthady 21 22 class Student: 23 def __init__(self,name,sex,brithday,course): 24 self.name = name 25 self.sex = sex 26 self.course = course 27 self.brithday = brithday 28 29 python_obj = Course('python',15800,'7m') #类实例化生成课程 30 birthday_teacher = Brith(1970,7,17) #类实例化生成生日 31 birthday_student = Brith(1994,7,29) #类实例化生成生日 32 t1 = Teacher('egon','male',birthday_teacher,python_obj) #将小类实例化,以参数形式传入大类中 33 s1 = Student('cobila','male',birthday_student,python_obj)#将小类实例化,以参数形式传入大类中 34 print(t1.course.name) #打印调用结果 35 36 #执行结果: 37 python
四、接口:
首先明确:python 本无接口的概念,而是通过class 创建类,来模拟实现接口的功能。
1、继承有两种用途:
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)。
二:声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能。
实践中,
继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
继承的第二种含义非常重要。它又叫“接口继承”。接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
2、为什么要用接口:
接口提取了一群类共同的函数,可以把接口当做一个函数的集合的类。然后让子类继承这个父类,去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化,让使用者无需关心对象的类是什么,只需要知道这些对象都具备某些功能就可以了,通过对象名.的方式直接调用函数,得到执行结果。这极大地降低了使用者的使用难度。
举例说明:比如我们定义一个动物接口,接口里定义了有跑、吃、说等接口函数,这样猪的类去实现了该接口,狗的类也去实现了该接口,由二者分别产生一头猪和一只狗送到你面前,即便是你分别不出哪只是什么动物,最起码你肯定知道他俩都会跑,都会吃,都能呼吸。
1 #以文件操作举例! 2 class Interface:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。 3 def read(self): #定接口函数read 4 pass 5 def write(self): #定义接口函数write 6 pass 7 8 class Txt(Interface): #文本,具体实现read和write 9 def read(self): 10 print('文本数据的读取方法') 11 def write(self): 12 print('文本数据的读取方法') 13 14 class Sata(Interface): #磁盘,具体实现read和write 15 def read(self): 16 print('硬盘数据的读取方法') 17 def write(self): 18 print('硬盘数据的读取方法') 19 20 class Process(Interface): #进程,具体实现read和write 21 def read(self): 22 print('进程数据的读取方法') 23 def write(self): 24 print('进程数据的读取方法') 25 #实例化 26 t1=Txt() 27 s1=Sata() 28 p1=Process() 29 # 调用方法 30 t1.read() 31 t1.write() 32 33 s1.read() 34 s1.write() 35 36 p1.read() 37 p1.write() 38 39 #执行结果 40 文本数据的读取方法 41 文本数据的读取方法 42 硬盘数据的读取方法 43 硬盘数据的读取方法 44 进程数据的读取方法 45 进程数据的读取方法
接口的弊端就是:定义了接口类,不管子类是否完全继承,都不会报错。若想有报错的功能,就需要导入第三方模块。
#coding:utf-8 #正常调用的情况下: class Animal: def run(self): raise AttributeError('子类必须实现这个方法') def speak(self): raise AttributeError('子类必须实现这个方法') class People(Animal): def run(self): print('人正在走') def speak(self): print('说话') peo1=People() peo1.run() peo1.speak() 执行结果: 人正在走 说话
1 #coding:utf-8 2 #未全部调用的情况: 3 class Animal: 4 def run(self): 5 raise AttributeError('子类必须实现这个方法') 6 def speak(self): 7 raise AttributeError('子类必须实现这个方法') 8 9 class People(Animal): 10 def run(self): 11 print('人正在走') 12 # def speak(self): 13 # print('说话') 14 15 peo1=People() 16 peo1.run() 17 peo1.speak() 18 19 #执行结果: 20 人正在走 21 Traceback (most recent call last): 22 File "H:/day28/接口与归一化设计.py", line 17, in <module> 23 peo1.speak() 24 File "H:/day28/接口与归一化设计.py", line 7, in speak 25 raise AttributeError('子类必须实现这个方法') 26 AttributeError: 子类必须实现这个方法
五、抽象类:
抽象类同其他的编程语言一样,都需要借助模块实现。 模块(import abc)
抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化。
抽象类的由来:
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。否则会报错!!!
抽象类:本质还是类,与普通类额外的特点的是:加了装饰器的函数,子类必须实现他们
#coding:utf-8 import abc #利用abc模块实现抽象类 #抽象类:本质还是类,与普通类额外的特点的是:加了装饰器的函数,子类必须实现他们 class Animal(metaclass=abc.ABCMeta): tag='123123123123123' @abc.abstractmethod #定义抽象方法,无需实现功能 def run(self):#子类必须定义跑功能 pass @abc.abstractmethod def speak(self): #子类必须定义说功能 pass class People(Animal): #子类继承抽象类,但是必须定义run和speak方法 def run(self): print('人正在走') def speak(self): print('人正在说') class Pig(Animal): #子类继承抽象类,未全部定义方法,报错! def run(self): print('pig is running') peo1=People() pig1= Pig() peo1.run() pig1.run() #执行结果: Traceback (most recent call last): File "H:day28/抽象类.py", line 23, in <module> pig1= Pig() TypeError: Can't instantiate abstract class Pig with abstract methods speak
1 #正常情况下的执行结果: 2 #coding:utf-8 3 import abc #利用abc模块实现抽象类 4 #抽象类:本质还是类,与普通类额外的特点的是:加了装饰器的函数,子类必须实现他们 5 class Animal(metaclass=abc.ABCMeta): 6 tag='123123123123123' 7 @abc.abstractmethod #定义抽象方法,无需实现功能 8 def run(self):#子类必须定义跑功能 9 pass 10 @abc.abstractmethod 11 def speak(self): #子类必须定义说功能 12 pass 13 14 class People(Animal): #子类继承抽象类,但是必须定义run和speak方法 15 def run(self): 16 print('人正在走') 17 def speak(self): 18 print('人正在说') 19 # class Pig(Animal): #子类继承抽象类,未全部定义方法,报错! 20 # def run(self): 21 # print('pig is running') 22 23 peo1=People() 24 # pig1= Pig() 25 peo1.run() 26 # pig1.run() 27 28 #执行结果: 29 人正在走
六、抽象类与接口:
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
接口和抽象类的概念不一样。接口是对动作的抽象,抽象类是对根源的抽象。
抽象类表示的是,这个对象是什么。接口表示的是,这个对象能做什么。比如,男人,女人,这两个类(如果是类的话……),他们的抽象类是人。说明,他们都是人。人可以吃东西,狗也可以吃东西,你可以把“吃东西”定义成一个接口,然后让这些类去实现它.
所以,在高级语言上,一个类只能继承一个类(抽象类)(正如人不可能同时是生物和非生物),但是可以实现多个接口(吃饭接口、走路接口)。
总结几句话来说:
1、抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
2、抽象类要被子类继承,接口要被类实现。
3、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
4、抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。