联想说 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 的多态,有两个前提需要实现:
- 继承
- 重写
比如代码:
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 你不是我的崽,跑来凑啥热闹!!
关于面向对象三大特性就这么多了,语法方面还是非常简单的,难的是在对它们的运用。而作为初学者,要你们设计啥。不需要,所以如果能力有限的话,掌握好语法就好,不会对之后的学习有什么影响。

浙公网安备 33010602011771号