首页 > 技术文章 > 面向对象三大特性 封装 继承 多态 鸭子类型

wakee 2019-08-13 21:09 原文

1 继承

1:定义

继承描叙的是两个类之间的关系,一个类可以直接使用另一个类中已定义的方法和属性;
被继承的称之为父类或基类,继承父类的类称之为子类;

在python3中创建类时必然继承另一个类,如果没有显式的指定父类,则默认继承object类; object是根类 所有类都直接或间接的继承object

2 继承的作用

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()

 

2 派生和覆盖

派生

当父类提供的属性无法完全满足子类的需求时,子类可以增加自己的属性或非法,或者覆盖父类已经存在的属性,此时子类称之为父类的派生类;

覆盖  

在子类中如果出现于父类相同的属性名称时,根据查找顺序,优先使用子类中的属性,这种行为也称为`覆盖`

  

# 抽取老师和学生的相同内容 形成一个新的类,作为它们的公共父类
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

  

3 子类中重用父类的方法(重点使用super())

很多情况下 子类中的代码与父类中仅有小部分不同,却不得不在子类定义新的方法,这时候可以在子类中调用父类已有的方法,来完成大部分工作,子类仅需编写一小部分与父类不同的代码即可

在子类中有两种方式可以重用父类中的代码

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()

 

在新式类中,D是继承B和C的,按照顺序,首先去找B,如果在B里面能找到实例化对象,便继承B,不再往别的地方寻找,如果没有,就会接着找C,而不是找B的父亲A!

但是在经典类中,如果B中找不到,它会优先考虑B的父亲A,而不是C。

在python3中,都是遵循广度优先的规则,在python2.7以前,应该是遵循深度优先的的规则。两种规则没有优劣之分

 

即使没有直接继承关系,super仍然会按照mro继续往后查找

而第一种方式明确指定了要到哪一个类中去查找,找不到则直接抛出异常

 
#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'>]

 *当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。如果每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次(注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表) 

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'>]

为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。 而这个MRO列表的构造是通过一个C3线性化算法来实现的。不需要深究这个算法的原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:  

 1.子类会先于父类被检查

2.多个父类会根据它们在列表中的顺序被检查

3.如果对下一个类存在两个合法的选择,选择第一个父类

 

6 组合

软件重用的重要方式除了继承之外还有另外一种方式,即:组合

组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合

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() #可以使用组合的类产生的对象所持有的方法

  

组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同,

1.继承的方式

通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。

当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人

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 不受用

  

3 .如何封装

在属性名前添加两个下划线__,将其设置为私有的

  

1.封装数据属性实例:网页中折叠

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
'''

  

 

 

推荐阅读