python面向对象的三大特性:封装性、继承性、多态性

发布时间 2023-09-25 09:52:39作者: 七落安歌

python面向对象的三大特性:封装性、继承性、多态性

一、python中的封装

在python代码中,封装具有两层含义:

① 在把现实世界中的实体中的属性和方法写到类的里面的操作即为封装。

class Person(object):
    # 封装属性
    # 封装方法

② 封装可以为属性和方法添加私有权限(属性和方法的封装)

封装中的私有属性和私有方法

在面向对象代码中,我们可以把属性和方法分为两大类,公有(属性,方法)、私有(属性,方法)

Python:公有(属性,方法)、私有(属性,方法)

Java:公有:(属性,方法)、受保护(属性,方法)、私有(属性,方法)

公有属性或公有方法:无论在类的内部还是在类的外部我们都可以对属性和方法进行操作

但是在有些情况下,我们不希望在类的外部对类内部的属性和方法进行操作。我们就可以将这些属性和方法封装为私有形式

  • 只能在类的内部访问,而不能在类的外部访问

私有属性和私有方法的目的:保护数据,过滤掉异常数据!

通过__定义的属性就是私有属性
class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.__age = age  # 私有属性

    # 在类的内部,私有属性可以访问的(只是针对外部无法访问而已)
    # 接口
    def getage(self):
        return self.__age


p1 = Person('赵薇', 18)
print(p1.name)
print(p1.age)  # 报错,在类的外部不能访问私有属性
print(p1.getage())  # 通过接口进行访问私有属性

  • 在实际工作中,所有的属性都应该封装为私有形式,保证数据的安全。

为私有属性添加设置与获取公共接口

class Girl(object):
    def __init__(self, name):
        self.name = name
        self.__age = 18  # 私有属性

    # 给__age私有属性添加一个访问的’接口‘
    def get_age(self):
        # 获取属性之前
        # 1、检验用户是否有查看属性的权限
        # 2、如果有,则返回私有属性,如果没有,则直接禁止访问
        return self.__age

    # 给__age私有属性添加一个设置的’接口‘
    def set_age(self, age):
        # 在设置之前
        # 1、首先要对age进行判断,判断是否是一个合理的数据
        if not isinstance(age, int):  # 判断age是否为int,否则直接退出该函数
            print('很抱歉,您设置的age参数非int类型!')
            return
        if age < 0 or age > 200:
            print('很抱歉,您设置的age参数范围不合理!')

        # 如果是一个合理的参数,则允许设置,否则直接禁止!
        self.__age = age

g1 = Girl('赵薇')

g1.set_age(20)   # 通过set_age函数来设置私有属性
  • 封装数据的属性:明确的区分内外,控制外部对隐藏属性的操作行为(过滤异常数据)

私有方法的定义与使用

私有方法并不是用来保护数据的,而是可以简化程序的复杂度

基本语法:

def __方法名称(self):
    私有方法
# 银行取款
class ATM(object):
    # 1、插卡
    def card(self):
        print("取款")

    def auth(self):
        print('用户验证')

    def input(self):
        print('请输入取款金额:')

    def take_money(self):
        print('取款')

    def print_bill(self):
        print('打印账单')


# 实例化ATM类,进行取款操作
atm = ATM()
atm.card()
atm.auth()
atm.input()
atm.take_money()
atm.print_bill()
  • 需要调用太多方法,增加了程序的复杂度,有没有办法只调用一个方法就实现了取款操作呢
# 银行取款
class ATM(object):
    # 1、插卡
    def __card(self):  # 定义为私用方法
        print("取款")

    def __auth(self):  # 私有方法
        print('用户验证')

    def __input(self):
        print('请输入取款金额:')

    def __take_money(self):
        print('取款')

    def __print_bill(self):
        print('打印账单')
        
    # 定义一个统一的接口,专门用于实现取款操作
    def withdraw(self):
        self.__card()
        self.__auth()
        self.__input()
        self.__take_money()
        self.__print_bill()

# 实例化ATM类,进行取款操作
atm = ATM()
atm.withdraw()

二、python中的继承

1、什么是继承

生活中的继承,一般指的是子女继承父辈的财产。

在python代码中,类是用来描述现实世界中同一组事务的共有特性的抽象模型,但是类也有上下级和范围之分,比如:生物 => 动物 => 哺乳动物 => 灵长型动物 => 人类 => 黄种人

其实就是个性与共性之间的关系,在OOP代码中,也一样要体现出类与类之间的共性与个性的关系,这里就需要通过类的继承来体现了。简单来说,如果一个类A使用了另一个类B的成员(属性和方法),我们就可以说A类继承了B类,同时这也体现了代码重用的特性。

2、继承的基本语法

基本语法:

# 父类B
class B(object):
    pass

# 子类A
class A(B):
    pass
  • 在python3中,所有类默认继承object类,object是顶级类或者基类;其他子类也叫派生类。
  • A类的实例化对象会自动拥有B类的所有公共属性和公共方法。

与继承相关的概念

继承:一个类从另一个已有类获得其成员(属性和方法)的相关特性,就叫作继承。

派生:从一个已有的类产生一个新的类,就叫作派生。

如上述:A类继承了B类 或者说 B类派生了A类

很显然,继承和派生就是从不同的方向描述相同的概念而已,其本质是一样的。

父类:也叫作基类,就是指已有被继承的类。

子类:也叫作派生类和扩展类

扩展:在子类中添加一些自己特有的特性,就叫作扩展,没有扩展,继承也就没有了意义。

单继承:一个类只能继承来自一个其他的类,不能继承多个类,单继承也是大多面向对象语言的特性。

多继承:一个类同时继承多个父类(C++,Python等语言都支持多继承)

3、单继承

class Car(object):
    # 定义公共属性
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model

    # 定义公共方法
    def run(self):
        print('I can run!')


# 定义一个汽油车类(继承汽车类)
class GasolineCar(Car):
    # 继承了汽车类的所有公共属性和方法
    pass


bwm = GasolineCar('宝马', 'X5')  # 继承了汽车类所有公共属性和方法
print(bwm.brand)
print(bwm.model)
bwm.run()

4、单继承特性(多层继承):传递性

在python继承中,如A类继承了B类,B类又继承了C类。根据继承的传递性,A也会自动继承C类的所有公共属性和方法。

class C(object):
    def func(self):
        print('我是C类中的公共方法!')


class B(C):
    pass


class A(B):
    pass


a = A()
a.func()  # C -> B -> A

5、多继承

python是为数不多支持多继承的面向对象的编程语言。所谓多继承就是一个类可以同时继承多个类的公共特性。

# 定义一个汽油车类
class GasolineCar(object):
    def run_with_gasoline(self):
        print('I run with Gasoline!')


# 定义一个电动车类
class ElectricCar(object):
    def run_with_electric(self):
        print('I run with electric!')


# 定义一个混动汽车类
class HybridCar(GasolineCar, ElectricCar):  # 同时继承汽油车类与电动车类
    pass


byd = HybridCar()
byd.run_with_gasoline()  # 汽油车的方法
byd.run_with_electric()  # 电动车的方法

6、MRO属性或MRO方法:方法解析顺序

MRO(method Resolution Order):方法解析顺序,我们可以通过类名.__mro__类名.mro()获得“类的层次结构”,方法解析顺序也是按照这个“类的层次结构”寻找到。

一个class如何去从他的父类里面找,应该优先使用哪个父类函数,这个顺序就叫MRO。我们提到的MRO是每一个类会把它所有的父类和他自己做一个线性化;也就是把它继承的所有类和它自己做一个排队,这个排队保证自己是最高优先级。在它想调用一个方法和数据时候,它会按照这个队列的优先级顺序从前往后找。

class A:
    def say(self):
        print("A")

class B:
    def say(self):
        print("B")

class C(A):
    pass

class M(C, B):
    pass

m = M()
m.say() # 输出A 根据MRO
print(M.mro())

image-20230923000039385

  • 当m调用say方法时候,优先从M类中寻找有没有say方法,依次往后M -> C -> A ->B

多继承中的覆盖问题

继承的多个父类中含义相同的属性或方法,那么子类会继承哪个父类中的成员方法呢?

# 定义一个汽油车类
class GasolineCar(object):
    def run(self):
        print('I run with Gasoline!')


# 定义一个电动车类
class ElectricCar(object):
    def run(self):  # 同样定义一个run方法
        print('I run with electric!')


# 定义一个混动汽车类
class HybridCar(GasolineCar, ElectricCar):  # 同时继承汽油车类与电动车类
    pass


byd = HybridCar()
byd.run()  # I run with Gasoline! 继承了GasonlineCar类中的run方法
print(HybridCar.__mro__)  # 子类调用__mro__查看类的层次结构
print(HybridCar.mro())   # 通过子类调用mro()方法查看类的层次结构

image-20230920084450036

  • 通过子类类名.__mro__类名.mro()获得类的层次结构是一样的,只不过两者返回值不同,一个是元组,一个是列表
  • 从类的层次结构中获知,优先继承HybridCar(自己)中的成员,其次GasolineCar,然后是ElectricCar,最后是object(基类)中的成员方法。

7、子类扩展:重写1父类的属性和方法

什么是重写与扩展

重写也叫作覆盖,就是当子类成员和父类成员名字相同的时候,真正启作用的是子类中的成员!

扩展:子类在继承父类的同时,还可以编写一些属于自己的属性和方法。甚至重写父类的属性和方法,我们把这种形式称为扩展。

继承的意义:① 实现代码的重用 ② 子类应该在继承的同时,拥有一些自己的特性(扩展)

class Animal(object):
    def eat(self):
        print('I can eat!')

    def call(self):
        print('I can call')


class Dog(Animal):
    # 重写父类中的call方法
    def call(self):
        print('I can wang wang wang!')


dog = Dog()
dog.eat()  # 继承父类中的方法
dog.call()  # 重写父类中的方法 I can wang wang wang!

思考:重写父类中的call方法之后,此时父类中的call方法还在不在?

答:还在,只不过是在其子类中找不到了。类方法的调用顺序,当我们在子类中重构父类的方法后,Dog子类的实例先会在自己的类Dog中查找该方法,当找不到该方法时才会去父类Animal中查找对应的方法。

8、super() : 调用父类属性和方法

在python代码中,如果在继承重写的基础上,我们还需要强制调用父类中的属性或方法,可以使用super()

  • super是一个类class,super()建立一个super对象,在super中的两个参数,第一个参数是一个class,决定了在mro链上从哪个class(当前类的后一个)开始往后找。

基本语法:

Python2版本中:

super.(当前类名,self).属性或方法

Python3版本中:

super().属性
super().方法
class Car(object):
    def __init__(self, brand, model, color):
        self.brand = brand
        self.model = model
        self.model = model

    def run(self):
        print('I can run!')


# 定义一个汽油车类
class GasolineCar(Car):
    # 继承Car中属性
    # 重写Car中的方法
    def run(self):
        print('I can run with gasoline!')


class EletricCar(Car):
    # 强制继承父类中的公共属性
    def __init__(self, brand, model, color, battery):
        # 强制继承父类中的brand,model,color
        super().__init__(brand, model, color)
        self.battery = battery

    # 重写父类中的run方法
    def run(self):
        print('I can run with eletric1')


car1 = EletricCar('tesla', 'model Y', 'red', '80%')
print(car1.battery)
print(car1.brand)
class A(object):
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model


class B(object):
    def __init__(self, brand, color):
        self.brand = brand
        self.color = color


class C(A, B): # 根据mro方法解析顺序 继承类为 C->A->B->object
    def __init__(self, brand, model, color):
        super(C, self).__init__(brand, model)  # C 代表从C之后的类开始往后寻找 init方法继承
        self.color = color


print(C.mro())
c = C('byd', 123, 'red')
print(c.model, c.color, c.brand)

三、Python中的多态

多态指的是一种事物具有多种形态

定义:多态是一种使用对象的方法,子类重写父类方法,调用不同子类的相同父类方法,可以产生不用的执行结果

① 多态依赖继承

② 子类必须重写父类方法

好处:调用灵活,有了多态,更容易编写出通用的代码,做出通用的编程,以适应需求的不断变化。

image-20230925092020290

class Fruit(object):
    # 定义一些公共属性和方法
    def makejuice(self):
        pass


class Apple(Fruit):
    # 重写父类中的方法
    def makejuice(self):
        print('I can make apple juice!')


class Orange(Fruit):
    def makejuice(self):
        print('I can make orange juice!')


class Banana(Fruit):
    def makejuice(self):
        print('I can make banana juice!')


# 定义一个公共借口,根据传入不同的对象,调用相同的方法产生不同的结果
def service(obj):
    obj.makejuice()


# 比如
service(Banana())  # I can make banana juice!
apple = Apple()
service(apple)  # I can make apple juice!
service(Orange())  # I can make orange juice!

案例:

class Dog(object):
    def work(self):
        pass


class ArmyDog(Dog):
    def work(self):
        print('追击敌人...')


class DrugDog(Dog):
    def work(self):
        print('追查毒品...')


class Person(object):
    def work_with_dog(self, dog):
        dog.work()


police = Person()
police.work_with_dog(ArmyDog())
police.work_with_dog(DrugDog)

加号 + 的多态案例:

当加号两边的对象为两个 数值型时 ,实现运算符的操作 1+2 = 3

当加号两边为 两个字符串的时候,实现字符串的拼接: 'a' + 'b' = 'ab'

当加号两边为两个列表的时候,实现列表的拼接:[1,2,3] + [4, 5, 6] = [1, 2, 3, 4, 5, 6]