Python 类与对象编程详解四(封装、继承、多态)

上一篇:Python 类与对象编程详解三(类属性、实例属性)

目录

      • 封装
        • 为什么要封装
        • 封装方法
        • 封装例子
        • 封装特性
        • 面向对象的封装有三种形式:
      • 继承
        • 什么是继承
        • 为什么要继承
        • 如何用继承
          • 单继承
          • 多继承
          • 新式类、经典类
          • 继承与抽象
          • 派生类
          • 组合
          • 属性查找顺序
          • 重写
          • 子类中访问父类的内容
          • 子类初始化基类的私有成员
          • 类的mro方法
          • 菱形继承
        • 多态
          • 什么是多态
          • 为什么要用多态

封装

为什么要封装

封装不是单纯意义的隐藏
加粗样式
1、封装数据:主要原因是:保护私隐,明确区分内外。将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。
实例1:

class teacher:
    def __init__(self,name,age):
        self.__name = name
        self.__age = age
    def tell_info(self):
        print("姓名:%s,年龄:%s"%(self.__name,self.__age))
    def set_info(self,name,age):
        if not isinstance(name,str):
            raise TypeError('姓名必须是字符串类型')
        if not isinstance(age,int):
            raise  TypeError('年龄必须是整形')
        self.__name = name
        self.__age = age
t = teacher('jack',19)
t.tell_info()
t.set_info('jery',18)
t.tell_info()

运行结果:

姓名:jack,年龄:19
姓名:jery,年龄:18

封装方法

目的是隔离复杂度
实例:

#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
#对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做隔离了复杂度,同时也提升了安全性

class ATM:
    def __card(self):
        print('插卡')
    def __auth(self):
        print('用户认证')
    def __input(self):
        print('输入取款金额')
    def __print_bill(self):
        print('打印账单')
    def __take_money(self):
        print('取款')

    def withdraw(self):
        self.__card()
        self.__auth()
        self.__input()
        self.__print_bill()
        self.__take_money()

a=ATM()
a.withdraw()

运行结果:

插卡
用户认证
输入取款金额
打印账单
取款

封装例子

比如说私有变量和私有方法,如果你还不太了解,请看这篇文章

Python 类与对象编程详解二(成员保护和访问限制)
1.类中定义的__x只能在内部使用,如self.__x,引用的就是私有属性的结果。外部是无法通过__x这个名字访问到的。
2.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
这种变形需要注意的问题是:
1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N

封装特性

什么是特性property
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
为什么要用property
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
在上一篇:Python 类与对象编程详解三(类属性、实例属性)
中也有property的实例,这里不在举例

面向对象的封装有三种形式:

public
这种其实就是不封装,是对外公开的
protect
下面的代码会用到装饰器,如果您还不太了解,请看这篇文章
Python 装饰器
实例:

class people(object):
    def __init__(self):

        self._x = 'male' # # p2.sex=male 一初始化或赋值操作就找 sex.setter
    @property   # 负责查询
    def x(self):   # 通过接口可以查看隐藏的性别
        return self._x # p2.__sex= male

    @x.setter# 定义修改性别的接口
    def x(self,value):
        # sexes=['male','female']
        if not isinstance(value,str):  # 在设定值之前进行类型检查,增加限制的扩展性
            raise TypeError('性别必须是字符串类型')
        # if value not in sexes:
        #     raise TypeError('性别只能是 male 或者 female')
        self._x=value  # p2.__sex= male

    @x.deleter
    def x(self):   # 删除属性接口
        del self._x  # del p2.__sex

p2=people()  # 触发 init 执行,这里有个赋值操作 p2.sex='male'
print(p2.x)
p2.x = 'female'
print(p2.x)
del p2.x  # 删掉 __sex 数据属性
print(p2.x)  # 再去 property 找的话找不到了

运行结果:

male
female
AttributeError: 'people' object has no attribute '_x'

private
这种封装对谁都不公开

class people(object):
    def __init__(self):
        self.__name = "hello"
Jack = people()
print(Jack.__name)
print(people.__name)

上面的代码执行就会报错,因为类和对象不能再类外访问私有属性

继承

什么是继承

继承是一种创建新的类的方式,新创建的叫子类,继承的叫父类、超类、基类。

特点:子类可以使用父类的属性,方法

继承是类与类之间的关系

为什么要继承

减少代码冗余、提高重用性

如何用继承

单继承

运行结果

class animal():
    print("我是动物")
class pet(animal):
    print("我是宠物")
class cat_pet(pet):
    print("我是宠物猫")
cat = cat_pet

运行结果:

我是动物
我是宠物
我是宠物猫
多继承
class animal():
    print("我是动物")
class pet():
    print("我是宠物")
class cat_pet(pet,animal):
    print("我是宠物猫")
cat = cat_pet

运行结果:

我是动物
我是宠物
我是宠物猫

使用魔法方法__bases__方法可以获取子类继承的类
如果你还想了解其他的魔法方法,请看这篇文章
Python 高级用法
实例:

class animal():
    print("我是动物")
class pet():
    print("我是宠物")
class cat_pet(pet,animal):
    print("我是宠物猫")
print(cat_pet.__bases__)

运行结果:

我是动物
我是宠物
我是宠物猫
(<class '__main__.pet'>, <class '__main__.animal'>)
新式类、经典类
  • 继承了object的类以及该类的子类,都是新式类。

在Python3中如果一个类没有继承任何类,则默认继承object类。因此python3中都是新式类

  • 没有继承object的类以及该类的子类,都是经典类。在Python2中如果一个类没有继承任何类,不会继承object类。因此,只有Python2中有经典类。
继承与抽象

抽象:通过抽象可以得到类,抽象是一种分析的过程。可以从猪、猫、狗等中,可以抽象出一个动物类。先分析、抽象之后,就可以通过继承,在程序上实现这个结构。

class Animals:
  	pass
  
class Pig(Animals):
  	pass
 
class Dog(Animals):
  	pass
  
class Cat(Animals):
  	pass
派生类

概念:派生,就是在子类继承父类的属性的基础上,派生出自己的属性。子类有不同于父类的属性,这个子类叫做派生类。通常情况下,子类和派生类是同一个概念,因为子类都是有不同于父类的属性,如果子类和父类属性相同,就没必要创建子类了。
实例1:

class Animals:
		pass
		
class Dog(Animals):
  	pass

这时候Dog类不是派生类
实例2:

class Animals:
  	def __init__(self, name):
      	self.name = name
       
    def walk(self):
				print('我会走')
       
class Dog(Animals):
  	#Dog类派生出bite功能
    #派生:狗有咬人的技能
  	def bite(self):
      	print('我会咬人')

这个类就是派生类

组合

除了继承之外,还有一种提高重用性的方式:组合
组合指的是,在一个类A中,使用另一个类B的对象作为类A的数据属性(特征)(变量),称为类的组合
比如:人和手机,人想要有打电话的功能,想要打电话,就需要用到手机,人想要用到手机里面的打电话功能,肯定不能用继承,人继承手机就非常尴尬了,这时候就可以用到组合。

class Mobile():
    def __init__(self, name):
        self.name = name
    def call(self):
        print('我要打电话')
class People():
    def __init__(self, name, mobile):
        self.name = name
        self.mobile = mobile
mobile = Mobile('华为')
people = People('小白', mobile)
people.mobile.call()

运行结果:

我要打电话

继承建立了派生类和基类的关系,是一种是的关系,比如白马是马,人是动物。

组合建立了两个类之间’有’的关系,比如人有手机,然后人可以使用手机打电话。

属性查找顺序

对象查找属性的顺序:对象自己的 - > 所在类中 -> 找父类 - >父类的父类 ->Object

重写

当子类出现了与父类名称完全一致的属性或是方法,子类就会重写父类的方法
实例:

class Person(object):
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def speak(self):
        print("Hello")
class student(Person):
    def speak(self):
        print("world")
Jack = student('jack',19)
Jack.speak()

运行结果:

world
子类中访问父类的内容

方式1:
super(当前类名称,self).你要调的父类的属性或方法
方式2:
super().你要调的父类的属性或方法
方式3:
类名称.你要调的父类的属性或方法(self)
实例:

class Person(object):
    name = "jack"
    def __init__(self,age):
        self.age = age
    def speak(self):
        print("Hello")
class student(Person):
    def speak(self):
        print(super(student,self).name)

Jack = student(19)
Jack.speak()

运行结果:

jack
子类初始化基类的私有成员

当你继承一个现有的类,并且你覆盖了父类的init方法时,必须在初始化方法的第一行调用父类的初始化方法,并传入父类所需的参数
实例:

class Person(object):
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def speak(self):
        print("Hello")

class student(Person):
    def __init__(self,name,age,grade):
        super(student,self).__init__(name,age)
        self.grade = grade
    def speak(self):
        print("world")
Jack = student('jack',19,2)
Jack.speak()

运行结果:

world
类的mro方法

mro方法可以列出类继承的所有父类,并且有顺序。调用mro方法要用类名.mro
实例:

class Person(object):
    name = "jack"
    def __init__(self,age):
        self.age = age
    def speak(self):
        print("Hello")
class student(Person):
    def speak(self):
        print(super(student,self).name)
Jack = student(19)
print(student.mro())

运行结果:

[<class '__main__.student'>, <class '__main__.Person'>, <class 'object'>]
菱形继承

类的继承图是从一个类开始继承,继承到顶点时,多个类继承同一个顶点,这种就叫做菱形继承。菱形继承查找类时就用广度查找。
遇到共同父类时就广度
经典类都是深度优先
实例:

class A(object):
    print("我是A")
class B(A):
    print("我是B")
class C(A):
    print("我是C")
class D(B,C):
    print("我是D")
d = D()

运行结果:

我是A
我是B
我是C
我是D

多态

什么是多态

Python中多态是指一类事物有多种形态。比如动物有多种形态,人,狗,猫,等等。文件有多种形态:文本文件,可执行文件。

为什么要用多态

①增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
②增加了程序额可扩展性
通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
实例:

import abc
class Animals(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def talk(self):
        pass
class People(Animals):
    def talk(self):
        print('People is talking')
class Cat(Animals):
    def talk(self):
        print('Cat is miaomiao')
class Dog(Animals):
    def talk(self):
        print('Dog is wangwang')
cat1 = Cat()
dog1 = Dog()
peo1 = People()
# peo、dog、pig都是动物,只要是动物肯定有talk方法
# 于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo1.talk()
dog1.talk()
peo1.talk()
# 定义一个统一的接口来访问
def func(obj):
    obj.talk()
func(cat1)

运行结果:

People is talking
Dog is wangwang
People is talking
Cat is miaomiao

要理解什么是多态,首先要对数据类型再做一点说明、在定义一个class的时候,实际上就定义了一种数据类型,通常,定义的数据类型和python自带的数据类型(例如string、list、dict)没什么区别

a = list()   a 是list类型
b = animal()  b 是animal 类型
c = Dog()   c 是 Dog 类型

一个变量是否为某个类型可以用isinstance()判断
实例:

class Animal:
    def run(self):
        print("Animal is runing...")
class Cat(Animal):
    def run(self):
        print("Cat is runing...")
class Dog(Animal):
    def run(self):
        print("Dog is runing...")
c = Dog()
c.run()
print(isinstance(c,Animal))
print(isinstance(c,Dog))

运行结果:

Dog is runing...
True
True

因为Dog是从Animal继承的,当创建了一个Dog的实例c时,认为c的数据类型是Dog没错,但c同时也是Animal,Dog本来也是Animal的一种
所以,在继承关系中,如果一个实例的数据类型是某个子类,那么它的数据类型也可以看做事是父类,但是,反过来就不可以

>>> b = Animal()
>>> isinstance(b,Dog)
False

Dog可以看成Animal,但Animal不可以看作Dog
要理解多态的好处,还需要编写一个函数,这个函数接收一个Amimal类型的变量

def run_twice(animal):
    animal.run()
    animal.run()

当传入Animal的实例时,run_twice就打印出:

>>> run_twice(Animal())
Animal is runing...
Animal is runing...

当传入Dog的实例时,run_twice()就打印出

>>> run_twice(Dog())
Dog is runing...
Dog is runing...

当传入Cat的实例时,run_twice()就打印出

>>> run_twice(Cat())
Catis runing...
Catis runing...

现在,如果定义一个Tortoise类型,也从Animal派生

class Tortoise(Animal):
    def run(self):
        print('Tortoise is running slowly')

当调用run_twice()时传入Tortoise实例

>>> run_twice(Tortoise())
Tortoise is running slowly
Tortoise is running slowly

  大家会发现新增了一个Animal的子类,不必对run_twice()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。
  多态地好处就是,当传入Dog,Cat,Tortoise等时,只需要接收Animal类型就可以了,因为Dog、Cat、Tortoise等都是Animal类型,然后按照Animal类型进行操作。由于Animal类型有run()方法,因此传入的任意类型,只要是Animal或者Animal的子类,就会自动调用实际类型的run()方法,这就是多态的意思。
  对于一个变量,用户只需要知道它是Animal类型,无须确切的知道它的子类型,就可以放心的调用run()方法,而具体调用的run()方法是作用在Animal、Dog、Cat、Tortoise对象上,由运行时该对象的确切类型决定,这就是多态真正的威力,调用方法只管调用,不管细节,而当新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的,这就是著名的"开闭"原则,包括以下两点。
1、对扩展开放:允许新增Animal子类
2、对修改封闭:不需要修改依赖Animal类型的run_twice()等函数
下一篇:Python 类与对象编程详解五(特殊成员)

你可能感兴趣的:(编程语言,python基础知识,多态,python,封装,类,面向对象编程)