面向对象三大特性 封装 继承 多态 鸭子类型
1 继承
1:定义
继承描叙的是两个类之间的关系,一个类可以直接使用另一个类中已定义的方法和属性; 被继承的称之为父类或基类,继承父类的类称之为子类;
1.减少代码重复 2.为多态提供必要的支持
3 继承的使用
1 先抽象在继承
# 抽取老师和学生的相同内容 形成一个新的类,作为它们的公共父类 class Person: def __init__(self,name,gender,age): self.name = name self.gender = gender self.age = age def say_hi(self): print("hi my name is %s age is %s gender is %s" % (self.name,self.age,self.gender)) class Teacher(Person): #指定Teacher类继承Person类 pass class Student(Person): #指定Student类继承Person类 pass #创建两个对象 t1 = Teacher("Jack","man",20) t1.say_hi() s1 = Student("Maria","woman",20) s1.say_hi()
当父类提供的属性无法完全满足子类的需求时,子类可以增加自己的属性或非法,或者覆盖父类已经存在的属性,此时子类称之为父类的派生类;
覆盖
在子类中如果出现于父类相同的属性名称时,根据查找顺序,优先使用子类中的属性,这种行为也称为`覆盖`
# 抽取老师和学生的相同内容 形成一个新的类,作为它们的公共父类 class Person: def __init__(self,name,gender,age): self.name = name self.gender = gender self.age = age def say_hi(self): print("my name is %s age is %s gender is %s" % (self.name,self.age,self.gender)) class Teacher(Person): #指定Teacher类继承Person类 # Teacher类从Person类中继承到了say_hi方法 但是,老师打招呼时应当说出自己的职业是老师,所以需要 # 定义自己的不同的实现方式 def say_hi(self): print("hi i am a Teacher") #print("my name is %s age is %s gender is %s" % (self.name,self.age,self.gender)) #上一行代码与父类中完全相同,可以直接调用父类提供的方法 Person.say_hi(self) # 创建Teacher对象 t1 = Teacher("Jack","man",20) t1.say_hi() #输出 hi i am a Teacher # my name is Jack age is 20 gender is man
在子类中有两种方式可以重用父类中的代码
1.使用类名直接调用 ,该方式与继承没有关系,即时没有继承关系,也可以调用
2.使用super()
class Vehicle: #定义交通工具类 Country='China' def __init__(self,name,speed,load,power): self.name=name self.speed=speed self.load=load self.power=power def run(self): print('开动啦...') class Subway(Vehicle): #地铁 def __init__(self,name,speed,load,power,line): #super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self) super().__init__(name,speed,load,power) self.line=line def run(self): print('地铁%s号线欢迎您' %self.line) super(Subway,self).run() class Mobike(Vehicle):#摩拜单车 pass line13=Subway('中国地铁','180m/s','1000人/箱','电',13) line13.run() ''' 地铁13号线欢迎您 开动啦... '''
4 经典类与新式类
那什么是经典类,什么是新式类呢?
# Author:Zhang Zhao class A(object): def __init__(self): print('A') class B(A): pass # def __init__(self): # print('B') class C(A): def __init__(self): print('C') class D(B,C): pass # def __init__(self): # print('D') r1 = D()
但是在经典类中,如果B中找不到,它会优先考虑B的父亲A,而不是C。
在python3中,都是遵循广度优先的规则,在python2.7以前,应该是遵循深度优先的的规则。两种规则没有优劣之分
#A没有继承B,但是A内super会基于C.mro()继续往后找 class A: def test(self): super().test() class B: def test(self): print('from B') class C(A,B): pass c=C() c.test() #打印结果:from B print(C.mro()) #[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
5 继承的顺序
对于你定义的每一个类,python通过一个算法算出一个查找顺序存放在(MRO)列表中,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如:
F.mro() #等同于F.__mro__ [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
class Equip: #武器装备类 def fire(self): print('release Fire skill') class Riven: #英雄Riven的类,一个英雄需要有装备,因而需要组合Equip类 camp='Noxus' def __init__(self,nickname): self.nickname=nickname self.equip=Equip() #用Equip类产生一个装备,赋值给实例的equip属性 r1=Riven('锐雯雯') r1.equip.fire() #可以使用组合的类产生的对象所持有的方法
组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同,
通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人
2.组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3...
2 封装
1 定义
''' 什么是封装 :对外部隐藏属性和方法 给外部提供使用的接口 目的:限制外部对内部数据的访问 因为有些数据是机密的 不方便对外透露 如何封装: __开头的语法 分为封装属性 和 封装方法 封装的好处: 封装属性--提高安全性 封装方法:提高便利性 '''
2 封装属性 方法
一.
对于属性而言,封装就为了限制属性的访问和修改,其目的是为了保护数据安全
例如:
学生对象拥有,姓名,性别,年龄,和身份证号,分数;其中身份证是一个相对隐私的数据,不应该让外界访问到;
分数属性,是一个非常关键的数据,决定学员能不能正常毕业,不应被随意修改;
class Student: def __init__(self,name,age,id_card): self.name = name self.age = age self.__id_card = id_card # 外部就无法通过.id_card 和 .__id_card def show_id_card(self): print(self.__id_card) stu = Student('tom',18,'xxxxxxxx') print(stu.name) # tom print(stu.age) # 18 # print(stu.__id_card) 是没有这个的 stu.show_id_card() # xxxxxxxx
对私有属性的访问以及修改
class Student: def __init__(self,name,age,id_card): self.name = name self.age = age self.__id_card = id_card # 外部就无法通过.id_card 和 .__id_card def get_id_card(self,pwd): # 访问被封装的属性 称之为访问器 可以为访问添加条件 if pwd == '123': return self.__id_card else: print('密码错误') def set_id_card(self,new_id_card): # 修改被封装的属性称之为设置器 # 身份证必须是字符 且 是18位 if isinstance(new_id_card,str) and len(new_id_card) == 18: self.__id_card = new_id_card else: print('新身份证不符合规定') stu = Student('tom',18,'123456789012345678') stu.get_id_card('wsx') # 密码错误 stu.set_id_card('123456789123456789') # 无结果
二.封装方法
说明这方法可以被内部访问 不应该被外界访问
一个大的功能很多情况下是由很多个小功能组合而成的,而这些内部的小功能对于用户而言是没有意义的,所以封装方法的目的是为了隔离复杂度;
例如:
电脑的开机功能,内部需要启动BIOS,读取系统配置,启动硬盘,载入操作系统,等等一系列复杂的操作,但是用户不需要关心这些实现逻辑,只要按下开机键等待开机即可;
class ATM: def withdraw(self): self.__user_auth() self.__input_money() self.__save_record() # 输入账号和密码 # 显示余额 # 输入取款金额 # 保存记录 def __user_auth(self): print('输入用户名和密码') def __input_money(self): print('余额为1000000,输入取款金额!') def __save_record(self): print('记录流水......') atm = ATM() atm.withdraw() # 提现只需要调用withdraw就可以了 不需要一步一步去操作其他三个步骤 简化了外部操作
三 封装特性property
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
@property封装时 特别要注意 封装的属性需要._
property的用途一 :将方法伪装成普通属性
''' 作用:将方法伪装成普通属性 为什么要用property 希望将私有属性的访问方式和普通属性一致 普通属性 点语法 与property相关的 两个装饰器 setter 用点语法 给属性赋值时触发 deleter 用点语法删除属性时触发 '''
class Teacher: def __init__(self,name,age,salary): self.name = name self.age = age self.__salary = salary # s实质是在名称空间里换名字为 _Teacher__salary @property def salary(self): # getter # 用于访问私有属性的值 也可以访问普通属性 return self.__salary @salary.setter # 用来设置私有属性的值 也可以设置普通属性 def salary(self,new_salary): self.__salary = new_salary @salary.deleter def salary(self): del self.__dict__['_Teacher__salary'] # 删除老师薪水部分 a = Teacher('jeck',18,50) # 这是访问私有属性 print(a.salary) # 50 a.salary=10 # 赋值时运用 setter print(a.salary) # 10 del a.salary # 已删除salary部分 # print(a.salary) 这时打印会报错的
class Foo: def __init__(self,val): self.__NAME=val #将所有的数据属性都隐藏起来 @property def name(self): return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置) @name.setter def name(self,value): if not isinstance(value,str): #在设定值之前进行类型检查 raise TypeError('%s must be str' %value) self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME @name.deleter def name(self): raise TypeError('Can not delete') f=Foo('Jack') print(f.name) # 访问property属性 #输出 Jack f.name="Rose" # 修改property属性 抛出异常'TypeError: 10 must be str' del f.name # 删除property属性 抛出异常'TypeError: Can not delete'
property的用途二 :计算属性
''' 有些属性不是固定的 是需要计算出来的 这是也可以用到property 例如计算BMI 体质指数(BMI)=体重(kg)÷身高^2(m) '''
class People: def __init__(self,name,weight,height): self.name=name self.weight=weight self.height=height @property def BMI(self): return self.weight/(self.height**2) @BMI.setter def BMI(self,new_bmi): print('bmi不支持自定义') p = People('egon',80,1.7) print(p.BMI) # 27.68166089965398 p.BMI = 10 # 运行这步 不会更改值 bmi不支持自定义 print(p.BMI) # 27.68166089965398 结果还是这个不会变的 计算属性时 setter 不受用
在属性名前添加两个下划线__,将其设置为私有的
class Student: def __init__(self, name, gender, age, id, score): # 初始化函数 self.name = name self.gender = gender self.age = age self.__id = id # 将id设置为私有的 self.__score = score # 将score设置为私有的 def test(self): print(self.__id) print(self.__score) stu = Student("Jack", "man", 20, "320684198901010001", 780) # 1.访问私有属性测试 # print(stu.id) # 直接访问到隐私数据 # print(stu.__id) # 换种写法 # 以上两行代码均输出相似的错误 # AttributeError: 'Student' object has no attribute 'id' # 错误含义 在Student类的对象中没有一个id或__id属性 # 2.修改私有属性测试 stu.score = 1 # 直接修改私有属性 由于语法特点,相当于给stu对象增加score属性 stu.__score = 2 # 直接修改私有属性 由于语法特点,相当于给stu对象增加__score属性 print(stu.score,) print(stu.__score) # 输出 1 # 输出 2 # 看起来已经被修改了 调用函数来查看私有属性是否修改成功 stu.test() # 输出 320684198901010001 # 输出 780 # 私有的数据没有被修改过
3 多态
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') peo=People() dog=Dog() pig=Pig() #peo、dog、pig都是动物,只要是动物肯定有talk方法 #于是我们可以不用考虑它们三者的具体是什么类型,而直接使用 peo.talk() dog.talk() pig.talk() #更进一步,我们可以定义一个统一的接口来使用 def func(obj): obj.talk() func(peo) func(dog) func(pig)
1.增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)*
2.增加了程序额可扩展性
通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用*
class Cat(Animal): #动物的另外一种形态:猫 def talk(self): print('say miao') def func(animal): #对于使用者来说,自己的代码根本无需改动 animal.talk() cat1=Cat() #实例出一只猫 func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能 say miao ''' 这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1) '''
4 鸭子类型
1 定义
如果一个对象叫声像鸭子,走路像鸭子,长得像鸭子,那它就是鸭子
class PC(): def conntent_device(self, usb_device): usb_device.open() usb_device.work() usb_device.close() class Mouse: # 实现接口规定的所有功能 def open(self): print("mouse opened") def work(self): print("mouse working...") def close(self): print("mouse closed") mouse = Mouse() pc = PC() pc.conntent_device(mouse) class KeyBoard: def open(self): print("KeyBoard opened") def work(self): print("KeyBoard working...") def close(self): print("KeyBoard closed") key1 = KeyBoard() # 如果key1的特征和行为都像USB设备 那就把它当做USB设备来使用 # 对于使用者而言可以不用关心这个对象是什么类,是如如何是实现, pc.conntent_device(key1) ''' mouse opened mouse working... mouse closed KeyBoard opened KeyBoard working... KeyBoard closed '''