Python的类和对象的初识

类是面向对象的重要内容,可以把类当成一种自定义类型,可以使用类来定义变量,也可以使用类来创建对象。

定义类

在面向对象的程序设计过程中有两个重要概念: 类( class )和对象( object ,也被称为实例,instance ),其中类是某一批对象的抽象,可以把类理解成某种概念;对象才是一个具体存在的实体。从这个意义上看,日常所说的人,其实都是人这个类的实体,而不是人这个类。

类定义由类头(指 class 关键字和 类名部分)和统一缩进的类体构成。Python 定义类的简单语法如下:

1
2
3
4
class 类名:
执行语句...
零个到多个类变量...
一个到多个方法...

Python 的类名必须是由一个或多个有意义的单词连缀而成的,每个单词首字母大写,其他字母全部小写,单词与单词之间不要使用任何分隔符。

Python 类所包含的最重要的两个成员就是变量和方法,其中类变量属于类本身,用于定义该类本身所包含的属性,比如人类的角色属性是人。而实例变量则属于该类的对象,用于定义对象所包含的属性,比如某个人的姓名、年龄、性别。方法则用于定义该类的对象的行为或功能,比如人类都会走、会说话。类中各成员之间的定义顺序没有任何影响,各成员之间可以相互调用。

下面程序将定义一个 Person 类:

1
2
3
4
5
6
7
class Person:
"""定义一个 Person 类"""
role = 'person' # 人的角色属性都是人

# 下面定义了一个 Person 类的 say 方法(功能)
def say(self, content): # 人都可以说话,也就是有一个说话的功能
print('%s say: %s' % (self.name, content))

属性引用

在定义了类之后,接下来即可使用该类了。对于类的属性,如果想要引用则使用 类.变量 的格式:

1
2
print(Person.role)  # 查看人的 role 属性
print(Person.say) # 引用人的走路方法,注意,这里不是在调用

类的实例化

在类中定义的方法默认是实例方法,定义实例方法与定义函数基本相同,只是实例方法的第一个参数会被绑定到方法的调用者(该类的实例)—— 因此实例方法至少应该定义一个参数,该参数通常会被命名为 self

在类中有一个特别的方法:__init__ ,这个方法被称为构造方法。构造方法用于构造该类的对象, Python 通过调用构造方法返回该类的对象 。如果没有为该类定义任何构造方法,那么 Python 会自动为该类定义一个只包含一个 self 参数的默认的构造方法。

1
2
3
4
5
6
7
8
9
10
11
12
class Person:
"""定义一个 Person 类"""
role = 'person' # 人的角色属性都是人

def __init__(self, name='Silence', age=18): # 定义一个对象的构造方法
# 下面为 Person 类的对象增加两个实例变量
self.name = name
self.age = age

# 下面定义了一个 Person 类的 say 方法(功能)
def say(self, content): # 人都可以说话,也就是有一个说话的功能
print('%s say: %s' % (self.name, content))

注意: 实例方法的第一个参数并不一定要叫 self,其实完全可以叫任意参数名,只是约定俗成地把该参数命名为 self,这样具有最好的可读性。

类名加括号就是实例化,这将会自动触发 __init__ 函数的运行,可以用它来为每个实例定制自己的特征。实例化的过程其实就是从类构造出对象的过程。调用某个类的构造方法即可创建这个类的对象:

1
2
3
4
5
p = Person()
# 调用 Person 类的构造方法,返回一个 Person 对象
# Person() 就等于在执行 Person.__init__()
# 执行完 __init__() 就会返回一个对象
# 这个对象类似一个字典,存着属于这个人本身的一些属性和方法

对象的使用

原本我们只有一个 Person 类,在这个过程中产生了一个 p 对象,有自己具体的名字、年龄。

查看对象属性的语法是 对象.变量 ,调用对象方法的语法是 对象.方法(参数) 。在这种方式中,对象是主调者,用于访问该对象的变量或方法。

1
2
3
4
5
6
7
8
9
10
11
12
# 输出 p 的 name、age 实例变量
print(p.name, p.age) # Sinence 18

# 访问 p 的 name 实例变量,直接为该实例变量赋值
p.name = '李刚'

# 调用 p 的 say() 方法,在声明 say() 方法时定义了两个形参
# 但第一个形参(self) 是自动绑定的,因此调用该方法只需为第二个形参指定一个值
p.say('Python 很简单,学习很容易!') # 李刚 say: Python 很简单,学习很容易!

# 再次输出 p 的 name、 age 实例变量
print(p.name, p.age) # 李刚 18

上面程序开始访问了 p 对象的 name 、age 两个实例变量。这两个变量是何时定义的?留意在 Person 的构造方法中有如下两行代码:

1
2
self.name = name
self.age = age

这两行代码用传入的 name 、age 参数(这两个参数都有默认值)对 self 的 name 、age 变量赋值。由于 Python 的第一个 self 参数是自动绑定的(在构造方法中自动绑定到该构造方法初始化的对象),而这两行代码就是对 self 的 name 、age 两个变量赋值,也就是对该构造方法初始化的对象( p 对象)的 name 、age 变量赋值,即为 p 对象增加了 name 、age 两个实例变量。self 最大的作用就是引用当前方法的调用者

上面代码中通过 Person 对象调用了 say() 方法,在调用方法时必须为方法的形参赋值。但 say () 方法的第一个形参 self 是自动绑定的,它会被绑定到方法的调用者( p ),因此程序只需要为 say() 方法传入一个字符串作为参数值,这个字符串将被传给 content 参数。

我们定义的类的属性到底存到哪里了?有两种方式查看:

  • dir(类名):查出的是一个名字列表
  • 类名.__dict__:查出的是一个字典,key为属性名,value为属性值

特殊的类属性

1
2
3
4
5
6
7
类名.__name__  # 类的名字(字符串)
类名.__doc__ # 类的文档字符串
类名.__base__ # 类的第一个父类
类名.__bases__ # 类所有父类构成的元组
类名.__dict__ # 类的字典属性
类名.__module__ # 类定义所在的模块
类名.__class__ # 实例对应的类(仅新式类中)

对象

假设你现在是一家游戏公司的开发人员,现在需要你开发一款叫做 <人狗大战> 的游戏,这个游戏中至少需要 2 个角色,一个是人, 一个是狗,且人和狗都有不同的技能,比如人拿棍打狗, 狗可以咬人,怎么描述这种不同的角色和功能呢?

人类除了有自己的昵称之外,还应该具备攻击力,生命值等属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person:
"""定义一个 Person 类"""
role = 'person' # 人的角色属性都是人

def __init__(self, name, aggressivity, life_value): # 定义一个对象的构造方法
self.name = name # 每一个对象都应该有自己的昵称
self.aggressivity = aggressivity # 每一个角色都有自己的攻击力
self.life_value = life_value # 每一个角色都有自己的生命值

def attack(self, dog):
# 人可以攻击狗,这里的狗也是一个对象。
# 人攻击狗,那么狗的生命值就会根据人的攻击力而下降
dog.life_value -= self.aggressivity

对象是关于类而实际存在的一个例子,即实例。对象(实例)只有一种作用,即属性引用:

1
2
3
4
5
6
7
8
9
liubei = Person('刘备', 10, 1000)
print(liubei.name)
print(liubei.aggressivity)
print(liubei.life_value)
'''
刘备
10
1000
'''

当然了,对象也可以引用一个方法,因为方法也是一个属性,只不过是一个类似函数的属性,我们也管它叫动态属性。引用动态属性并不是执行这个方法,要想调用方法和调用函数是一样的,都需要在后面加上括号:

1
2
3
4
print(liubei.attack)
'''
<bound method Person.attack of <__main__.Person object at 0x7fb57de2f860>>
'''

可能有些难以理解,下面用函数的实现方式来解释一下类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def Person(*args, **kwargs):
self = {}

def attack(self, dog):
dog['life_value'] -= self['aggressivity']

def __init__(name, aggressivity, life_value):
self['name'] = name
self['aggressivity'] = aggressivity
self['life_value'] = life_value
self['attack'] = attack

__init__(*args, **kwargs)
return self


liubei = Person('刘备', 78, 10)
print(liubei['name'])

理解了对象之后,接下来我们定义一个圆形类,并为其提供计算面积和周长的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from math import pi


class Circle:
"""
定义了一个圆形类
提供计算面积(area)和周长(perimeter)的方法
"""

def __init__(self, radius):
self.radius = radius

def area(self):
return pi * (self.radius ** 2)

def perimeter(self):
return 2 * pi * self.radius


c1 = Circle(5) # 从圆形类实例化出一个圆 c1
print(c1.area()) # 计算圆的面积
print(c1.perimeter()) # 计算圆的周长

对象之间的交互

现在已经有一个人类了,通过给人类一些具体的属性我们就可以拿到一个实实在在的人。现在要再创建一个狗类,狗就不能打人了,只能咬人,所以我们给狗一个 bite 方法。有了狗类,我们还要实例化一只实实在在的狗出来。然后人和狗就可以打架了。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Dog:  # 定义一个狗类
role = 'dog' # 狗的角色属性都是狗

def __init__(self, name, breed, aggressivity, life_value):
self.name = name # 每一只狗都有自己的昵称
self.breed = breed # 每一只狗都有自己的品种
self.aggressivity = aggressivity # 每一只狗都有自己的攻击力
self.life_value = life_value # 每一只狗都有自己的生命值

def bite(self, preson):
# 狗可以咬人,这里的狗也是一个对象。
# 狗咬人,那么人的生命值就会根据狗的攻击力而下降
person.life_value -= self.aggressivity

实例化出一只实实在在的二哈:

1
ha2 = Dog('二愣子', '哈士奇', 10, 1000)  # 创造了一只实实在在的狗ha2

交互 ha2 与 liubei 打一下:

1
2
3
4
5
6
7
8
print(ha2.life_value)  # 看看ha2的生命值
liubei.attack(ha2) # liubei 打了 ha2 一下
print(ha2.life_value) # ha2 掉了10 点血

'''
1000
990
'''

完整代码如下:

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
class Person:
"""定义一个 Person 类"""
role = 'person' # 人的角色属性都是人

def __init__(self, name, aggressivity, life_value): # 定义一个对象的构造方法
self.name = name # 每一个对象都应该有自己的昵称
self.aggressivity = aggressivity # 每一个角色都有自己的攻击力
self.life_value = life_value # 每一个角色都有自己的生命值

def attack(self, dog):
# 人可以攻击狗,这里的狗也是一个对象。
# 人攻击狗,那么狗的生命值就会根据人的攻击力而下降
dog.life_value -= self.aggressivity


class Dog:
"""定义一个狗类"""
role = 'dog' # 狗的角色属性都是狗

def __init__(self, name, breed, aggressivity, life_value):
self.name = name # 每一只狗都有自己的昵称
self.breed = breed # 每一只狗都有自己的品种
self.aggressivity = aggressivity # 每一只狗都有自己的攻击力
self.life_value = life_value # 每一只狗都有自己的生命值

def bite(self, preson):
# 狗可以咬人,这里的狗也是一个对象。
# 狗咬人,那么人的生命值就会根据狗的攻击力而下降
person.life_value -= self.aggressivity


liubei = Person('刘备', 10, 1000)
ha2 = Dog('二愣子', '哈士奇', 10, 1000) # 创造了一只实实在在的狗ha2

print(ha2.life_value) # 看看ha2的生命值
liubei.attack(ha2) # liubei 打了 ha2 一下
print(ha2.life_value) # ha2 掉了10 点血

'''
1000
990
'''

命名空间

创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性。而类有两种属性:

  • 静态属性:接在类中定义的变量
  • 动态属性:定义在类中的方法

并且类的数据属性是共享给所有对象的:

1
2
3
4
5
6
print(id(ha2.role))
print(id(Dog.role))
'''
140481389229648
140481389229648
'''

类的动态属性是绑定到所有对象的:

1
2
3
4
5
6
print(ha2.bite)
print(Dog.bite)
'''
<bound method Dog.bite of <__main__.Dog object at 0x7f6bac22db00>>
<function Dog.bite at 0x7f6bac199d90>
'''

创建一个对象(实例)就会创建一个对象(实例)的名称空间,存放对象(实例)的名字,称为对象(实例)的属性。

obj.name 会先从 obj 自己的名称空间里找 name,找不到则去类中找,类也找不到就找父类…最后都找不到就抛出异常。

面向对象的组合用法

代码重用的重要方式除了继承之外还有另外一种方式,即组合。在一个类中以另外一个类的对象作为数据属性,称为类的组合。

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
class Weapon:
def prick(self, obj): # 这是该装备的主动技能,扎死对方
obj.life_value -= 500 # 假设攻击力是500


class Person:
role = 'person'

def __init__(self, name, aggr, life_value):
self.name = name
self.aggr = aggr
self.life_value = life_value
self.weapon = Weapon() # 给角色绑定一个武器;

def attack(self, dog):
dog.life_value -= self.aggr


class Dog:
role = 'dog'

def __init__(self, name, aggr, life_value):
self.name = name
self.aggr = aggr
self.life_value = life_value

def bite(self, person):
person.life_value -= self.aggr


ha2 = Dog(name='二哈', aggr=100, life_value=1000)
liubei = Person(name='刘备', aggr=120, life_value=1000)

print(ha2.life_value)
liubei.weapon.prick(ha2)
# liubei 组合了一个武器的对象,可以直接 liubei.weapon 来使用组合类中的所有方法
print(ha2.life_value)

'''
1000
500
'''

圆环是由两个圆组成的,圆环的面积是外面圆的面积减去内部圆的面积。圆环的周长是内部圆的周长加上外部圆的周长。这个时候,我们就首先实现一个圆形类,计算一个圆的周长和面积。然后在 “环形类” 中组合圆形的实例作为自己的属性来用:

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
from math import pi


class Circle:
"""
定义了一个圆形类
提供计算面积(area)和周长(perimeter)的方法
"""

def __init__(self, radius):
self.radius = radius

def area(self):
return pi * (self.radius ** 2)

def perimeter(self):
return 2 * pi * self.radius


c1 = Circle(5) # 从圆形类实例化出一个圆 c1
print(c1.area()) # 计算圆的面积
print(c1.perimeter()) # 计算圆的周长


class Ring:
"""
定义一个圆环类
提供圆环的面积和周长计算方法
"""

def __init__(self, radius_outside, radius_inside):
self.outside_circle = Circle(radius_outside)
self.inside_circle = Circle(radius_inside)

def area(self):
return self.outside_circle.area() - self.inside_circle.area()

def perimeter(self):
return self.outside_circle.perimeter() + self.inside_circle.perimeter()


ring = Ring(10, 5) # 实例化出一个圆环对象
print(ring.area()) # 计算圆环的面积
print(ring.perimeter()) # 计算圆环的周长

用组合的方式建立了类与组合的类之间的关系,它是一种 ‘有’ 的关系,比如教授有生日,教授教 Python 课程。

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
class BirthDate:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day


class Course:
def __init__(self, name, price, period):
self.name = name
self.price = price
self.period = period


class Teacher:
def __init__(self, name, gender, birth, course):
self.name = name
self.gender = gender
self.birth = birth
self.course = course

def teach(self):
print('teaching')


t1 = Teacher(
name='Jerry',
gender='male',
birth=BirthDate(year='1995', month='3', day='17'),
course=Course(name='Python', price='30000', period='4 months')
)

print(t1.birth.year, t1.birth.month, t1.birth.day)
print(t1.course.name, t1.course.price, t1.course.period)

"""
1995 3 17
Python 30000 4 months
"""

当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好。

类的三大特性之继承

继承是一种创建新类的方式,在 Python 中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类。

Python 中类的继承分为:单继承和多继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ParentClass1:  # 定义父类
pass


class ParentClass2: # 定义父类
pass


class SubClass1(ParentClass1): # 单继承,基类是 ParentClass1,派生类是 SubClass1
pass


class SubClass2(ParentClass1, ParentClass2): # Python 支持多继承,用逗号分隔开多个继承的类
pass

使用类的内置属性 __bases__ 即可查看对应的类所有继承的父类:

1
2
3
4
5
6
7
8
9
print(SubClass1.__bases__)
'''
(<class '__main__.ParentClass1'>,)
'''

print(SubClass2.__bases__)
'''
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
'''

如果对应的类没有所继承的父类,则 Python 会默认继承 object 类。并且 object 是所有 Python 类的父类,它提供了一些常见方法(如 __str__ )的实现。

1
2
3
4
5
6
7
8
9
print(ParentClass1.__bases__)
'''
(<class 'object'>,)
'''

print(ParentClass2.__bases__)
'''
(<class 'object'>,)
'''

继承与抽象

抽象,即抽取类似或者说比较相像的部分。抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)。

如上图所示,抽象分成两个层次:

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
class Cat:

def __init__(self, name):
self.name = name
self.breed = '猫'

def eat(self):
print("%s 吃 " % self.name)

def drink(self):
print("%s 喝 " % self.name)

def climb(self):
print('爬树')


class Dog:

def __init__(self, name):
self.name = name
self.breed = '狗'

def eat(self):
print("%s 吃 " % self.name)

def drink(self):
print("%s 喝 " % self.name)

def look_after_house(self):
print('汪汪叫')

从上述代码中不难看出,吃和喝是猫类和狗类都具有的功能,但是这共有的功能在上面分别为猫和狗的类中编写了两次。如果使用了类的继承则可以实现代码的重用:

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
lass Animal:

def eat(self):
print("%s 吃 " % self.name)

def drink(self):
print("%s 喝 " % self.name)


class Cat(Animal):

def __init__(self, name):
self.name = name
self.breed = '猫'

def climb(self):
print('爬树')


class Dog(Animal):

def __init__(self, name):
self.name = name
self.breed = '狗'

def look_after_house(self):
print('汪汪叫')


c1 = Cat('小白家的小黑猫')
c1.eat()

c2 = Cat('小黑家的小白猫')
c2.drink()

d1 = Dog('胖子家的小瘦狗')
d1.eat()
'''
小白家的小黑猫 吃
小黑家的小白猫 喝
胖子家的小瘦狗 吃
''

这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大。

派生

子类也可以添加自己新的属性或者自己重新定义这些属性(不会影响到父类)。需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。

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
class Animal:
'''
人和狗都是动物,所以创造一个 Animal 基类
'''

def __init__(self, name, aggressivity, life_value):
self.name = name # 人和狗都有自己的昵称;
self.aggressivity = aggressivity # 人和狗都有自己的攻击力;
self.life_value = life_value # 人和狗都有自己的生命值;

def eat(self):
print('%s is eating' % self.name)


class Dog(Animal):
'''
狗类,继承 Animal 类
'''

def bite(self, people):
'''
派生:狗有咬人的技能
:param people:
'''
people.life_value -= self.aggressivity


class Person(Animal):
'''
人类,继承 Animal
'''

def attack(self, dog):
'''
派生:人有攻击的技能
:param dog:
'''
dog.life_value -= self.aggressivity


liubei = Person('刘备', 10, 1000)
ha2 = Dog('二哈', 50, 1000)
print(ha2.life_value)
print(liubei.attack(ha2))
print(ha2.life_value)

在上述代码中,像 ha2.life_value 之类的属性引用,会先从实例的命名空间中找 life_value ,然后去类中找,然后再去父类中找,直到最顶级的父类。

super

在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是 self 参数也要为其传值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class A:
def hahaha(self):
print('A')


class B(A):
def hahaha(self):
A.hahaha(self)
print('B')


a = A()
b = B()
b.hahaha()
'''
A
B
'''

在 Python3 中,子类执行父类的方法也可以直接用 super 方法:

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
class A:
def hahaha(self):
print('A')


class B(A):
def hahaha(self):
# 方法一:
super().hahaha()
# 方法二:
super(B, self).hahaha()
print('B')


a = A()
b = B()
b.hahaha()

# 方法三:
super(B, b).hahaha()

'''
A
A
B
A
'''

再比如人狗大战中的应用:

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

class Animal:
'''
人和狗都是动物,所以创造一个 Animal 基类
'''

def __init__(self, name, aggressivity, life_value):
self.name = name # 人和狗都有自己的昵称;
self.aggressivity = aggressivity # 人和狗都有自己的攻击力;
self.life_value = life_value # 人和狗都有自己的生命值;

def eat(self):
print('%s is eating' % self.name)


class Dog(Animal):
'''
狗类,继承Animal类
'''

def __init__(self, name, breed, aggressivity, life_value):
super().__init__(name, aggressivity, life_value) # 执行父类Animal的init方法
self.breed = breed # 派生出了新的属性

def bite(self, people):
'''
派生出了新的技能:狗有咬人的技能
:param people:
'''
people.life_value -= self.aggressivity

def eat(self):
# Animal.eat(self)
# super().eat()
print('from Dog')


class Person(Animal):
'''
人类,继承Animal
'''

def __init__(self, name, aggressivity, life_value, money):
# Animal.__init__(self, name, aggressivity, life_value)
# super(Person, self).__init__(name, aggressivity, life_value)
super().__init__(name, aggressivity, life_value) # 执行父类的init方法
self.money = money # 派生出了新的属性

def attack(self, dog):
'''
派生出了新的技能:人有攻击的技能
:param dog:
'''
dog.life_value -= self.aggressivity

def eat(self):
# super().eat()
Animal.eat(self)
print('from Person')


liubei = Person('刘备', 10, 1000, 600)
ha2 = Dog('二哈', '哈士奇', 10, 1000)
print(liubei.name)
print(ha2.name)
liubei.eat()

'''
刘备
二哈
刘备 is eating
from Person
''

通过继承建立了派生类与基类之间的关系,它是一种 ‘是’ 的关系,比如白马是马,人是动物。当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如教授是老师。

抽象类与接口类

接口类

继承有两种用途:

一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)。

二:声明某个子类兼容于某基类,定义一个接口类 Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能。

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 Alipay:
'''
支付宝支付
'''

def pay(self, money):
print('支付宝支付了%s元' % money)


class Applepay:
'''
apple pay支付
'''

def pay(self, money):
print('Applepay支付了%s元' % money)


def pay(payment, money):
'''
支付函数,总体负责支付
对应支付的对象和要支付的金额
'''
payment.pay(money)


p = Alipay()
pay(p, 200)

'''
支付宝支付了200元
'''

开发中容易出现的问题,当一个程序员写好程序后,由于需求变更需要另外一个程序员补充功能:

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
class Alipay:
'''
支付宝支付
'''

def pay(self, money):
print('支付宝支付了%s元' % money)


class Applepay:
'''
apple pay支付
'''

def pay(self, money):
print('apple pay支付了%s元' % money)


class Wechatpay:
def fuqian(self, money):
'''
实现了pay的功能,但是名字不一样
'''
print('微信支付了%s元' % money)


def pay(payment, money):
'''
支付函数,总体负责支付
对应支付的对象和要支付的金额
'''
payment.pay(money)


p = Wechatpay()
pay(p, 200) # 执行会报错

'''
AttributeError: 'Wechatpay' object has no attribute 'pay'
'''

接口初成:主动抛出报异常 NotImplementedError 来解决开发中遇到的问题:

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 Payment:
'''
创建一个基类,使其他类都继承这个类,在子类中如果没有定义pay方法则会到这个类来调用
'''

def pay(self, money):
raise NotImplementedError


class Alipay(Payment):
'''
支付宝支付
'''

def pay(self, money):
print('支付宝支付了%s元' % money)


class Applepay(Payment):
'''
apple pay支付
'''

def pay(self, money):
print('apple pay支付了%s元' % money)


class Wechatpay(Payment):
def fuqian(self, money):
'''
实现了pay的功能,但是名字不一样
'''
print('微信支付了%s元' % money)


def pay(payment, money):
'''
支付函数,总体负责支付
对应支付的对象和要支付的金额
'''
payment.pay(money)


p = Wechatpay() # 这里不报错
pay(p, 200) # 这里报错了

'''
NotImplementedError
'''

借用 abc 模块来实现接口:

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
from abc import ABCMeta, abstractmethod


class Payment(metaclass=ABCMeta):
'''
创建一个基类,使其他类都继承这个作为规范的类
要求实现一个 pay 的方法
'''

@abstractmethod
def pay(self, money):
pass


class Alipay(Payment):
'''
支付宝支付
'''

def pay(self, money):
print('支付宝支付了%s元' % money)


class Applepay(Payment):
'''
apple pay支付
'''

def pay(self, money):
print('apple pay支付了%s元' % money)


class Wechatpay(Payment):
def fuqian(self, money):
'''
实现了pay的功能,但是名字不一样
'''
print('微信支付了%s元' % money)


def pay(payment, money):
'''
支付函数,总体负责支付
对应支付的对象和要支付的金额
'''
payment.pay(money)


p = Wechatpay() # 实例化的时候就报错了,能很快的定位到问题所在

'''
报错很明显:无法用抽象方法 pay 实例化抽象类 Wechatpay
TypeError: Can't instantiate abstract class Wechatpay with abstract methods pay
'''

实践中,继承的第一种用途意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。

继承的第二种用途非常重要。它又叫“接口继承”。接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。

归一化使得外部使用者可以不加区分地处理所有接口兼容的对象集合——就好象 linux 一样,一切皆文件,不必关心它是内存、磁盘、网络还是屏幕(对于底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。

1
2
依赖倒置原则:
高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程

在 Python 中根本就没有一个叫做 interface 的关键字,上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口 ,如果非要去模仿接口的概念,可以借助第三方模块:http://pypi.python.org/pypi/zope.interface 。twisted 的 twisted\internet\interface.py 里使用 zope.interface,文档 https://zopeinterface.readthedocs.io/en/latest/ 。设计模式:https://github.com/faif/python-patterns

为什么要用接口呢?

接口提取了一群类共同的函数,可以把接口当做一个函数的集合。然后让子类去实现接口中的函数。

这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。

归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。

比如:我们定义一个动物接口,接口里定义了有跑、吃、呼吸等接口函数,这样老鼠的类去实现了该接口,松鼠的类也去实现了该接口,由二者分别产生一只老鼠和一只松鼠送到你面前,即便是你分别不到底哪只是什么鼠你肯定知道他俩都会跑,都会吃,都能呼吸。

再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心开的是哪一类车,操作手法(函数调用)都一样。

抽象类

什么是抽象类

与 Java 一样,Python 也有抽象类的概念。但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化。

为什么要有抽象类

如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。

比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,在吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子,但永远无法吃到一个叫做水果的东西。

从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。

从实现角度来看,抽象类与普通类的不同之处在于:抽象类中有抽象方法,该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的。

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
# 一切皆文件
import abc # 利用abc模块实现抽象类


class All_file(metaclass=abc.ABCMeta):
all_type = 'file'

@abc.abstractmethod # 定义抽象方法,无需实现功能
def read(self):
'子类必须定义读功能'
pass

@abc.abstractmethod # 定义抽象方法,无需实现功能
def write(self):
'子类必须定义写功能'
pass


# class Txt(All_file):
# pass
#
# t1=Txt() #报错,子类没有定义抽象方法

class Txt(All_file): # 子类继承抽象类,但是必须定义read和write方法
def read(self):
print('文本数据的读取方法')

def write(self):
print('文本数据的读取方法')


class Sata(All_file): # 子类继承抽象类,但是必须定义read和write方法
def read(self):
print('硬盘数据的读取方法')

def write(self):
print('硬盘数据的读取方法')


class Process(All_file): # 子类继承抽象类,但是必须定义read和write方法
def read(self):
print('进程数据的读取方法')

def write(self):
print('进程数据的读取方法')


wenbenwenjian = Txt()

yingpanwenjian = Sata()

jinchengwenjian = Process()

# 这样大家都是被归一化了,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()

print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)

抽象类与接口类

抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如 all_type)和函数属性(如 read、write),而接口只强调函数属性的相似性。

抽象类是一个介于类和接口之间的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计

在 Python 中,并没有接口类这种东西,即便不通过专门的模块定义接口,我们也应该有一些基本的概念。

类的三大特性之多态

多态

多态指的是一类事物有多种形态。比如动物有多种形态:人,狗,猪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import abc


class Animal(metaclass=abc.ABCMeta): # 同一类事物:动物
@abc.abstractmethod
def talk(self):
pass


class People(Animal): # 动物的形态之一:人
def talk(self):
print('say hello')


class Dog(Animal): # 动物的形态之二:狗
def talk(self):
print('say wangwang')


class Pig(Animal): # 动物的形态之三:猪
def talk(self):
print('say aoao')

再比如,文件有多种形态:文本文件,可执行文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import abc


class File(metaclass=abc.ABCMeta): # 同一类事物:文件
@abc.abstractmethod
def click(self):
pass


class Text(File): # 文件的形态之一:文本文件
def click(self):
print('open file')


class ExeFile(File): # 文件的形态之二:可执行文件
def click(self):
print('execute file')

多态性

多态性是指在不考虑实例类型的情况下使用实例(在继承的背景下使用时,有时也称为多态性)。在面向对象方法中一般是这样表述多态性:

向不同的对象发送同一条消息(obj.func(): 是调用了 obj 的方法 func ,又称为向 obj 发送了一条消息 func ),不同的对象在接收时会产生不同的行为(即方法)。

也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。比如:老师.下课铃响了()学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
peo = People()
dog = Dog()
pig = Pig()

# peo、dog、pig 都是动物,只要是动物肯定有talk方法
# 于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo.talk()
dog.talk()
pig.talk()


# 更进一步,我们可以定义一个统一的接口来使用
def func(obj):
obj.talk()

鸭子类型

Python 崇尚鸭子类型,即 “如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子”

Python 程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象

也可以创建一个外观和行为很像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。

例1:利用标准库中定义的各种 ”与文件类似“ 的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用
class TxtFile:
def read(self):
pass

def write(self):
pass


class DiskFile:
def read(self):
pass

def write(self):
pass

鸭子类型不崇尚根据继承所得来的相似,只是自己实现功能就可以了。如果两个类刚好相似,并不产生父类的子类间兄弟关系,这就是鸭子类型。list 和 tuple 两个类比较相似,他们是在写代码的时候约束的,而不是通过父类约束的。

鸭子类型的优点是松耦合,每个相似的类之间都没有影响;缺点是代码写起来太随意,只能靠自觉。

例2:序列类型有多种形态:字符串,列表,元组,但他们之间没有直接的继承关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class List():
def __len__(self): pass


class Tuple():
def __len__(self): pass


def len(obj):
return obj.__len__()


l = Tuple()
len(l)

类的三大特性之封装

有钱任性,请我吃包辣条
0%