python面向对象

面向对象

面向过程

与面向对象对应的就是面向过程编程(Procedure Oriented Programming),简称OPP
就是分析解决问题所需的步骤,然后用函数将这些步骤一步一步实现,使用的时候一个一个一次调用。
面向过程最重要的是模块化的思想方法。

面向对象

面向对象编程(Object Oriented Programming),简称OOP
面向对象将对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向对象的设计思想是抽象出Class(类),根据Class创建Instance(实例)。其三个特点是封装继承多态

类和实例

定义

类是用来描述具有相同属性和方法的对象的集合。它定义了集合中每个对象共有的属性和方法。
实例是根据类创建出来的一个个具体的对象,每个对象都有相同的方法,但各自的数据可能不同。

  • 定义类通过class关键字
1
2
class Student(object):
pass

上面代码定义了一个学生类。
class后面紧接着类名,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的。object类是所有类的源类,也就是所有类都会继承该类。

  • 创建实例通过类名+()实现
    1
    student = Student()

变量student指向的就是一个Student的实例。
另外可以自由的给实例变量绑定属性。

1
2
3
>>> student.name = "xiaohh"
>>> student.name
'xiaohh'

类的构造器

__init__构造函数,在生成对象时调用。
类可以起到模板的作用,因此在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,将属性绑上去。
例如:学生类中增加学号、姓名、分数属性。

1
2
3
4
5
class 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
7
>>> student = Student(1101, "xiaohh", 88)
>>> student.no
1101
>>> student.name
'xiaohh'
>>> student.score
88

类中定义的函数,第一个参数默认是self,并且在调用时不需要传递。

类的私有变量

在python中,如果实例的变量名以__(双下划线)开头,就变成了一个私有变量,只有内部可以访问,外部不能访问。
同样是上面的类,传入相同的参数

1
2
3
4
5
6
>>> student = Student(1101, "xiaohh", 88)
>>> student.score
88
>>> student.score = 99
>>> student.score
99

可见通过外部代码可以自由的修改一个实例的属性。
通过私有变量可以限制外部代码的访问。

1
2
3
4
5
6
7
8
9
10
11
>>> class Student(object):
... def __init__(self, no, name, score):
... self.__no = no
... self.__name = name
... self.__score = score #修改score为私有变量
...
>>> student = Student(1101, "xiaohh", 88)
>>> 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
16
class 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
12
class 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
17
class 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()方法,但是子类BirdCat可以使用run()方法。
对上述代码修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class 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
2
print(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
49
class 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
5
class Cat(Mamm, Runner):
pass

class Spar(Bird, Flyer):
pass

多继承中,假如多个父类均定义某个方法,子类调用时候如何确定调用顺序呢?
看下面代码:

1
2
3
4
5
6
7
8
9
10
class 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
5
class Spar(Flyer, Bird):
pass

spar = Spar()
print(spar.fly()) #输出结果:Flyer can fly。

修改父类的继承顺序

1
2
3
4
5
class 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
12
class 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
8
class 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
11
class 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()
>>> c.spam() #super调用的是类B中的spam()方法,此处类A和类B无任何关系
A.spam
B.spam
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

更多关于python中super()及继承实现部分内容,参考cookbook中调用父类方法的内容。

Recommended Posts