面向对象的三大特性:继承、多态、封装
一、继承
1、什么是继承?
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类。
python中类的继承分为:单继承和多继承。
class A:pass # 父类,基类,超类 class B:pass # 父类,基类,超类 class A_son(A,B):pass # 子类,派生类 class AB_son(A,B):pass # 子类,派生类
注:一个类可以被多个类继承;
一个类可以继承多个类。
2、查看继承
class A:pass class B:pass class A_son(A,B):pass class AB_son(A,B):pass print(A_son.__base__) #__base__只查看从左到右继承的第一个子类, #(<class '__main__.A'>, <class '__main__.B'>) print(AB_son.__bases__) #__bases__则是查看所有继承的父类 #(<class '__main__.A'>, <class '__main__.B'>) print(A.__bases__) #python的类会默认继承object类 #(<class 'object'>,)
注:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。
3、继承与抽象(先抽象再继承)
抽象即抽取类似或者说比较像的部分。
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类。
4、继承与重用性
有两个类,类B的内容与类A大部分相同时,可以用到类的继承,让B继承A,B会继承A的所有属性(数据属性和函数属性),实现代码的重用。
class Animal: ''' 人和狗都是动物,所以创造一个Animal基类 ''' def __init__(self, name, aggressivity, life_value): self.name = name # 人和狗都有自己的昵称; self.aggressivity = aggressivity # 人和狗都有自己的攻击力; self.life_value = life_value # 人和狗都有自己的生命值; def eat(self): print('%s is eating'%self.name) class Dog(Animal): pass class Person(Animal): pass ya = Person('yaya',10,1000) ha2 = Dog('二愣子',50,1000) ya.eat() ha2.eat()
5、派生
在继承中,子类也可以添加自己新的属性,也可以在不影响父类的情况下重新定义这些属性。
#人狗大战 class Animal: def __init__(self,name,aggr,hp): self.name = name self.aggr = aggr self.hp = hp def eat(self): print('吃药回血') self.hp+=100 class Dog(Animal): def __init__(self,name,aggr,hp,kind): Animal.__init__(self,name,aggr,hp) # self.kind = kind # 派生属性 def eat(self): Animal.eat(self) # 如果既想实现新的功能也想使用父类原本的功能,还需要在子类中再调用父类 self.teeth = 2 def bite(self,person): # 派生方法 person.hp -= self.aggr jin = Dog('金老板',100,500,'吉娃娃') jin.eat() print(jin.hp) class Person(Animal): def __init__(self,name,aggr,hp,sex): Animal.__init__(self,name,aggr,hp) self.sex = sex # 派生属性 self.money = 0 # 派生属性 def attack(self,dog): dog.hp -= self.aggr def get_weapon(self,weapon): if self.money >= weapon.price: self.money -= weapon.price self.weapon = weapon self.aggr += weapon.aggr else: print("余额不足,请先充值") alex = Person('alex',1,2,None) alex.eat() print(alex.hp) jin.bite(alex) print(alex.hp)
注意:
1)、父类中没有的属性 在子类中出现 叫做派生属性;
2)、父类中没有的方法 在子类中出现 叫做派生方法;
3)、只要是子类的对象调用,子类中有的名字 一定用子类的,子类中没有才找父类的,如果父类也没有报错
4)、如果父类 子类都有 用子类的
如果还想用父类的,单独调用父类的:
父类名.方法名 需要自己传self参数
super().方法名 不需要自己传self(只在新式类中有,python3中所有类都是新式类)
5)、正常的代码中 单继承 === 减少了代码的重复
6)、继承表达的是一种 子类是父类的关系
7)、如果子类中实现了调用父类的方法
在类内:super(子类,self).方法名() super().__init__(参数)
在类外:super(子类名,对象名).方法名()
通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好。
继承的实例
class Animal: #父类 基类 超类 2 def __init__(self,name,life_value,aggr): 3 self.name= name 4 self.life_value = life_value 5 self.aggr = aggr #攻击力 6 def eat(self): 7 self.life_value += 10 #谁调谁的血量就增加 8 9 class Person(Animal): #子类 派生类 10 def __init__(self, money, name, life_value, aggr): 11 super().__init__(name, life_value, aggr) 12 self.money = money #派生出来的一个属性 13 def attack(self,enemy): #人的派生方法 14 #enemy.life_value = enemy.life_value - self.aggr 15 enemy.life_value -= self.aggr 16 17 class Dog(Animal): #子类 派生类 18 def __init__(self,name,breed, life_value,aggr): 19 # Animal.__init__(self,name,breed, life_value,aggr)#让子类执行父类的方法 就是父类名.方法名(参数),连self都得传 20 super().__init__(name,life_value,aggr) #super关键字 ,都不用传self了,在新式类里的 21 # super(Dog,self).__init__(name,life_value,aggr) #上面super是简写 22 self.breed = breed 23 def bite(self,person): #狗的派生方法 24 person.life_value -= self.aggr 25 def eat(self): #父类方法的重写 26 super().eat() 27 print('dog is eating') 28 29 # ha2 = Dog('旺财','哈士奇',20000,100) 30 # egg = Person('egon',500,1000,50) 31 # print(egg.aggr) 32 # print(ha2.aggr) 33 # egg.eat() 34 # print(egg.life_value) 35 # 36 # egg.eat() 37 # print(egg.life_value) 38 # 39 # ha2.eat() 40 # print(ha2.life_value) 41 # 42 # print(egg.life_value) 43 # ha2.bite(egg) 44 # print(egg.life_value) 45 # 46 47 ha2 = Dog('牛头梗','旺财',20000,100) 48 print(ha2.life_value) 49 ha2.eat() #如果父类有的方法子类里面也有,那么就叫做方法的重写,就不执行父类的了,去执行子类了 50 print(ha2.life_value) 51 52 53 super(Dog,ha2).eat() #生掉父类的方法,但是不推荐这样用 54 print(ha2.life_value) 55 56 #在继承中,如果子类有的方法就执行子类的 57 # 如果子类没有的方法就执行父类的
6、抽象类与接口类
6.1、接口类
接口类:python原生不支持
本质:是做代码规范用的,希望在子类中实现和父类方法名字完全一样的方法。
from abc import abstractmethod,ABCMeta class Payment(metaclass=ABCMeta): # 元类 默认的元类 type @abstractmethod def pay(self,money):pass # 没有实现这个方法 # 规范 :接口类或者抽象类都可以 # 接口类 支持多继承,接口类中的所有的方法都必须不能实现 —— java # 抽象类 不支持多继承,抽象类中方法可以有一些代码的实现 —— java class Wechat(Payment): def pay(self,money): print('已经用微信支付了%s元'%money) class Alipay(Payment): def pay(self,money): print('已经用支付宝支付了%s元' % money) class Applepay(Payment): def pay(self,money): print('已经用applepay支付了%s元' % money) def pay(pay_obj,money): # 统一支付入口 pay_obj.pay(money) wechat = Wechat() ali = Alipay() app = Applepay() wechat.pay(100) ali.pay(200)
接口类的多继承:
# tiger 走路 游泳 # swan 走路 游泳 飞 # oldying 走路 飞 from abc import abstractmethod,ABCMeta class Swim_Animal(metaclass=ABCMeta): @abstractmethod def swim(self):pass class Walk_Animal(metaclass=ABCMeta): @abstractmethod def walk(self):pass class Fly_Animal(metaclass=ABCMeta): @abstractmethod def fly(self):pass class Tiger(Walk_Animal,Swim_Animal): def walk(self): pass def swim(self): pass class OldYing(Fly_Animal,Walk_Animal):pass class Swan(Swim_Animal,Walk_Animal,Fly_Animal):pass
接口类, 刚好满足接口隔离原则 面向对象开发的思想 、规范。
6.2、抽象类
抽象类:规范
一般情况下 单继承 能实现的功能都是一样的,所以在父类中可以有一些简单的基础实现
多继承的情况 由于功能比较复杂,所以不容易抽象出相同的功能的具体实现写在父类中
利用abc模块实现抽象类
import abc #利用abc模块实现抽象类 class All_file(metaclass=abc.ABCMeta): all_type='file' @abc.abstractmethod #定义抽象方法,无需实现功能 def read(self): '子类必须定义读功能' with open('filaname') as f: pass @abc.abstractmethod #定义抽象方法,无需实现功能 def write(self): '子类必须定义写功能' pass class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('文本数据的读取方法') def write(self): print('文本数据的读取方法') class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('硬盘数据的读取方法') def write(self): print('硬盘数据的读取方法') class Process(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('进程数据的读取方法') def write(self): print('进程数据的读取方法') wenbenwenjian=Txt() #实例化 yingpanwenjian=Sata() jinchengwenjian=Process() #这样大家都是被归一化了,也就是一切皆文件的思想 wenbenwenjian.read() #文本数据的读取方法 yingpanwenjian.write() #硬盘数据的读取方法 jinchengwenjian.read() #进程数据的读取方法 print(wenbenwenjian.all_type) #file print(yingpanwenjian.all_type) #file print(jinchengwenjian.all_type) #file
补充:
抽象类还是接口类 : 面向对象的开发规范 所有的接口类和抽象类都不能实例化
java :
java里的所有类的继承都是单继承,所以抽象类完美的解决了单继承需求中的规范问题
但对于多继承的需求,由于java本身语法的不支持,所以创建了接口Interface这个概念来解决多继承的规范问题
python:
python中没有接口类 :
python中自带多继承 所以我们直接用class来实现了接口类
python中支持抽象类 : 一般情况下 单继承 不能实例化
且可以实现python代码
6.3、接口类和抽象类的区别:
在java的角度上看 是有区别的
java本来就支持单继承 所以就有了抽象类
java没有多继承 所以为了接口隔离原则,设计了接口这个概念,支持多继承了
python既支持单继承也支持多继承,所以对于接口类和抽象类的区别就不那么明显了
甚至在python中没有内置接口类
7、继承顺序
单继承:子类有的用子类,子类没有用父类;
多继承:子类的对象调用一个方法,默认是就近原则;
经典类中:深度优先
新式类中:广度优先
python2.7中:新式类与经典类共存,新式类要继承object
python3:只有新式类,默认继承object
经典类和新式类中还有一个区别:
mro方法只在新式类中存在;
super方法只在python3中新式类中存在;
super的本质:不是直接找父类,而是根据调用者的节点位置的广度优先顺序来的。
二、多态
1、什么是多态?
多态指的是一类事物有多种形态。
#动物有多种形态:人,狗,猪 import abc class Animal(metaclass=abc.ABCMeta): #同一类事物:动物 @abc.abstractmethod def talk(self): pass class People(Animal): #动物的形态之一:人 def talk(self): print('say hello') class Dog(Animal): #动物的形态之二:狗 def talk(self): print('say wangwang') class Pig(Animal): #动物的形态之三:猪 def talk(self): print('say aoao')
#文件有多种形态:文本文件,可执行文件 import abc class File(metaclass=abc.ABCMeta): #同一类事物:文件 @abc.abstractmethod def click(self): pass class Text(File): #文件的形态之一:文本文件 def click(self): print('open file') class ExeFile(File): #文件的形态之二:可执行文件 def click(self): print('execute file')
2、多态性
多态性是指在不考虑实例类型的情况之下使用实例。
peo=People() dog=Dog() pig=Pig() #peo、dog、pig都是动物,只要是动物肯定有talk方法 #于是我们可以不用考虑它们三者的具体是什么类型,而直接使用 peo.talk() dog.talk() pig.talk() #更进一步,我们可以定义一个统一的接口来使用 def func(obj): obj.talk()
3、鸭子类型
鸭子类型是指不依赖父类的情况下实现两个相似类中的同名方法。
#二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用 class TxtFile: def read(self): pass def write(self): pass class DiskFile: def read(self): pass def write(self): pass
三、封装
1、什么是封装?
隐藏对象的属性的实现细节,仅对外提供公共访问方式。
2、封装的好处?
1)、将变化隔离;
2)、便于使用;
3)、提高复用性;
4)、提高安全性。
3、封装原则?
1)、 将不需要对外提供的内容都隐藏起来;
2)、把属性都隐藏,提供公共方法对其访问。
4、私有变量和私有方法
在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
4.1、私有变量
#其实这仅仅这是一种变形操作 #类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式: class A: __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N def __init__(self): self.__X=10 #变形为self._A__X def __foo(self): #变形为_A__foo print('from A') def bar(self): self.__foo() #只有在类内部才可以通过__foo的形式访问到. #A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形
自动变形的特点:
1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
2.这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
3.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
注意:
1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
2.变形的过程只在类的内部生效,在定义后的赋值操作,不会变形
4.2私有方法
在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
#正常情况 >>> class A: ... def fa(self): ... print('from A') ... def test(self): ... self.fa() ... >>> class B(A): ... def fa(self): ... print('from B') ... >>> b=B() >>> b.test() from B #把fa定义成私有的,即__fa >>> class A: ... def __fa(self): #在定义时就变形为_A__fa ... print('from A') ... def test(self): ... self.__fa() #只会与自己所在的类为准,即调用_A__fa ... >>> class B(A): ... def __fa(self): ... print('from B') ... >>> b=B() >>> b.test() from A
5、会用到私有的这个概念的场景:
1)、隐藏起一个属性 不想让类的外部调用
2)、我想保护这个属性,不想让属性随意被改变
3)、我想保护这个属性,不被子类继承