python面向对象

面向对象与面向过程的区别

(先说明代码我希望大家ctrl+c,我希望大家能够边读我的文章边写变记忆理解)

很多人在了解面向对象就步入了面向过程,但是面向过程和面向对象是不一样的

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。就如:生活中的五子棋,面向过程的设计思路就是首先分析问题的步骤:1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤2,9、输出最后结果。把上面每个步骤用不同的方法来实现。

如果是面向对象的设计思想来解决问题。面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为1、黑白双方,这两方的行为是一模一样的,2、棋盘系统,负责绘制画面,3、规则系统,负责判定诸如犯规、输赢等。第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。

可以明显地看出,面向对象是以功能来划分问题,而不是步骤

面向对象的特点

万物对象。其实面向对象的思想之所以会产生,其实也是根据人认识事物的方式产生的,下图可以帮助理解。

面向对象的思想就是把一切都看成对象,而对象一般由属性和方法组成。

属性:属性属于对象静态的一面,用来描述某个对象的具体特征。比如小志身高180M,体重70KG,这里身高、体重都是属性。 

方法:方法方法属于对象动态的一面,举一个例子,小明会跑,会说话,跑、说话这些行为就是对象的方法!所以为动态的一面

类:具有同种属性的对象称为类,是个抽象的概念。比如“人类”就是一类,比如小明、小红、小玲等等这些人都是一个个的对象。类就相当于一个模具,他定义了它所包含的全体对象的公共特征和功能,对象就是类的一个实例化,小明就是人的一个实例化!我们在做程序的时候,经常要将一个变量实例化,就是这个原理!我们一般在做程序的时候一般都不用类名的,比如我们在叫小明的时候,不会喊“人,你干嘛呢!”而是说的是“小明,你在干嘛呢!”

面向过程与面向对象的优缺点

用面向过程的方法写出来的程序是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭。所谓盖浇饭,北京叫盖饭,东北叫烩饭,广东叫碟头饭,就是在一碗白米饭上面浇上一份盖菜,你喜欢什么菜,你就浇上什么菜。我觉得这个比喻还是比较贴切的。

蛋炒饭的好处就是入味均匀,吃起来香。如果恰巧你不爱吃鸡蛋,只爱吃青菜的话,那么唯一的办法就是全部倒掉,重新做一份青菜炒饭了。盖浇饭就没这么多麻烦,你只需要把上面的盖菜拨掉,更换一份盖菜就可以了。盖浇饭的缺点是入味不均,可能没有蛋炒饭那么香。

到底是蛋炒饭好还是盖浇饭好呢?其实这类问题都很难回答,非要比个上下高低的话,就必须设定一个场景,否则只能说是各有所长。如果大家都不是美食家,没那么多讲究,那么从饭馆角度来讲的话,做盖浇饭显然比蛋炒饭更有优势,他可以组合出来任意多的组合,而且不会浪费。

盖浇饭的好处就是”菜”“饭”分离,从而提高了制作盖浇饭的灵活性。饭不满意就换饭,菜不满意换菜。用软件工程的专业术语就是”可维护性“比较好,”饭” 和”菜”的耦合度比较低。蛋炒饭将”蛋”“饭”搅和在一起,想换”蛋”“饭”中任何一种都很困难,耦合度很高,以至于”可维护性”比较差。软件工程追求的目标之一就是可维护性,可维护性主要表现在3个方面:可理解性、可测试性和可修改性。面向对象的好处之一就是显著的改善了软件系统的可维护性。 

面向对象的类和实例

类和实例都是面对对象的重要感念********最重要的概念

在python中定义一个类

1
2
class 类名:
    pass

不同于定义方法,类名后面可以不带括号,如果定义方法的话,必须带括号,就像下面;

1
2
def 方法名():
    pass

定义一个类记实例化

1
2
3
4
class People:
    pass
 
laowang = People()  # laowang是People的实例化

 以下是关于类的基本知识,需要不断重复的锻炼和记忆在理解

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
class People:
    name = "laowang"
    age = 12
    sex = "nan"
 
laowang = People()
laowang.eye_color = "black"
laowang.skin_color = "yellow"
laowang.sex = "nv"
 
print(People.name)      # laowang
print(People.age)       # 12
print(People.sex)       # nan
print(laowang.name)     # laowang
print(laowang.age)      # 12
print(laowang.eye_color)        # black
print(laowang.sex)      # nv
print(People.eye_color)     # 报错
 
# 由此可见实例可以使用类属性,而类无法使用实例属性
# 类的属性分为:数据属性和函数属性
# 1.类的数据属性是所有对象共有的
# 2.类的函数属性是绑定给对象用的,有兴趣的可以在类中写一个方法,再实例化两个对象,打印一下地址看看是不是不一样。
 
类属性与实例属性

 

类内置的特殊属性 

类名.__name__ # 类的名字(字符串)
类名.__doc__ # 类的文档字符串(就是类中用三括号包裹的说明内容)
类名.__base__ # 类的第一个父类(在讲继承时会讲)
类名.__bases__ # 类所有父类构成的元组(在讲继承时会讲)
类名.__dict__ # 类的字典属性,键为属性名,对应的值为属性本身
类名.__module__ # 类定义所在的模块
类名.__class__ # 实例对应的类(仅新式类中)

1
2
3
4
5
6
7
8
dir([object])   # 会返回object所有有效的属性列表。
vars([object])  # 返回object对象的__dict__属性,其中object对象可以是模块,<br>类,实例,或任何其他有__dict__属性的对象。所以,其与直接访问__dict__属性等价。
help([object])  # 调用内置帮助系统。
type(object)    # 回对象object的类型。
hasattr(object, name)   # 用来判断name(字符串类型)是否是object对象的属性,<br>若是返回True,否则,返回False
callable(object)    # 若object对象是可调用的,则返回True,否则返回False。注意,<br>即使返回True也可能调用失败,但返回False调用一定失败。
 
实例化对象常用函数

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
类中常用的内置函数
__init__(self,…):初始化对象,在创建新对象时调用
__del__(self):释放对象,在对象被删除之前调用
__new__(cls,*args,**kwd):实例的生成操作
__str__(self):在使用print语句时被调用
__getitem__(self,key):获取序列的索引key对应的值,等价于seq[key]
__len__(self):在调用内联函数len()时被调用
__cmp__(stc,dst):比较两个对象src和dst
__getattr__(s,name):获取属性的值(s.name)
__setattr__(s,name,value):设置属性的值(s.name=value)
__delattr__(s,name)    删除name属性(del s.name)
__getattribute__():getattribute()功能与getattr()类似
__gt__(self,other):判断self对象是否大于other对象
__lt__(slef,other):判断self对象是否小于other对象
__ge__(slef,other):判断self对象是否大于或者等于other对象
__le__(slef,other):判断self对象是否小于或者等于other对象
__eq__(slef,other):判断self对象是否等于other对象
__call__(self,*args):把实例对象作为函数调用
1
2
3
4
5
6
7
8
9
10
11
12
复制代码
# __init__方法是在实例化对象之后才会执行,只用来对对象进行初始化操作。
class school:
    def __init__(self, name, addr, type):
        self.name = name
        self.addr = addr
        self.type = type
 
 
s1 = school('清华''北京''公立大学')<br> # 先调用类产生空对象s1,然后调用school.__init__('清华', '北京', '公立大学')
 
# 注:__init__中默认有return了,再写return就冲突了

  

1
self的详解
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
53
54
55
56
<br># 在理解这个self的时候,我看到了一个很好的说明方法,就是用方法实现类的作用
def school(name, addr, type):
    def init(name, addr, type):
        sch = {
            'name': name,
            'addr': addr,
            'type'type,
            'kao_shi': kao_shi,
            'zhao_sheng': zhao_sheng,
        }
        return sch
 
    def kao_shi(school):
        print('%s 学校正在考试' % school['name'])
 
    def zhao_sheng(school):
        print('%s %s 正在招生' % (school['type'], school['name']))
 
    return init(name, addr, type)
 
 
s1 = school('哈佛''美国''私立大学')
print(s1['name'])
s1['zhao_sheng'](s1)
 
s2 = school('清华''北京''公立大学')
print(s2['name'], s2['addr'], s2['type'])
s2['zhao_sheng'](s2)
 
 
# 可以看到,当我们调用school类中的方法需要将实例化的对象作为参数传入
# 类其实也是这搞的,我们可以做个试验
 
class school:
    def __init__(self, name, addr, type):
        self.name = name
        self.addr = addr
        self.type = type
 
    def kao_shi(self):
        print('%s 学校正在考试' % self.name)
 
    def zhao_sheng():
        print('%s %s 正在招生' % (self.typeself.name))
 
 
s1 = school('哈佛''美国''私立大学')
print(s1.name)  # 哈佛
s1.kao_shi()  # 哈佛 学校正在考试
 
s2 = school('清华''北京''公立大学')
print(s2.name)
s2.zhao_sheng()  # 报错,提示要获取0个参数,但是给了1个
 
# 到这里就应该看出来,当我们用实例化对象调用类的方法的时候,会自动传一个参数,而这个参数就是这个实例化的对象了。
# 到这里应该就知道self是个什么东西了吧

 

1
几种为对象初始化自己独有特征的方式
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<br># 方式一
class People:
    country = 'China'
    = 1
 
    def run(self):
        print('----->'self)
 
 
# 实例化出三个空对象
obj1 = People()
obj2 = People()
obj3 = People()
 
# 为对象定制自己独有的特征
obj1.name = 'egon'
obj1.age = 18
obj1.sex = 'male'
 
obj2.name = 'lxx'
obj2.age = 38
obj2.sex = 'female'
 
obj3.name = 'alex'
obj3.age = 38
obj3.sex = 'female'
 
 
# 方式二
class People:
    country = 'China'
    = 1
 
    def run(self):
        print('----->'self)
 
 
# 实例化出三个空对象
obj1 = People()
obj2 = People()
obj3 = People()
 
 
# 为对象定制自己独有的特征
def chu_shi_hua(obj, x, y, z):
    obj.name = x
    obj.age = y
    obj.sex = z
 
 
chu_shi_hua(obj1, 'egon'18'male')
chu_shi_hua(obj2, 'lxx'38'female')
chu_shi_hua(obj3, 'alex'38'female')
 
 
# 方式三
class People:
    country = 'China'
    = 1
 
    def chu_shi_hua(obj, x, y, z):
        obj.name = x
        obj.age = y
        obj.sex = z
 
    def run(self):
        print('----->'self)
 
obj1 = People()
# print(People.chu_shi_hua)
People.chu_shi_hua(obj1, 'egon'18'male')
 
obj2 = People()
People.chu_shi_hua(obj2, 'lxx'38'female')
 
obj3 = People()
People.chu_shi_hua(obj3, 'alex'38'female')
 
 
# 方式四
class People:
    country = 'China'
    = 1
 
    def __init__(obj, x, y, z):
        obj.name = x
        obj.age = y
        obj.sex = z
 
    def run(self):
        print('----->'self)
 
obj1 = People('egon'18'male')  # People.__init__(obj1,'egon',18,'male')
obj2 = People('lxx'38'female')  # People.__init__(obj2,'lxx',38,'female')
obj3 = People('alex'38'female')  # People.__init__(obj3,'alex',38,'female')

 

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# @property可以将python定义的函数“当做”属性访问,从而提供更加友好访问方式,但是有时候setter/deleter也是需要的。
# 1》只有@property表示只读。
# 2》同时有@property和@x.setter表示可读可写。
# 3》同时有@property和@x.setter和@x.deleter表示可读可写可删除。
#
# 为什么要使用property
# 将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行
# 了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
 
# -------------------------例子一-----------------------------------------
import math
 
 
class Circle:
    def __init__(self, radius):  # 圆的半径radius
        self.radius = radius
 
    @property
    def area(self):
        return math.pi * self.radius ** 2  # 计算面积
 
    @property
    def perimeter(self):
        return 2 * math.pi * self.radius  # 计算周长
 
 
= Circle(10)
print(c.radius)
print(c.area)  # 可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
print(c.perimeter)  # 同上
 
# 输出
# 10
# 314.1592653589793
# 62.83185307179586
 
# 注意:此时的特性arear和perimeter不能被赋值
c.area = 3  # 为特性area赋值
'''
抛出异常:
AttributeError: can't set attribute
'''
 
# -------------------------例子二-----------------------------------------
class A(object):  # 要求继承object
    def __init__(self):
        self.__name = None
 
    # 下面开始定义属性,3个函数的名字要一样!
    @property  # 读
    def name(self):
        return self.__name
 
    @name.setter  # 写
    def name(self, value):
        self.__name = value
 
    @name.deleter  # 删除
    def name(self):
        del self.__name
 
 
= A()
print(a.name)  # 读
 
a.name = 'python'  # 写
print(a.name)  # 读
 
del a.name  # 删除
print(a.name)  # 报错,AttributeError: 'A' object has no attribute '_A__name'
 
property

  

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
53
54
55
56
57
# 组合是指,在一个类中以另一个类的对象作为数据属性,称为类的组合。
class People:
    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex
 
 
class Course:
    def __init__(self, name, school_hour):
        self.name = name
        self.school_hour = school_hour
 
    def tell_info(self):
        print('课程%s, 课时:%s>' % (self.name, self.school_hour))
 
 
class Teacher(People):
    def __init__(self, name, age, sex, job_title):
        People.__init__(self, name, age, sex)
        self.job_title = job_title
        self.course = []
        self.students = []
 
 
class Student(People):
    def __init__(self, name, age, sex):
        People.__init__(self, name, age, sex)
        self.course = []
 
 
laowang = Teacher('老王'30'男''语文老师')
laoli = Student('老李'18'男')
 
python = Course('python'20)
linux = Course('java'10)
 
laowang.course.append(python)
laowang.course.append(linux)
laoli.course.append(python)
 
laowang.students.append(laoli)
 
# 使用
for obj in laowang.course:
    obj.tell_info()
 
# 说明:
# 组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同,
# 1.继承的方式
# 通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。
# 当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人
# 2.组合的方式
# 用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,<br>教授教python和linux课程,教授有学生s1、s2、s3...
# 当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
 
组合

类的三大就是:封装,继承,多态

1
 
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
<br># 封装就是把我们不想让人看到的东西隐藏起来。
# 反应到代码中就是让类中的属性和方法不被外面的访问。
# 而隐藏的方式就是用双下划线开头的方式将属性隐藏起来。
# 类中所有双下划线开头的名称如"__x"都会在类定义时自动变形成:_类名__x的形式。
# 而这种变形只有在类定义阶段发生变形。
class A:
    __N = 0  # 类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
 
    def __init__(self):
        self.__x = 10  # 变形为self._A__X
 
    def __foo(self):  # 变形为_A__foo
        print("from A")
 
    def bar(self):
        self.__foo()  # 只有在类内部才可以通过__foo的形式访问
 
 
= A()
a.bar()  # from A
print(A._A__N)  # 0
a._A__foo()  # from A
print(a.__dict__)  # {'_A__x': 10}
a.__foo()  # 报错,AttributeError: 'A' object has no attribute '__foo'
 
 
# 1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,
#   然后就可以访问了,如a._A__N,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形,
#   主要用来限制外部的直接访问。
# 2.变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形
 
 
# 封装的真谛在于明确地区分内外,封装的属性可以直接在内部使用,而不能被外部直接使用,然而定义属性的目的终
# 归是要用,外部要想用类隐藏的属性,需要我们为其开辟接口,让外部能够间接地用到我们隐藏起来的属性,那这么
# 做的意义何在???
# 先给出一个简单但是经典的例子。
class Teacher:
    def __init__(self, name, age):
        # self.__name=name
        # self.__age=age
        self.set_info(name, age)
 
    def tell_info(self):
        print('姓名:%s,年龄:%s' % (self.__name, self.__age))
 
    def set_info(self, name, age):
        if not isinstance(name, str):
            raise TypeError('姓名必须是字符串类型')
        if not isinstance(age, int):
            raise TypeError('年龄必须是整型')
        self.__name = name
        self.__age = age
 
 
= Teacher('老王'18)
t.tell_info()
 
t.set_info('老李'19)
t.tell_info()
 
# 1:封装数据:将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上
#    对该数据操作的限制,以此完成对数据属性操作的严格控制。
# 2:封装方法:目的是隔离复杂度,在编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称
#    为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。比如说取款是功能,这个功能有很多功
#    能组成:插卡、密码认证、输入金额、打印账单、取钱对使用者来说,只需要知道取款这个功能即可,其余功能我们
#    都可以隐藏起来,很明显这么做隔离了复杂度,同时也提升了安全性
# 3:python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果你非要不遵守,那也没法,只能来个最终的
#    办法。python要想与其他编程语言一样,严格控制属性的访问权限,只能借助内置方法如__getattr__,详见面向
#    对象进阶
 
# 封装的好处
# 封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接
# 口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只
# 要接口这个基础约定不变,则代码改变不足为虑。
# 例子:
# 类的设计者
class Room:
    def __init__(self, name, owner, width, length, high):
        self.name = name
        self.owner = owner
        self.__width = width
        self.__length = length
        self.__high = high
 
    def tell_area(self):  # 对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积
        return self.__width * self.__length
 
 
# 使用者
r1 = Room('卧室''egon'202020)
print(r1.tell_area())  # 400, 使用者调用接口tell_area
 
 
# 类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码
class Room:
    def __init__(self, name, owner, width, length, high):
        self.name = name
        self.owner = owner
        self.__width = width
        self.__length = length
        self.__high = high
 
    def tell_area(self):  # 对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了<br>,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了
        return self.__width * self.__length * self.__high
 
 
# 对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能
r1 = Room('卧室''egon'202020)
print(r1.tell_area())  # 8000

继承

继承同时具有两种含义

  1. 继承基类的方法,并且做出自己的改变或者拓展(常用于代码重用)
  2. 声明某个子类兼容于某基类,定义一个接口类,子类继承接口类。并实现接口中定义的方法

  

1
 
1
继承基本知识
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<br># ------------------------继承由来及单继承和多继承----------------------------------------
# 例子说明继承的由来
# 猫:喵喵叫  吃 喝 睡
# 狗:汪汪叫  吃 喝 睡
# 可以看到猫和狗除了有自己特殊的能力,还有大量重复的部分,反应到代码中,这些就是重复的冗余代码。<br>类的继承就是用来解决这个问题的。
# 我们可以把猫和狗的共有属性提取成一个动物类,
# 动物类:吃 喝 睡
# 猫和狗只要继承这个动物类,就可以拥有继承的基类中的所有类属性,然后再实现自己特殊的属性就可以了。<br>这个时候动物类可以称为是父类,猫和狗可以称为子类。
# 一个类可以继承一个或者多个类,父类又可称为基类或者超类,子类又称为派生类。
# 在提取基类找出共性的时候,我们可以称之为是抽象,
# 例子:
class Animal:
    def eat(self):
        print("%s 吃 " % self.name)
 
    def drink(self):
        print("%s 喝 " % self.name)
 
    def shit(self):
        print("%s 睡 " % self.name)
 
 
class Cat(Animal):
    def __init__(self, name):
        self.name = name
        self.breed = '猫'
 
    def cry(self):
        print('喵喵叫')
 
 
class Dog(Animal):
    def __init__(self, name):
        self.name = name
        self.breed = '狗'
 
    def cry(self):
        print('汪汪叫')
 
 
c1 = Cat('小白家的小黑猫')
c1.eat()
 
c2 = Cat('小黑的小白猫')
c2.drink()
 
d1 = Dog('胖子家的小瘦狗')
d1.eat()
 
 
# -------------------------单继承与多继承-----------------------------------------
class p1:
    pass
 
 
class p2:
    pass
 
 
class p3(p1):
    pass
 
 
class p4(p1, p2):
    pass
 
 
print(p3.__bases__)  # (<class '__main__.p1'>,)
print((p4.__bases__))  # (<class '__main__.p1'>, <class '__main__.p2'>)
 
 
# 一例看继承
class Foo:
    def f1(self):
        print('Foo.f1')
 
    def f2(self):
        print('Foo.f2')
        self.f1()
 
 
class Bar(Foo):
    def f1(self):
        print('Bar.f1')
 
 
= Bar()
b.f2()
# 调用一个实例化对象的方法,它会先到自己的所属类中找,找不到再到父类的。
# 也就是说,当子类定义了自己的属性且与父类重名时,调用的时候以自己为准,所以不要认为是子类覆盖了父类的类属性。
 
# -------------------------经典类和新式类-----------------------------------------
# 关于这个继承又要引出一个概念,就是经典类和新式类
# 1.只有在python2中才分新式类和经典类,python3中统一都是新式类
# 2.在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类
# 3.在python2中,显式地声明继承object的类,以及该类的子类,都是新式类
# 3.在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类
# 4.object是所有python类的基类,它实现了一些常见方法(如之前说明的类内置的特殊属性等。

 继承基本知识

1.当类是经典类时,多继承情况下,会按照深度优先方式查

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
class A(object):
    def test(self):
        print('from A')
 
class B(A):
    def test(self):
        print('from B')
 
class C(A):
    def test(self):
        print('from C')
 
class D(B):
    def test(self):
        print('from D')
 
class E(C):
    def test(self):
        print('from E')
 
class F(D,E):
    # def test(self):
    #     print('from F')
    pass
f1=F()
f1.test()
print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性
 
#新式类继承顺序:F->D->B->E->C->A
#经典类继承顺序:F->D->B->A->E->C
#python3中统一都是新式类
#pyhon2中才分新式类与经典类

  

对于定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个列表就是一个简单的所有基类的线性顺序表。

所有当我们想知道继承顺序的时候,不用拿着笔算,可以直接用“类名.__mro__ ”查看即可,需要注意的是python2中没有这个方法。

为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。

而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:

1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
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
53
54
55
# 子类调用父类的方法一共有两种方式
# 方法一:父类名.父类方法()
class Animal:
    def __init__(self, name):
        self.name = name
 
    def eat(self):
        print("%s 吃 " % self.name)
 
    def drink(self):
        print("%s 喝 " % self.name)
 
    def shit(self):
        print("%s 睡 " % self.name)
 
 
class Cat(Animal):
    def __init__(self, name):
        Animal.__init__(self, name)
 
    def jiao(self):
        print('%s喵喵叫'% self.name)
 
xiaohua = Cat("小花")
xiaohua.jiao()        # 小花喵喵叫
 
# 方法二:super
class Animal:
    def __init__(self, name):
        self.name = name
 
    def eat(self):
        print("%s 吃 " % self.name)
 
    def drink(self):
        print("%s 喝 " % self.name)
 
    def shit(self):
        print("%s 睡 " % self.name)
 
 
class Cat(Animal):
    def __init__(self, name):
        # super().__init__(name)      # 第一种写法
        super(Cat, self).__init__(name)     # 第二种写法,推荐
 
    def jiao(self):
        print('%s喵喵叫'% self.name)
 
xiaohua = Cat("小花")
xiaohua.jiao()      # 小花喵喵叫
 
# 需要注意的是,如果用的是super().属性去查找,它会基于Cat.mro()继续往后查找。
 
子类调用父类的两种方式

 下面是两种方式的区别

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
53
54
55
56
57
58
59
60
61
#父类名.父类方法()
class A:
    def __init__(self):
        print('A的构造方法')
class B(A):
    def __init__(self):
        print('B的构造方法')
        A.__init__(self)
 
 
class C(A):
    def __init__(self):
        print('C的构造方法')
        A.__init__(self)
 
 
class D(B,C):
    def __init__(self):
        print('D的构造方法')
        B.__init__(self)
        C.__init__(self)
 
    pass
f1=D() #A.__init__被重复调用
'''
D的构造方法
B的构造方法
A的构造方法
C的构造方法
A的构造方法
'''
 
 
#使用super()
class A:
    def __init__(self):
        print('A的构造方法')
class B(A):
    def __init__(self):
        print('B的构造方法')
        super(B,self).__init__()
 
 
class C(A):
    def __init__(self):
        print('C的构造方法')
        super(C,self).__init__()
 
 
class D(B,C):
    def __init__(self):
        print('D的构造方法')
        super(D,self).__init__()
 
f1=D() #super()会基于mro列表,往后找
'''
D的构造方法
B的构造方法
C的构造方法
A的构造方法
'''
01-20 23:38