面向对象
面向过程
与面向对象对应的就是面向过程编程(Procedure Oriented Programming),简称OPP
。
就是分析解决问题所需的步骤,然后用函数将这些步骤一步一步实现,使用的时候一个一个一次调用。
面向过程最重要的是模块化
的思想方法。
面向对象
面向对象编程(Object Oriented Programming),简称OOP
。
面向对象将对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向对象的设计思想是抽象出Class(类)
,根据Class创建Instance(实例)
。其三个特点是封装
、继承
和多态
。
类和实例
定义
类是用来描述具有相同属性和方法的对象的集合。它定义了集合中每个对象共有的属性和方法。
实例是根据类创建出来的一个个具体的对象,每个对象都有相同的方法,但各自的数据可能不同。
- 定义类通过
class
关键字
1 | class Student(object): |
上面代码定义了一个学生类。class
后面紧接着类名,类名通常是大写开头的单词,紧接着是(object)
,表示该类是从哪个类继承下来的。object类是所有类的源类
,也就是所有类都会继承该类。
- 创建实例通过类名+()实现
1
student = Student()
变量student
指向的就是一个Student
的实例。
另外可以自由的给实例变量绑定属性。1
2
3"xiaohh" student.name =
student.name
'xiaohh'
类的构造器
__init__
构造函数,在生成对象时调用。
类可以起到模板的作用,因此在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__
方法,在创建实例的时候,将属性绑上去。
例如:学生类中增加学号、姓名、分数属性。1
2
3
4
5class Student(object):
def __init__(self, no, name, score):
self.no = no
self.name = name
self.score = score
__init__
方法第一个参数永远是self
,表示创建的实例本身。
在创建实例的时候必须传入与该方法匹配的参数,self
不需要传。1
2
3
4
5
6
71101, "xiaohh", 88) student = Student(
student.no
1101
student.name
'xiaohh'
student.score
88
类中定义的函数,第一个参数默认是self
,并且在调用时不需要传递。
类的私有变量
在python中,如果实例的变量名以__
(双下划线)开头,就变成了一个私有变量,只有内部可以访问,外部不能访问。
同样是上面的类,传入相同的参数1
2
3
4
5
61101, "xiaohh", 88) student = Student(
student.score
88
99 student.score =
student.score
99
可见通过外部代码可以自由的修改一个实例的属性。
通过私有变量可以限制外部代码的访问。1
2
3
4
5
6
7
8
9
10
11class Student(object):
def __init__(self, no, name, score):
self.__no = no
self.__name = name
#修改score为私有变量 self.__score = score
...
1101, "xiaohh", 88) student = Student(
#外部无法访问 student.score
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
私有变量的访问和修改可以通过在类中增加方法来实现,通过私有变量,可以提高代码的健壮性。
例如:获取分数和修改分数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Student(object):
def __init__(self, no, name, score):
self.__no = no
self.__name = name
self.__score = score
def get_score(self):
return self.__score
def set_score(self, score):
self.__score = score
student = Student(1101, "xiaohh", 88)
print(student.get_score()) #输出结果:88
student.set_score(99)
print(student.get_score()) #输出结果:99
- 变量名类似
__xx__
这样双下划线开头和结尾的是特殊变量,可以直接访问,不是私有变量。 - 变量名类似
_xx
这样一个下划线开头的外部可以访问,但是按照约定俗成的规定,虽然可以被访问,但不要随意访问。 - 私有变量可以通过
_类名_变量名
方式访问。但是不建议使用。
关于私有变量的内容,可以参考python3 cookbook中在类中封装属性名部分的说明。1
2 student._Student__score
88
其实外部不能直接访问__score
原因是python解释器对外将__score
改成了_Student__score
。
数据封装
数据封装
是面向对象编程的一个重要特点。任何程序,只要封装好,对外只提供接口,使用方便且安全。
实例本身拥有数据,那么访问数据可以直接通过在类的内部定义访问数据的函数,而不用从外部的函数访问,这样就实现了数据的封装。封装数据的函数和类本身是关联起来的,我们称之为类的方法。
例如:学生类中添加打印学生信息的函数。1
2
3
4
5
6
7
8
9
10
11
12class Student(object):
def __init__(self, no, name, score):
self.no = no
self.name = name
self.score = score
def pr_ifo(self):
print("{0}的学号是{1},分数是{2}".format(self.name, self.no, self.score))
student = Student(1101, "xiaohh", 88) #实例化并传入参数
student.pr_ifo() #通过实例变量调用类的内部函数
继承和多态
定义一个类的时候,可以从某个现有的类继承,新的类称为子类,被继承的类称为基类、父类或超类。
继承使得在设计相似的东西时更加方便。多态使得我们在使用类似东西时不用考虑它们细微的区别。
继承可以使得子类获得父类的全部功能。
代码示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Animal(object):
def run(self):
print("Animal run。")
class Bird(Animal):
pass
class Cat(Animal):
pass
animal = Animal()
print(animal.run()) #输出结果:Animal run。
bird = Bird()
print(bird.run()) #输出结果:Animal run。
cat = Cat()
print(cat.run()) #输出结果:Animal run。
对于Bird
来说,Animal
是它的父类,它是Animal的子类。Cat
也类似。
此处只有父类Animal
实现了run()
方法,但是子类Bird
和Cat
可以使用run()
方法。
对上述代码修改:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Animal(object):
def run(self):
print("Animal run。")
class Bird(Animal):
def run(self):
print("Bird run。")
class Cat(Animal):
def run(self):
print("Cat run。")
animal = Animal()
print(animal.run()) #输出结果:Animal run。
bird = Bird()
print(bird.run()) #输出结果:Bird run。
cat = Cat()
print(cat.run()) #输出结果:Cat run。
可见,当子类和父类均有相同的方法时,优先调用子类的方法。
也就是说,当父类的方法无法满足子类的需求,子类就需要重构父类的方法。如果子类中有重构的方法,通过实例变量调用的时候优先调用子类的方法,若子类中没有,再去父类中寻找。
对于前面定义的Animal类执行命令:1
2print(isinstance(bird, Bird)) #输出结果:True
print(isinstance(bird, Animal)) #输出结果:True
上面结果说明Bird
的实例bird
的数据类型是Bird
,同时也是Animal
。
也就是说,在继承关系中如果一个实例的数据类型是某个子类,那么它的数据类型也可以被看做是父类。这就是多态这一特性,实际使用有啥好处呢?
继续修改上面的类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
49class Animal(object):
def run(self):
print("Animal run。")
class Bird(Animal):
def run(self):
print("Bird run。")
class Cat(Animal):
def run(self):
print("Cat run。")
def runtwice(animal): #增加函数
animal.run()
animal = Animal()
print(runtwice(animal)) #输出结果:Animal run。
bird = Bird()
print(runtwice(bird)) #输出结果:Bird run。
cat = Cat()
print(runtwice(cat)) #输出结果:Cat run。
```
此处对于不同的实例对象我们使用相同的调用方法,输出结果则是根据其传入的实例的实际类型确定。
也就是说一样的调用方式,不同的执行效果。这就是多态带来的效果。这样增强了代码的灵活性和可扩展性。
* 灵活性:不论对象怎么变化,使用者都是通过同一种方式调用。
* 可扩展性:即使创建新类,使用者不许修改代码,还是ongoing同一种方式调用。
**多继承**
多继承就是类似`C(A, B)`这种表示继承自一个以上的类。
多继承功能可以使我们选择组合不同的类的功能,从而快速构造出所需的子类,降低继承链的复杂度。
例如:对于`动物`类的设计,假如分为`哺乳类`和`鸟类`,现在需要增加`会飞类`和`会跑类`。
使用单继承,只能在哺乳类和鸟类下面分别增加哺乳会飞类、哺乳会跑类、鸟类会飞类、鸟类会跑类。
使用多继承,只需要新增`会飞类`、`会跑类`,然后让有对应特性的类继承即可。
``` python
class Animal(object):
pass
class Bird(object):
pass
class Mamm(object):
pass
class Flyer(object):
pass
class Runner(object):
pass
可以直接通过多继承实现既是Mamm
又是runner
这样的多个的特性。1
2
3
4
5class Cat(Mamm, Runner):
pass
class Spar(Bird, Flyer):
pass
多继承中,假如多个父类均定义某个方法,子类调用时候如何确定调用顺序呢?
看下面代码:1
2
3
4
5
6
7
8
9
10class Animal(object):
pass
class Bird(Animal):
def fly(self):
print("Bird can fly。")
class Flyer(object):
def fly(self):
print("Flyer can fly。")
多继承1
2
3
4
5class Spar(Flyer, Bird):
pass
spar = Spar()
print(spar.fly()) #输出结果:Flyer can fly。
修改父类的继承顺序1
2
3
4
5class Spar(Bird, Flyer):
pass
spar = Spar()
print(spar.fly()) #输出结果:Bird can fly。
根据上面输出结果可见:在多继承中,若父类都有该方法,那么先继承谁,就优先调用谁的方法。
super()
在类的继承中,子类重定义了某个方法,该方法会覆盖父类的同名方法。但有时我们希望能同时调用父类的方法,此时可以通过super()
来实现。1
2
3
4
5
6
7
8
9
10
11
12class Animal(object):
def __init__(self):
print("Animal初始化")
def fly(self):
print("Not all Animal can fly。")
class Bird(Animal):
def __init__(self):
pass
def fly(self):
super(Bird, self).fly()
print("Bird can fly。")
输出结果1
2
3
4 bird = Bird()
bird.fly()
Not all Animal can fly。
Bird can fly。
super()
函数更常见的是用法是在__init__()
方法中保证父类被正常初始化。1
2
3
4
5
6
7
8class A:
def __init__(self):
self.x = 0
class B(A):
def __init__(self):
super().__init__()
self.y = 1
输出结果1
2
3>>> b = B()
>>> print(b.x + b.y)
1
假如不使用super()
方法会出现什么情况呢?1
2
3
4
5
6
7
8
9
10
11
12 b = B()
print(b.x + b.y)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'B' object has no attribute 'x'
```
## 继承实现原理
对于定义的每一个类,python会计算出一个方法解析顺序(MRO)表。这个`MRO`列表就是一个简单的所有基类的线性顺序表。
对于上面定义的`class B`,可以看到其MRO列表内容
``` python
>>> B.__mro__
(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
为实现继承,python会在MRO列表从左往右开始查找基类,直到找到第一个匹配这个属性的类为止。
其遵循如下三条基本准则
- 子类会先于父类被检查
- 多个父类会根据它们在列表中的顺序被检查
- 如果对下一个类存在两个合法的选择,选择第一个父类
这也解释了子类重写
和多继承
中方法调用的顺序问题。
另外一个需要注意的问题是:super()可以在没有直接父类的类中使用。但是这种情况下直接使用该类(或单继承方式使用)将会报错,只能通过多继承方式使用。1
2
3
4
5
6
7
8
9
10
11class A:
def spam(self):
print('A.spam')
super().spam()
class B:
def spam(self):
print("B.spam")
class C(A, B):
pass
输出结果1
2
3
4
5
6 c = C()
#super调用的是类B中的spam()方法,此处类A和类B无任何关系 c.spam()
A.spam
B.spam
C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
更多关于python中super()
及继承实现部分内容,参考cookbook中调用父类方法的内容。