联想说 Python 基础 17 - 面向对象三大特性

三大特性

目标:了解三大特性各有什么特点,掌握其中有代码实现的部分。

当我们学了类和对象之后,我们的大多数代码都可以用它们来实现,通过类和对象来实现其实就叫面向对象编程。

现在网络上有各种说面向对象是一种编程思维,本质上也没有错。但是如果是初学者就考虑怎么去设计,那百分之九十都会被劝退了,都还没学会用就要做设计,想什么呢?所以就有了上面那段话。

等啥时候语法没有问题了,程序需要优化了,可以再看看设计模式来提升思维。

好像说了一些无关的哈,下面继续正文。

面相对象的三大特性主要有封装、继承和多态。其实个人感觉这三大特性在 Python 中并没有那么好的体现,不过有相关的实现。

如果读者有机会,可以再看看其他语言,比如 java、c++ 这类。

封装

封装这个还是比较好理解的哈,日常里面也经常有把 xx 东西封装起来,这不就是把东西打包并且封起来避免遗漏嘛。比如快递,把商品放到盒子里面叫装,然后还要用胶带缠几圈,免得盒子打开了,这叫封。

在面向对象中的封装也差不多这个意思了,我们把属性(数据)和操作数据的方法放在一个对象中,这就是装了。

装好之后,为了避免装进去的东西被半路拿出来偷用,还可以因此隐藏对象的内部细节,这可以叫做封了。

所以封装就是把相关代码结合组成一个对象,并隐藏对象内部细节

怎么隐藏对象细节?

面向对象中隐藏对象细节的操作叫做私有化,而实现私有化的操作则是给成员命名时,用 __ 开头(有两个)。

实验:

class Chicken:
    __name = '小红'
    color = '粉红色'

    def eat(self):
        print("吃天蚕和土豆")

    def __skill(self):
        print("愤怒的小鸡")


xh = Chicken()
print(xh.color)
xh.eat()

print(xh._name)
xh.__skill()

在这段代码中有两个成员是用 __ 开头的,在类的外面用对象调用,会出现错误信息:AttributeError: 'Chicken' object has no attribute '__name',它表示 Chicken 的对象没有叫 __name 的属性。

这样就防止了类的外面直接访问或修改某些重要数据了,但是类的内部还是可以访问的哈,访问方式不变。

tips:

其实在 Python 中是没有真正的私有化的,它只是给私有属性换了一个名字,换成了 _类名__成员名 的格式,按照这个格式还是可以调用的,但不建议。

为什么要隐藏?

一般来说并没有真正隐藏,因为将数据隐藏后会提供对应的方法让你来访问这个属性。比如:

class Chicken:
    __name = '小红'

    def get_name(self):
        return self.__name

    def set_name(self, name):
        self.__name = name


xh = Chicken()

print(xh.get_name())  # 小红
xh.set_name("小粉红")
print(xh.get_name())  # 小粉红

在这段代码中就提供了 get_name 方法用来查看 __name 的值,提供 set_name 方法设置它的值,如果不提供这些方法,类的外部将无法操作这个属性。

这不是多此一举吗,给你隐藏又给你写方法去操作。

既然有这样的设计,自然是有用的。比如给 Chicken 类添加一个属性 age 表示年龄,年龄不应该有负数,但是由于类的外面可以操作,所以这个是不可控的。现在改成隐藏的形式:

class Chicken:
    __name = '小红'
    color = '粉红色'
    __age = 0

    def get_age(self):
        return self.__age

    def set_age(self, age):
        if age < 0:
            print("还没出生凑什么热闹")
            return

        self.__age = age


xh = Chicken()

print(xh.get_age())  # 0
xh.set_age(-1)  # 还没出生凑什么热闹
print(xh.get_age())  # 0

当传入进入的年龄不合理,程序就会给你提示信息,这样程序就变得可控了。

继承

继承这个词大家应该也不陌生,总有人幻想着自己的哪个亲戚非常有钱,然后快要不行了,又没有后代,于是派人找到跟他有血缘关系的你让你继承他的巨大遗产。但是这基本都在小说/电影里,咱们就别想了,努力提升自己吧。

Python 里的继承产生于类与类之间,而继承后的效果与上面说的那个继承很像。

什么意思呢?

就是说类可以继承另一个类的 “遗产”,而类所能拥有的也就只有属性和方法了,这些就是它的 “遗产”,这些都能被继承到。

为了方便区分,对这俩类分别取个名字。继承遗产的这个类我们称为子类,有遗产给其他类继承的,我们称为父类

子类继承父类后,它就拥有了父类的所有成员,并且可以直接使用。在 Python 中要实现继承哪个类,只需要在当前类名称后面的括号里写上要继承的类名称就行,格式如下:

class 类名称(父类名称):
    pass

继承分类

关于继承,先讲几个概念:单继承、多继承 和 多层继承。

  • 单继承:表示子类继承父类

    也就是前面说的那种形式

  • 多继承:表示子类继承好几个父类

    多继承,只需要在括号中用逗号隔开再添加其他父类就行

  • 多层继承:表示子类继承父类,父类还继承另一个父类,这就有三层了

这个部分主要了解这几个概念,没有太多需要说的。除了这些之外,还有一个 Object 需要了解一下,它是所有类的父类。

Object

Object 也是一个类,不需要我们手动写继承,所有类都默认继承了它。

重写

继承后,如果子类中有和父类同名的方法,那就称为 重写,也叫覆盖。比如:

class Animal:
    money = "1000万"

    def skill(self):
        print("打鸣")

class Chicken(Animal):
    name = '小红'
    color = '粉红色'

    def eat(self):
        print("吃天蚕和土豆")

    def skill(self):
        print("愤怒的小鸡")

xh = Chicken()
xh.skill()

这里 Chicken 类继承了 Animal 类,并且都存在 skill 方法,就说 Chicken 类重写了 Animal 类的 skill 方法。

好不容易继承了,为什么还要覆盖掉?就像祖辈传下来的房子,为什么要装修呢?自然是因为达不到自己的需求了。

但是注意,并不是父类的所有成员都达不到自己的要求,其中大部分内容还是没有改的。

super()

那如果父类的东西还有用,需要基于父类的方法来实现自己的方法,该怎么做呢?

这需要引入另外一个关键字 - super 来实现。比如:

class Animal:

    def add(self, num1, num2):
        return num1 + num2

class Chicken(Animal):

    def add(self, num1, num2):
        num = super().add(num1, num2)
        return num * 2

xh = Chicken()
print(xh.add(1, 2))

在这段代码中,Animal 中存在 add 方法实现两个变量的相加。

现在我想把所有得到的结果都扩大一遍,直接继承就不够用了,就可以通过 super() 调用父类的 add,得到它的结果,然后乘以 2。

注意:不要子类继承父类,父类又继承子类的情况,它会出问题的!!!

多态

多态即多种形态,多种形态表示的是谁的多种形态呢?

这个问题先保留,带着这个问题继续往下看吧!!

多态的前提

要体现 Python 的多态,有两个前提需要实现:

  1. 继承
  2. 重写

比如代码:

class Animal:
    def bark(self):
        print("打鸣")


class Chicken(Animal):
    def bark(self):
        print("愤怒的小鸡")

两个前提准备好了,多态怎么体现呢?

需求实现

现在给一个需求:动物嚎叫比赛,每种动物都派出一个发出嚎叫,这个怎么实现?

按照以前的所学,可以这样来写:

import random


class Chicken:
    def bark(self):
        print("愤怒的小鸡")
        print("分数:", random.randint(0, 100))


class Dragon:
    def bark(self):
        print("恶龙咆哮")
        print("分数:", random.randint(0, 100))


class Dog:
    def bark(self):
        print("天狗啸月")
        print("分数:", random.randint(0, 100))


class Pig:
    pass


xh = Chicken()
xh.bark()

xl = Dragon()
xl.bark()

xg = Dog()
xg.bark()

xz = Pig()
xz.bark()

前面几个动物都正常的嚎叫了,但是 Pig 由于太懒还没有学会,当用它来调用就会报错。

此时,如果用继承来实现,那么 Pig 至少也会拥有父类的 bark 了。我们把代码修改一下:

import random


class Animal:
    def bark(self):
        print("嘎嘎嘎")
        print("分数:", 0)


class Chicken(Animal):
    def bark(self):
        print("愤怒的小鸡")
        print("分数:", random.randint(0, 100))


class Dragon(Animal):
    def bark(self):
        print("恶龙咆哮")
        print("分数:", random.randint(0, 100))


class Dog(Animal):
    def bark(self):
        print("天狗啸月")
        print("分数:", random.randint(0, 100))


class Pig(Animal):
    pass


def bark(animal):
    animal.bark()


bark(Chicken())
bark(Dragon())
bark(Dog())
bark(Pig())

继承:将代码修改成这样后,如果以后还有动物参加比赛,只要这些动物继承了 Animal,那么它们都可以正常执行。

重写:当不同的动物重写了 Animal 类后,每种动物都可以发出自己的嚎叫,这就体现了 animal 的多种形态。

从这个例子中,我们可以看出 animal 这个参数它能代表非常多的对象,比如 Chicken、Dragon 等,并且能正常使用这些对象的 bark 方法。所以我们所说的多态,指的就是 animal 的多态。

由于 Python 是弱类型语言,所以一个变量的值可以被其他类型的值替代,因此在 Python 中多态就是变量的覆盖。

但是如果随便拿个值覆盖是会出现问题的,比如上面例子中 animal 赋值成数值型,根本没有 bark 方法存在,所以报错。

所以多态还是需要在继承和重写的基础上,才能体现一个父类引用(变量)指向子类实现(对象),从而实现不同的方法。

isinstance

由于 Python 对类型没有强制的约束,所以可能传递了不符合要求的对象过来,从而导致程序报错,此时应该对传递过来的对象进行判断,如果符合要求才让它调用相关方法。

Python 提供了 isinstance 函数用来判断对象所属,它的用法如下:

isinstance(对象, 类名称)

如果 对象类名称 的子类(多层子类都可以),那么就返回 True。

将前面的代码加上 isinstance 后就变成这样:

import random


class Animal:
    def bark(self):
        print("嘎嘎嘎")
        print("分数:", 0)


class Chicken(Animal):
    def bark(self):
        print("愤怒的小鸡")
        print("分数:", random.randint(0, 100))


class Dragon(Animal):
    def bark(self):
        print("恶龙咆哮")
        print("分数:", random.randint(0, 100))


class Dog(Animal):
    def bark(self):
        print("天狗啸月")
        print("分数:", random.randint(0, 100))


class Pig(Animal):
    pass


def bark(animal):
    # 判断对象所属
    if not isinstance(animal, Animal):
        print(animal, "你不是我的崽,跑来凑啥热闹!!")
        return

    animal.bark()


bark(Chicken())  # 愤怒的小鸡 分数: 29
bark(Dragon())  # 恶龙咆哮 分数: 73
bark(Dog())  # 天狗啸月 分数: 100
bark(Pig())  # 嘎嘎嘎 分数: 0
bark("123")  # 123 你不是我的崽,跑来凑啥热闹!!

关于面向对象三大特性就这么多了,语法方面还是非常简单的,难的是在对它们的运用。而作为初学者,要你们设计啥。不需要,所以如果能力有限的话,掌握好语法就好,不会对之后的学习有什么影响。

posted @ 2023-10-13 16:55  笔锋微凉~~  阅读(27)  评论(0)    收藏  举报