前面说到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
| 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__)
''' 运行结果: 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()
Peggy = Pig() Peggy.eat()
''' 运行结果: 是个动物都会进食 '''
|
一般而言,super在继承中经常用来继承父类的初始化方法,例如 super().__init__()
3 多态
3.1 多态概念
多态指不同对象对同一个方法调用,可能会产生不同的行为。举个栗子,对于同样一个吃饭的方法,不同对象比如中国人用筷子吃饭,印度三哥用手抓饭,欧美人用刀叉吃饭。
需要注意以下几点:
- 多态是方法的多态,属性没有多态
- 多态存在的必要条件:继承和方法重写
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) Eatting(ASan) Eatting(George) print('*******************************************')
class Chinese: def eat(self): print('中国人用筷子吃饭') class Indian: def eat(self): print('印度人用手抓饭') class American: def eat(self): print('欧美人用刀叉吃饭') People_list = [Chinese, Indian, American]
for person in People_list: person().eat() ''' 运行结果: 中国人用筷子吃饭 印度人用手抓饭 欧美人用刀叉吃饭 ******************************************* 中国人用筷子吃饭 印度人用手抓饭 欧美人用刀叉吃饭 '''
|
不同对象调用同名方法,产生不同结果,这就体现了多态性,好处在于增强了程序的灵活性和可扩展性。
Python崇尚的“鸭子类型”就是动态类型的风格:“当看到一直鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以称为鸭子。”这种动态风格中,一个对象的有效语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定,也就是说,我们并不关心对象是什么类型,而是关心对象是怎么使用的。
总而言之,这种动态类型使得编程非常灵活,可以避免一些重写和继承,省去复制大量重复代码的操作。