抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

前面说到python中一切皆为对象,面向对象是python的核心,也通过代码方式了解了什么是类和对象、属性和方法以及具体的分类。这篇笔记主要记录下前面没讲完的面向对象编程具体的三个特征。

面向对象编程的特征

python是面向对象的语言,支持面向对象的三大特征:封装(隐藏),继承和多态

1. 封装(隐藏)

1.1 封装概念

隐藏对象的属性和实现细节,只对外提供必要的方法。相当于将“细节封装起来”,只对外暴露“相关调用方法”。

通过私有属性、私有方法(都是在属性或者方法前加上__来实现私有化,类外部不能直接访问)的方式,实现封装(Encapsulation)。封装的概念类似权限控制,有些属性或方法只想于类别内部使用,而不想公开于外部,除了减少代码因来源端不适当的使用发生问题外,也可保护其中重要的商业逻辑。

当然,前面说过python没有严格意义上的访问控制限制,更多还是靠编程人员的自觉= =

2 继承

2.1 继承概念

继承是创建新类的方式,是实现代码复用的重要手段(比如一个新类继承自设计好的类,就直接具备已有类的特征,减少代码重复编写)。对于已有的类,我们称为父类或基类,而要创建的新类,我们称为子类或派生类。python支持多继承,也就是新建的类可以有一个或者多个父类

python3中默认继承object类,object是根类,是所有类的父亲。编写过程中object可以省略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 定义一个父类(object可省)
class Animal(object):
def __init__(self, name, color):
self.name = name
self.color = color

def eat(self):
print('%s在进食' % self.name)

# 定义一个子类,括号中为父类的名字
class Pig(Animal):
def setName(self, newname):
self.name = newname
return self.name

Peggy = Pig('猪', '粉色') # 实例化对象
print('Peggy是%s,颜色是%s' % (Peggy.name, Peggy.color)) # 查看对象属性
Peggy.eat() # 调用父类方法
print('现在Peggy的名字叫做%s' % Peggy.setName('George')) # 调用子类方法
print(Pig.__mro__) # 查看类的继承层次结构,可以用类属性__mro__或者类方法mro()

'''
运行结果:
Peggy是猪,颜色是粉色
猪在进食
现在Peggy的名字叫做George
(<class '__main__.Pig'>, <class '__main__.Animal'>, <class 'object'>)
'''

从上面的例子可以看到,子类Pig从父类Animal中继承了__init__()方法,从子类中实例化对象Peggy是可以调用父类的方法的。

需要注意:

私有的属性和方法(前面带有__)不能被子类继承,也不能被访问!

2.2 多继承

顾名思义一个子类继承自多个直接父类,这样也就有了多个父类的特点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 定义一个父类马
class Horse:
def output(self):
print('骡子的一半基因来自马')
# 定义一个父类驴
class Donkey:
def output(self):
print('骡子的一半基因来自驴')
# 定义一个子类骡子,继承自马和驴
class Mule(Horse, Donkey):
pass

a = Mule() # 实例化一个对象骡子
a.output() # 调用同名父类方法
print(Mule.__mro__)

'''
运行结果:
骡子的一半基因来自马
(<class '__main__.Mule'>, <class '__main__.Horse'>, <class '__main__.Donkey'>, <class 'object'>)
'''

上面的例子可以看到,子类骡子(Mule)继承自父类马(Horse)和驴(Donkey),这样可以拥有两个父类各自的特征。但是,如果父类中如果有同名的方法(这里的output(self)),那么子类只会从左到右的顺序,调用先继承的父类(Horse)中的方法。

同样可以通过类属性__mro__来查看继承结构,显示结果也是从左到右的顺序,从子类开始一层层往上到父类,这就是继承的顺序。

一般情况下不建议用多继承(一个人不可能有两个爹),代码可读性会变差。

2.3 重写父类方法

重写的意思是,当子类中有一个和父类相同的名字的方法,子类中的方法会重新定义覆盖掉父类中的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animal:
def eat(self):
print('是个动物都会进食')

class Pig(Animal):
def eat(self):
print('只有猪才会吃了睡睡了吃')

Peggy = Pig()
Peggy.eat() # 调用父类同名方法,子类的方法会覆盖

'''
运行结果:
只有猪才会吃了睡睡了吃
'''

如果想要在子类方法中调用父类的同名方法,最简单的实现方式是在子类方法中进行类调用,但是父类名如果修改过,在多继承时子类的方法也要重复改很多次,python为了解决这个问题引入了super()函数,需要注意super()代表父类的定义,而不是父类对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animal:
def eat(self):
print('是个动物都会进食')

class Pig(Animal):
def eat(self):
super().eat() # super()代表父类名,即使父类名改变这里也不需要改

Peggy = Pig()
Peggy.eat()

'''
运行结果:
是个动物都会进食
'''

一般而言,super在继承中经常用来继承父类的初始化方法,例如 super().__init__()

3 多态

3.1 多态概念

多态指不同对象对同一个方法调用,可能会产生不同的行为。举个栗子,对于同样一个吃饭的方法,不同对象比如中国人用筷子吃饭,印度三哥用手抓饭,欧美人用刀叉吃饭。

需要注意以下几点:

  1. 多态是方法的多态,属性没有多态
  2. 多态存在的必要条件:继承和方法重写

3.2 代码演示多态和“鸭子类型”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class People:
pass

class Chinese(People):
def eat(self):
print('中国人用筷子吃饭')
class Indian(People):
def eat(self):
print('印度人用手抓饭')
class American(People):
def eat(self):
print('欧美人用刀叉吃饭')

# 分别实例化,并定义一个统一的接口来使用
XiaoMing = Chinese()
ASan = Indian()
George = American()

def Eatting(self):
self.eat()
Eatting(XiaoMing) # 相当于调用了XiaoMing.eat,以下同理
Eatting(ASan)
Eatting(George)
print('*******************************************')

# “鸭子类型”
# 定义三个不同的类(实际上也都继承自根类object)
class Chinese:
def eat(self):
print('中国人用筷子吃饭')
class Indian:
def eat(self):
print('印度人用手抓饭')
class American:
def eat(self):
print('欧美人用刀叉吃饭')

People_list = [Chinese, Indian, American] # 封装好的类作为People_list的元素

for person in People_list:
person().eat() # person()是实例化对象的过程,分别调用不同类的同名方法

'''
运行结果:
中国人用筷子吃饭
印度人用手抓饭
欧美人用刀叉吃饭
*******************************************
中国人用筷子吃饭
印度人用手抓饭
欧美人用刀叉吃饭
'''

不同对象调用同名方法,产生不同结果,这就体现了多态性,好处在于增强了程序的灵活性和可扩展性。

Python崇尚的“鸭子类型”就是动态类型的风格:“当看到一直鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以称为鸭子。”这种动态风格中,一个对象的有效语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定,也就是说,我们并不关心对象是什么类型,而是关心对象是怎么使用的。

总而言之,这种动态类型使得编程非常灵活,可以避免一些重写和继承,省去复制大量重复代码的操作。

欢迎小伙伴们留言评论~