面向对象程序设计2

人狗大战

我们接到了一个游戏开发任务,需要开发一款叫做“人狗大战”的游戏。

思考:

人狗大战,那至少需要2个角色,一个是人, 一个是狗,且人和狗都有不同的技能,比如人可以打狗, 狗可以咬人,怎么描述这种不同的角色和他们的功能呢?

  • 角色
    • 狗:名字、种类、攻击力、生命值…
    • 人:名字、年龄
  • 动作
    • 狗咬人
    • 人打狗
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 用字典定义角色
#一条狗
dog = {
"name":"大黄",
"d_type":"金毛",
"attack_val":70,
"hp":100
}
# 用函数定义动作
def bite(person):
person.hp -= 30

# 另一条狗
# 参数相同,值不同
dog2 = {
"name":"老黑",
"d_type":"藏獒",
"attack_val":150,
"hp":150
}

为了避免重复,可以把字典也写成函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
def dog(name, d_type, attack_val, hp):
data = {
"name":name,
"d_type":d_type,
"attack_val":attack_val,
"hp":hp
}
return data
d1 = dog("大黄","金毛",70,70)
d2 = dog("老黑","藏獒",150,150)

print(d1)
print(d2)
{'name': '大黄', 'd_type': '金毛', 'attack_val': 70, 'hp': 70}
{'name': '老黑', 'd_type': '藏獒', 'attack_val': 150, 'hp': 150}

可以这样认为:

  • 函数dog就是一个狗的模板

  • 变量d1和d2就是基于这个模板创建的实体

我们还可以把狗的attack_val和与狗的d_type关联起来,给hp设置一个默认值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
attack_vals = {
"金毛":70,
"藏獒":150
}
def dog(name, d_type):
data = {
"name":name,
"d_type":d_type,
"hp":100
}
if d_type in attack_vals:
data["attack_val"] = attack_vals[d_type]
return data
d1 = dog("大黄","金毛")
d2 = dog("老黑","藏獒")

print(d1)
print(d2)
{'name': '大黄', 'd_type': '金毛', 'hp': 100, 'attack_val': 70}
{'name': '老黑', 'd_type': '藏獒', 'hp': 100, 'attack_val': 150}

接下来照方抓药,把人的角色也写出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def person(name, age):
data = {
"name":name,
"age":age,
"hp":100
}
if age > 18:
data["attack_val"] = 50
else:
data["attack_val"] = 30
return data

p1 = person('Tom', 25)
p2 = person('Jerry', 17)
print(p1)
print(p2)
{'name': 'Tom', 'age': 25, 'hp': 100, 'attack_val': 50}
{'name': 'Jerry', 'age': 17, 'hp': 100, 'attack_val': 30}

接下来是动作,动作可以让角色直接产生交互:

1
2
3
4
5
6
def dog_bite(dog_obj, person_obj):
person_obj['hp'] -= dog_obj['attack_val']
print('狗[%s]咬了人[%s]一口,人生命值减少[%s],生命值还剩[%s]...'%(dog_obj['name'],
person_obj['name'],
dog_obj['attack_val'],
person_obj['hp']))

合并一下,完成交互:

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
attack_vals = {
"金毛":70,
"藏獒":150
}
def dog(name, d_type):
data = {
"name":name,
"d_type":d_type,
"hp":100
}
if d_type in attack_vals:
data["attack_val"] = attack_vals[d_type]
return data
def person(name, age):
data = {
"name":name,
"age":age,
"hp":100
}
if age > 18:
data["attack_val"] = 50
else:
data["attack_val"] = 30
return data
def dog_bite(dog_obj, person_obj):
person_obj['hp'] -= dog_obj['attack_val']
print('狗[%s]咬了人[%s]一口,人生命值减少[%s],生命值还剩[%s]...'%(dog_obj['name'],
person_obj['name'],
dog_obj['attack_val'],
person_obj['hp']))
p1 = person('Tom', 25)
p2 = person('Jerry', 17)
d1 = dog("大黄","金毛")
d2 = dog("老黑","藏獒")
dog_bite(d1, p1)
狗[大黄]咬了人[Tom]一口,人生命值减少[70],生命值还剩[30]...

练习:

在刚才程序的基础上增加一个人打狗的函数person_beat

接下来考虑一个问题:

如果我们在交互的时候不小心写错了会怎么样?

比如:dog_bite(p1, d1)

1
2
3
4
5
6
def person_beat(person_obj, dog_obj):
dog_obj['hp'] -= person_obj['attack_val']
print('人[%s]打了狗[%s]一棒,狗生命值减少[%s],生命值还剩[%s]...'%(person_obj['name'],
dog_obj['name'],
person_obj['attack_val'],
dog_obj['hp']))
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
attack_vals = {
"金毛":70,
"藏獒":150
}
def dog(name, d_type):
data = {
"name":name,
"d_type":d_type,
"hp":100
}
if d_type in attack_vals:
data["attack_val"] = attack_vals[d_type]
return data
def person(name, age):
data = {
"name":name,
"age":age,
"hp":100
}
if age > 18:
data["attack_val"] = 50
else:
data["attack_val"] = 30
return data
def dog_bite(dog_obj, person_obj):
person_obj['hp'] -= dog_obj['attack_val']
print('狗[%s]咬了人[%s]一口,人生命值减少[%s],生命值还剩[%s]...'%(dog_obj['name'],
person_obj['name'],
dog_obj['attack_val'],
person_obj['hp']))
p1 = person('Tom', 25)
p2 = person('Jerry', 17)
d1 = dog("大黄","金毛")
d2 = dog("老黑","藏獒")
dog_bite(p1, d1)
狗[Tom]咬了人[大黄]一口,人生命值减少[50],生命值还剩[50]...

按照正常逻辑,dog_bite只能狗来使用,person_beat只能人使用,否则就会产生逻辑混乱。

怎么解决这个问题呢?

我们可以增加一些判断,也可以把dog_bite函数写到dog函数内部…

总结

其实上面写的代码,就是面向对象的代码。

我们在设计角色时,为了让一个角色可以变成多个实体对象,设计了一个基础模板,只要传入不同参数,就会产生不同的狗。

这代表我们已经开始切换成上帝视角看事情 ,上帝视角就是面向对象编程的视角,上帝要造世界万物,他肯定不是一个一个的造出来,他肯定是设计出一个个的物种的模板,然后通过模子批量批一个个的实体造出来。造出来的实体各有特色,属性、功能都不尽相同,这些人之间会发生什么关系 ,上帝懒的管,上帝只控制大局。

面向过程 VS 面向对象

编程范式

编程是程序员用特定的语法+数据结构+算法组成的代码来告诉计算机如何执行任务的过程 ,一个程序是程序员为了得到一个任务结果而编写的一组指令的集合。

正所谓条条大路通罗马,实现一个任务的方式有很多种不同的方式, 对这些不同的编程方式的特点进行归纳总结出来的编程方式类别,即为编程范式。

不同的编程范式本质上代表对各种类型的任务采取的不同的解决问题的思路, 大多数语言只支持一种编程范式,当然也有些语言可以同时支持多种编程范式。

两种最重要的编程范式分别是面向过程编程面向对象编程

面向过程编程(Procedural Programming)

Procedural programming uses a list of instructions to tell the computer what to do step-by-step.

面向过程编程依赖procedures,一个procedure包含一组要被进行计算的步骤, 面向过程又被称为top-down languages, 就是程序从上到下一步步执行,一步步从上到下,从头到尾的解决问题 。基本设计思路就是程序一开始是要着手解决一个大的问题,然后把一个大问题分解成很多个小问题或子过程,这些子过程再执行的过程再继续分解直到小问题足够简单到可以在一个小步骤范围内解决。

举个典型的面向过程的例子, 有个需求是对网站日志进行分析,生成邮件报告,整个流程分以下几步:

  1. 到各台服务器上收集日志,因为有多台网站服务器,共同对外提供服务

  2. 对日志进行各种维度分析,比如pv,uv, 来源地区、访问的设备等

  3. 生成报告,发送邮件

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1 整合日志
def collect_logs():
print("log on server A ,get access.log")
print("log on server B ,get access.log")
print("log on server C ,get access.log")
print("combine logs in to one file")
# 2 日志分析
def log_analyze(log_file):
print("pv、uv分析....")
print("用户来源分析....")
print("访问的设备来源分析....")
print("页面停留时间分析....")
print("入口页面分析....")
# 3 生成报告并发送
def send_report(report_data):
print("connect email server...")
print("send email....")
def main():
collect_logs()
log_analyze('my_db')
send_report()
if __name__ == '__main__':
main()

这样做的问题也是显而易见的,就是如果你要对程序进行修改,对你修改的那部分有依赖的各个部分你都也要跟着修改, 举个例子,如果程序开头你设置了一个变量值为1 ,但如果其它子过程依赖这个值为1的变量才能正常运行,那如果你改了这个变量,那这个子过程你也要修改,假如又有一个其它子程序依赖这个子过程 ,那就会发生一连串的影响,随着程序越来越大,这种编程方式的维护难度会越来越高。

所以我们一般认为, 如果你只是写一些简单的脚本,去做一些一次性任务,用面向过程的方式很合适,但如果你要处理的任务是复杂的,且需要不断迭代和维护的,那还是用面向对象比较好。

面向对象编程(Object-Oriented Programming)

OOP编程是利用“类”和“对象”来创建各种模型来实现对真实世界的描述,使用面向对象编程的原因一方面是因为它可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。

面向对象的几个核心特性:

  • Class 类

一个类即是对一类拥有相同属性的对象的抽象、蓝图、原型。在类中定义了这些对象的都具备的属性(variables(data))、共同的方法

前面我们写到“人狗大战”中dog、person其实就是

  • Object 对象

一个对象即是一个类的实例化后实例,一个类必须经过实例化后方可在程序中调用,一个类可以实例化多个对象,每个对象亦可以有不同的属性,就像人类是指所有人,每个人是指具体的对象,人与人之前有共性,亦有不同

前面我们写到“人狗大战”中d1、d2、p1、p2其实就是对象

  • Encapsulation 封装

在类中对数据的赋值、内部调用对外部用户是透明的,这使类变成了一个胶囊或容器,里面包含着类的数据和方法

  • Inheritance 继承

一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承

  • Polymorphism 多态

多态是面向对象的重要特性,简单点说:“一个接口,多种实现”,指一个基类中派生出了不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现,这就是同一种事物表现出的多种形态。

编程其实就是一个将具体世界进行抽象化的过程,多态就是抽象化的一种体现,把一系列具体事物的共同点抽象出来, 再通过这个抽象的事物, 与不同的具体事物进行对话。

对不同类的对象发出相同的消息将会有不同的行为。比如,你的老板让所有员工在九点钟开始工作, 他只要在九点钟的时候说:“开始工作”即可,而不需要对销售人员说:“开始销售工作”,对技术人员说:“开始技术工作”, 因为“员工”是一个抽象的事物, 只要是员工就可以开始工作,他知道这一点就行了。至于每个员工,当然会各司其职,做各自的工作。

多态允许将子类的对象当作父类的对象使用,某父类型的引用指向其子类型的对象,调用的方法是该子类型的方法。这里引用和调用方法的代码编译前就已经决定了,而引用所指向的对象可以在运行期间动态绑定。

面向对象vs面向过程总结

面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西。

优点是:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。

缺点是:一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身。

面向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来想了想解决这个问题需要四个人:唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的属性和方法),然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙互相缠斗着直到最后取得真经。如来根本不会管师徒四人按照什么流程去取。

面向对象的程序设计的

优点:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。

缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。于是我们经常看到一个游戏人某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。

应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。

面向对象程序设计

类的定义

1
help(list)
Help on class list in module builtins:

class list(object)
 |  list() -> new empty list
 |  list(iterable) -> new list initialized from iterable's items
 |  
 |  Methods defined here:
 |  
 |  __add__(self, value, /)
 |      Return self+value.
 |  
 |  __contains__(self, key, /)
 |      Return key in self.
 |  
 |  __delitem__(self, key, /)
 |      Delete self[key].
 |  
 |  __eq__(self, value, /)
 |      Return self==value.
 |  
 |  __ge__(self, value, /)
 |      Return self>=value.
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __getitem__(...)
 |      x.__getitem__(y) <==> x[y]
 |  
 |  __gt__(self, value, /)
 |      Return self>value.
 |  
 |  __iadd__(self, value, /)
 |      Implement self+=value.
 |  
 |  __imul__(self, value, /)
 |      Implement self*=value.
 |  
 |  __init__(self, /, *args, **kwargs)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  __iter__(self, /)
 |      Implement iter(self).
 |  
 |  __le__(self, value, /)
 |      Return self<=value.
 |  
 |  __len__(self, /)
 |      Return len(self).
 |  
 |  __lt__(self, value, /)
 |      Return self<value.
 |  
 |  __mul__(self, value, /)
 |      Return self*value.n
 |  
 |  __ne__(self, value, /)
 |      Return self!=value.
 |  
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.
 |  
 |  __repr__(self, /)
 |      Return repr(self).
 |  
 |  __reversed__(...)
 |      L.__reversed__() -- return a reverse iterator over the list
 |  
 |  __rmul__(self, value, /)
 |      Return self*value.
 |  
 |  __setitem__(self, key, value, /)
 |      Set self[key] to value.
 |  
 |  __sizeof__(...)
 |      L.__sizeof__() -- size of L in memory, in bytes
 |  
 |  append(...)
 |      L.append(object) -> None -- append object to end
 |  
 |  clear(...)
 |      L.clear() -> None -- remove all items from L
 |  
 |  copy(...)
 |      L.copy() -> list -- a shallow copy of L
 |  
 |  count(...)
 |      L.count(value) -> integer -- return number of occurrences of value
 |  
 |  extend(...)
 |      L.extend(iterable) -> None -- extend list by appending elements from the iterable
 |  
 |  index(...)
 |      L.index(value, [start, [stop]]) -> integer -- return first index of value.
 |      Raises ValueError if the value is not present.
 |  
 |  insert(...)
 |      L.insert(index, object) -- insert object before index
 |  
 |  pop(...)
 |      L.pop([index]) -> item -- remove and return item at index (default last).
 |      Raises IndexError if list is empty or index is out of range.
 |  
 |  remove(...)
 |      L.remove(value) -> None -- remove first occurrence of value.
 |      Raises ValueError if the value is not present.
 |  
 |  reverse(...)
 |      L.reverse() -- reverse *IN PLACE*
 |  
 |  sort(...)
 |      L.sort(key=None, reverse=False) -> None -- stable sort *IN PLACE*
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  __hash__ = None
1
2
3
4
5
6
7
8
9
class Dog:  # 类名首字母要大写,驼峰体
d_type = "金毛" # 公共属性,又称类变量
def say_hi(self): # 类的方法,必须带一个self参数,代表实例本身
print("hello , I am a dog,my type is ",self.d_type) # 想调用类里的属性,都要加上self.
d1 = Dog() # 生成一个狗的实例
d2 = Dog() # 生成一个狗的实例
d1.say_hi() # 调用狗这个类的方法 实例.方法
d2.say_hi()
print(d1.d_type) # 调用Dog类的公共属性
hello , I am a dog,my type is  金毛
hello , I am a dog,my type is  金毛
金毛

以上代码就是定义好了Dog这个类,相当于先生成了一个模板,接下来生成了2个实例d1, d2,相当于2条有血有肉的狗被创造出来了。

d_type是类变量,是Dog类下所有实例共有的属性,它存在Dog类本身的内存里。你可以查看d1.d_type,d2.d_type的内存地址,指向的是同一处。

除了共有属性,还有私有属性,比如每条狗的名字、年龄都不一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Dog:  # 类名首字母要大写,驼峰体
d_type = "金毛" # 公共属性,又称类变量
def __init__(self,name,age,master): # 初始化函数,只要一实例化,就会自动执行
print('初始化这个实例....',name)
self.name = name # self.name 就是实例自己的变量
self.age = age
self.master = master
def say_hi(self): # 类的方法,必须带一个self参数,代表实例本身
print("hello , I am a dog,my type is ",self.d_type) # 想调用类里的属性,都要加上self.
d1 = Dog("毛毛",2,"Alex") # 生成一个狗的实例
d2 = Dog("大黄",3,"Jack") # 生成一个狗的实例
d1.say_hi() # 调用狗这个类的方法
d2.say_hi()
print(d2.name, d2.age, d2.master) # 调用实例的变量
初始化这个实例.... 毛毛
初始化这个实例.... 大黄
hello , I am a dog,my type is  金毛
hello , I am a dog,my type is  金毛
大黄 3 Jack

我们并没有调用init(self,….),但它会自动执行,因为它叫初始化函数,就是在实例化的时候,用来初始化一些数据的,比如初始化你实例的名字、年龄等属性。

self就是代表实例本身,你实例化时Python会自动把这个实例本身通过self参数传进去。

self在实例化时自动将对象/实例本身传给init的第一个参数,你也可以给它起个别的名字,但是正常人都不会这么做,因为你瞎改别人就不认识。

属性引用

类的公共属性引用(类名.属性)

1
2
3
4
5
6
class Dog:  # 类名首字母要大写,驼峰体
d_type = "京巴" # 公共属性,又称类变量
def say_hi(self):
print("hello , I am a dog,my type is ",self.d_type)
print(Dog.d_type) # 查看Dog的d_type属性
print(Dog.say_hi) # 引用Dog的say_hi方法,注意只是引用,不是调用
京巴
<function Dog.say_hi at 0x10f045488>

实例属性引用(实例名.属性)

1
2
3
4
5
6
7
8
9
10
11
12
class Dog:  # 类名首字母要大写,驼峰体
d_type = "京巴" # 公共属性,又称类变量
def __init__(self,name,age,master): # 初始化函数,只要一实例化,就会自动执行
self.name = name # self.name 就是实例自己的变量
self.age = age
self.master = master
def say_hi(self):
print("hello , I am a dog,my type is ",self.d_type)
d2 = Dog("二蛋",3,"Jack") # 生成一个狗的实例
d2.say_hi() # 调用狗这个类的方法
print(d2.name, d2.age, d2.master) # 调用实例的属性
print(d2.d_type) # 注意通过实例也可以调用类的公共属性
hello , I am a dog,my type is  京巴
二蛋 3 Jack
京巴

练习

设计一个类Person,生成若干实例,在终端输出如下信息

小明,10岁,男,喜欢打篮球

小红,13岁,女,喜欢跳芭蕾

老李,40岁,男,喜欢吹牛

老张…

回顾

编程范式:编程方法论

最初,编程就是无组织无结构,按步骤写指令

后来提取重复代码和指令,组织到一起(定义函数),使程序结构化。但此时的数据与动作是分离的(还记得前面人狗大战中的“人咬狗”吗)。

再后来就产生了把数据和动作内嵌到一个结构中的编程方式,创建了对象系统。

1
2
3
4
5
class Point:
'''Represent a point in 2-D space.'''
a = Point()
print(Point)
print(a)
<class '__main__.Point'>
<__main__.Point object at 0x10f0536d8>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 回顾属性引用
class Person:
nationality = 'TW' #公共属性
def __init__(self, name, age, sex):
self.name = name #实例属性
self.age = age
self.sex = sex
p1 = Person('蔡某某', 50, 'F')
p2 = Person('吴某', 34, 'M')

# 修改国籍
Person.nationality = 'CN'

print(p1.nationality)
print(p2.nationality)

p1.nationality = 'JP' # 实例自己修改了国籍
print(p1.nationality)

print(Person.nationality)
CN
CN
JP
CN

练习

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
# 定义一个类描述平面上的点并提供移动点和计算到另一个点距离的方法。
from math import sqrt
class Point(object):
def __init__(self, x=0, y=0):
"""初始化方法

:param x: 横坐标
:param y: 纵坐标
"""
self.x = x
self.y = y

def move_to(self, x, y):
"""移动到指定位置

:param x: 新的横坐标
"param y: 新的纵坐标

"""
self.x = x
self.y = y

def move_by(self, dx, dy):
"""移动指定的增量

:param dx: 横坐标的增量
:param dy: 纵坐标的增量

"""
self.x += dx
self.y += dy

def distance_to(self, other):
"""计算与另一个点的距离

:param other: 另一个点

"""
dx = self.x - other.x
dy = self.y - other.y
return sqrt(dx ** 2 + dy ** 2)

#特殊方法__str__()用于把一个类的实例变成 str
def __str__(self):
return '(%s, %s)' % (str(self.x), str(self.y))

def main():
p1 = Point(3, 5)
p2 = Point()
print(p1)
print(p2)
p2.move_by(-1, 2)
print(p2)
print(p1.distance_to(p2))

if __name__ == '__main__':
main()
<__main__.Point object at 0x10f098cf8>
<__main__.Point object at 0x10f098080>
<__main__.Point object at 0x10f098080>
5.0

对象间的交互、组合

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
class Dog:  # 定义一个狗类
role = 'dog' # 狗的角色属性都是狗
def __init__(self, name, breed, attack_val):
self.name = name
self.breed = breed # 每一只狗都有自己的品种;
self.attack_val = attack_val # 每一只狗都有自己的攻击力;
self.life_val = 100 # 每一只狗都有自己的生命值;
def bite(self, person):
# 狗可以咬人,这里传递进来的person也是一个对象。
person.life_val -= self.attack_val # 狗咬人,那么人的生命值就会根据狗的攻击力而下降
print("狗[%s]咬了人[%s],人生命值减少[%s],还剩[%s]..." % /
(self.name,person.name,self.attack_val,person.life_val))
class Person: # 定义一个人类
role = 'person' # 人的角色属性都是人
def __init__(self, name, sex, attack_val):
self.name = name
self.attack_val = attack_val
self.life_val = 100
self.sex = sex
def attack(self,dog):
# 人可以攻击狗,这里传递进来的dog也是一个对象。
# 人攻击狗,那么狗的生命值就会根据人的攻击力而下降
dog.life_val -= self.attack_val
print("人[%s]打了狗[%s],狗生命值减少[%s],还剩[%s]..." % /
(self.name,dog.name,self.attack_val,dog.life_val))
d = Dog("大黄", "金毛", 20)
p = Person("老王", "Male", 60)
d.bite(p) # 对象交互, 把p实例传递给d的方法
p.attack(d)
狗[大黄]咬了人[老王],人生命值减少[20],还剩[80]...
人[老王]打了狗[大黄],狗生命值减少[60],还剩[40]...



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-41-89494cc1680a> in <module>()
     26 d.bite(p) # 对象交互, 把p实例传递给d的方法
     27 p.attack(d)
---> 28 p.bite(d)


AttributeError: 'Person' object has no attribute 'bite'

类与类之间的关系

大千世界, 万物之间皆有规则和规律. 我们的类和对象是对大千世界中的所有事物进行归类. 那事物之间存在着相对应的关系. 类与类之间也同样如此. 在面向对象的世界中. 类与类中存在以下关系:

  • 依赖关系,狗和主人的关系

  • 关联关系,你和你的工作搭档的关系就是关联关系

  • 聚合关系,电脑的各部件组成完整的电脑,电脑里有CPU, 硬盘, 内存等。 每个组件有自己的生命周期, 电脑挂了. CPU还是好的. 还是完整的个体

  • 组合关系,比聚合还要紧密.比如人的大脑, 心脏, 各个器官. 这些器官组合成一个人. 这时. 人如果挂了. 其他的东西也跟着挂了

  • 继承关系, 类的三大特性之一,子承父业

依赖关系

狗和主人的关系可以理解为是一种依赖关系,如果没有主人,它就是流浪狗了,可能会死。

1
2
3
4
5
6
7
8
9
10
11
12
class Dog:
def __init__(self,name,age,breed):
self.name = name
self.age = age
self.breed = breed


class Person:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Dog:
def __init__(self,name,age,breed,master):
self.name = name
self.age = age
self.breed = breed
self.master = master # master传进来的应该是个对象
self.sayhi() # 调用自己的方法在实例化的时候
def sayhi(self):
print("Hi,我是 %s, 一只%s狗,我的主人是%s" %/
(self.name,self.breed,self.master.name))
class Person:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def walk_dog(self,dog_obj):
"""遛狗"""
print("主人[%s]带狗[%s]去遛遛。。。" % /
(self.name,dog_obj.name ))
p = Person("赵四", 46, "Male")
d = Dog("小黑", 5, "腊肠", p)
p.walk_dog(d)
Hi,我是 小黑, 一只腊肠狗,我的主人是赵四
主人[赵四]带狗[小黑]去溜溜。。。

关联关系

你和你工作搭档的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex

def do_stuff(self):
pass
p1 = Person("张三", 25, "男")
p2 = Person("李四", 23, "女")

#建立关联关系

#解除关联关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
self.partner = None # 搭档,是个对象
def do_stuff(self):
"""和搭档一起完成某项工作"""
print("%s和%s一起完成项目开发。" %/
(self.name,self.partner.name))
p1 = Person("张三", 25, "男")
p2 = Person("李四", 23, "女")
p1.partner = p2 # 两个对象要互相绑定彼此
p2.partner = p1
p1.do_stuff()
p2.do_stuff()
张三和李四一起完成项目开发。



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-1-e568e80b056e> in <module>()
     13 #p2.partner = p1
     14 p1.do_stuff()
---> 15 p2.do_stuff()


<ipython-input-1-e568e80b056e> in do_stuff(self)
      7     def do_stuff(self):
      8         """和搭档一起完成某项工作"""
----> 9         print("%s和%s一起完成项目开发。" %(self.name,self.partner.name))
     10 p1 = Person("张三", 25,  "男")
     11 p2 = Person("李四", 23,  "女")


AttributeError: 'NoneType' object has no attribute 'name'

以上虽然实现了2个对象的关联,但细想其实是有问题的,张三和李四需要在自己的实例中分别绑定下彼此才能实现伴侣关系。假如有一方忘记了关联,那这个伙伴关系就只是单方面成立了,李四知道自己的伙伴是张三,张三却不识李四。

为了确保这两人的关系是一致的,怎么办呢?

可以创建个单独的类,存储2个人的关系状态,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
class RelationShip:
"""保存2个人的工作伙伴关系"""
def __init__(self):
self.partner = []
def make_partner(self,obj1,obj2):
self.partner.append(obj1)
self.partner.append(obj2)
print("[%s] 和 [%s] 成为搭档..." % (obj1.name,obj2.name))
def break_up(self):
if self.partner:
print("[%s] 和 [%s] 结束搭档关系。" % (self.partner[0].name,self.partner[1].name))
self.partner.clear()
else:
print("你根本就没搭档...")
def get_my_partner(self,obj):
"""返回我的搭档"""
for i in self.partner:
if obj != i: # partner列表里有2个值,一个是我自己,一个是我对象,只要跟传进来的obj不相等,代表找到了我对象
return i.name
else:
print("你没有搭档,自己不知道么....")
class Person:
def __init__(self,name,age,sex,relation_obj):
self.name = name
self.age = age
self.sex = sex
self.relation = relation_obj # 把RelationShip对象传进来
#self.partner = None # 另一半,是个对象
def do_stuff(self):
"""和搭档一起完成某项工作"""
print("%s和%s一起完成项目开发。" %(self.name,self.relation.get_my_partner(self)))
relation_obj = RelationShip()
p1 = Person("张三",25,"男",relation_obj)
p2 = Person("李四",23,"女",relation_obj)
relation_obj.make_partner(p1,p2) # 把2个人组合在一起
p1.do_stuff()
p2.do_stuff()
p1.relation.break_up() # 结束搭档关系
p1.relation.get_my_partner(p1) # 再去查,就没有搭档了
p2.relation.get_my_partner(p2) # 再去查,就没有搭档了
[张三] 和 [李四] 成为搭档...
张三和李四一起完成项目开发。
李四和张三一起完成项目开发。
[张三] 和 [李四] 结束搭档关系。
你没有搭档,自己不知道么....
你没有搭档,自己不知道么....

组合关系

组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合

由一堆组件构成一个完整的实体,组件本身独立,但又不能自己运行,必须组合在一起运行

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
class Dog:  # 定义一个狗类
role = 'dog' # 狗的角色属性都是狗
def __init__(self, name, breed, attack_val):
self.name = name
self.breed = breed # 每一只狗都有自己的品种;
self.attack_val = attack_val # 每一只狗都有自己的攻击力;
self.life_val = 100 # 每一只狗都有自己的生命值;
def bite(self, person):
# 狗可以咬人,这里传递进来的person也是一个对象。
person.life_val -= self.attack_val # 狗咬人,那么人的生命值就会根据狗的攻击力而下降
print("狗[%s]咬了人[%s],人生命值减少[%s],还剩[%s]..." % /
(self.name,person.name,self.attack_val,person.life_val))
class Weapon:
def stick(self,obj):
"""打狗棒"""
self.name = "打狗棒"
self.attack_val = 40
obj.life_val -= self.attack_val
self.print_log(obj)
def knife(self,obj):
"""屠龙刀"""
self.name = "屠龙刀"
self.attack_val = 80
obj.life_val -= self.attack_val
self.print_log(obj)
def gun(self,obj):
"""AK47"""
self.name = "AK47"
self.attack_val = 100
obj.life_val -= self.attack_val
self.print_log(obj)
def print_log(self,obj):
print("[%s]被[%s]攻击了,掉血[%s],还剩血量[%s]..." %/
(obj.name,self.name,self.attack_val,obj.life_val))
class Person: # 定义一个人类
role = 'person' # 人的角色属性都是人
def __init__(self, name, sex, attack_val):
self.name = name
self.attack_val = attack_val
self.life_val = 100
self.sex = sex
self.weapon = Weapon() # 在此处实例化一个Weapon对象
def attack(self,dog):
# 人可以攻击狗,这里传递进来的dog也是一个对象。
# 人攻击狗,那么狗的生命值就会根据人的攻击力而下降
dog.life_val -= self.attack_val
print("人[%s]打了狗[%s],狗掉血[%s],还剩血量[%s]..." %
(self.name,dog.name,self.attack_val,dog.life_val))
d = Dog("大黄","金毛",30)
p = Person("老李","Male",80)
d.bite(p) # 对象交互,把p实例传递给d的方法
p.attack(d)
p.weapon.knife(d) # 通过组合的方式调用weapon实例下的具体武器
p.weapon.stick(d)
狗[mjj]咬了人[Alex],人生命值减少[20],还剩[80]...
人[Alex]打了狗[mjj],狗掉血[60],还剩血量[40]...
[mjj]被[屠龙刀]攻击了,掉血[80],还剩血量[-40]...
[mjj]被[打狗棒]攻击了,掉血[40],还剩血量[-80]...

用组合的方式建立了类与组合的类之间的关系,它是一种‘有’或者”包含”的关系,比如老师有生日,老师教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
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 teaching(self):
print('teaching.....',self.course.name)
p1 = Teacher('Alex', 'Male',
BirthDate('1985', '1', '27'),
Course('Python', '2800', '1 months')
)
print(p1.birth.year, p1.birth.month, p1.birth.day)
print(p1.course.name, p1.course.price, p1.course.period)
1995 1 27
Python 28000 5 months

实践

设计一个扑克牌游戏:

两名玩家每人从牌堆中抽出一张牌,牌面最大的玩家获胜

牌面大小比较规则:2 最小,A 最大,值相同则比较花色,’黑桃’>’红桃’>’方块’>’梅花’

版本 0.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
# 扑克牌游戏
# 牌面大小比较规则:2 最小,A 最大,值相同则比较花色,'黑桃'>'红桃'>'方块'>'梅花'
class Card:
suits = ['黑桃','红桃','方块','梅花'] # spades/hearts/diamonds/clubs
values = [None, None, '2', '3',
'4', '5', '6', '7',
'8', '9', '10', 'J',
'Q', 'K', 'A'] # Jack/Queen/King/Ace

# 初始化,为对象传入 v,s 值
def __init__(self, v, s):
"""suit和 value 的值都为整型数"""
self.value = v
self.suit = s

# 定义__lt__方法,可用小于运算符比较对象,相当于重新定义了<
def __lt__(self, c2):
if self.value < c2.value:
return True
if self.value == c2.value:
if self.suit < c2.suit:
return True
else:
return False
else:
return False

# 定义__gt__方法,可用大于运算符比较对象,相当于重新定义了>
def __gt__(self, c2):
pass
# 用__repr__方法把一个类的实例变成 str,注意与__str__方法的区别
def __repr__(self):
v = self.suits[self.suit] + self.values[self.value]
return v


c1 = Card(3, 2)
c2 = Card(3, 3)
print(c1)
print(c2)
print(c1 < c2)
方块3

版本 0.2:定义牌堆

需要考虑:

  • 初始化时创建 52 张扑克牌
  • 定义一个方法从牌堆中随机抽一张牌并把牌从牌堆中删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from random import shuffle
class Deck:
def __init__(self):
"""初始化方法
cards: 列表,用于存储牌堆中的扑克牌
"""
self.cards = []
pass
shuffle(self.cards)
def rm_card(self):
pass

deck = Deck()
for card in deck.cards:
print(card)

版本 0.3:玩家

需要考虑:

  • 玩家姓名
  • 记录玩家当前手中的牌
  • 记录玩家赢的局数
1
2
3
4
5
6
class Player:
def __init__(self, name):
"""初始化方法
:param name: 玩家姓名
"""
pass

版本 0.4:游戏

需要考虑:

  • 初始化时获取两名玩家姓名并创建两个玩家对象
  • 初始化时创建一个 Deck 对象
  • 定义一个方法开始游戏,直到玩家选择退出或牌堆中的牌数不足两张
  • 在开始游戏的方法中实现发牌、比较、输出结果
  • 定义一个方法接受两个玩家对象,比较各自赢的局数,返回获胜次数多的玩家
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Game:
def __init__(self):
pass
def wins(self, winner):
pass
def draw(self, p1n, p1c, p2n, p2c):
"""抽牌
:param p1n: 玩家 1 姓名
:param p1c: 玩家 1 手中的牌
:param p2n: 玩家2 姓名
:param p2c: 玩家 2 手中的牌
"""
pass
def play_game(self):
pass
def winner(self, p1, p2):
"""返回比赛结果
:param p1: 玩家 1
:param p2: 玩家 2
"""
pass

版本 1.0:整合

1
2
3
4
5
6
7
8
9
10
class Card:
pass
class Deck:
pass
class Player:
pass
class Game:
pass
game = Game()
game.play_game()
1
2


三大特性之-继承

继承(inheritance)是面向对象软件技术当中的一个概念。如果一个类别A“继承自”另一个类别B,就把这个A称为“B的子类别”,而把B称为“A的父类别”也可以称“B是A的超类”。

继承可以使得子类别具有父类别的各种属性和方法,而不需要再次编写相同的代码。

在子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类别追加新的属性和方法也是常见的做法。

继承与抽象(先抽象再继承)

抽象即抽取类似或者说比较像的部分。

抽象分成两个层次:

1.将雷昂纳多和王思聪这俩对象比较像的部分抽取成类;

2.将人,猪,狗这三个类比较像的部分抽取成父类。

抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)

继承与抽象

继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。

抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类

继承与抽象

1
2
3
4
5
6
7
8
9
10
11
12
13
class Animal:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print("[%s] is eating..." % self.name)

class People(Animal):
pass

p = People()
p.eat()
[q] is eating...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Animal:
type = '哺乳动物'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print("[%s] is eating..." % self.name)
class People(Animal):
def talk(self):
print("People [%s] is talking..." % self.name)
class Pig(Animal):
def eat(self): #重写
print("Pig [%s] is eating..." % self.name)
class Dog(Animal):
def chase_rabbit(self):
print("Dog [%s] is chasing rabbit..." % self.name)
person = People("Alex",25,"Male")
pig = Pig("Mjj",4,"公")
dog = Dog("毛毛",3,"母")
person.talk()
pig.eat()
dog.chase_rabbit()
print(person.type)
People [Alex] is talking...
Pig [Mjj] is eating...
Dog [毛毛] is chasing rabbit...
哺乳动物

继承的优点显而易见:

1.增加了类的耦合性。

2.减少了重复代码。

3.使得代码更加规范化,合理化。

继承的分类

在上面的例子中:

  • Aminal 叫做父类,基类,超类。

  • Person Pig Dog: 子类,派生类。

继承:可以分单继承,多继承。

继承父类的方法&重写父类方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Animal:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print("[%s] is eating..."%self.name)

class People(Animal):
def walk(self):
print("People [%s] is walking..." % self.name)

class Pig(Animal):
def eat(self): # 重写了父类的方法
print("Pig [%s] is eating..." % self.name)

person = People("Tom",25,"Male")
pig = Pig("猪坚强",4,"公")
person.walk()
person.eat() # 继承自父类的eat方法
pig.eat()
People [Alex] is walking...
[Alex] is eating...
Pig [Mjj] is eating...

同时执行子类&父类方法

方法一:

如果想执行父类的init方法,这个方法并且子类中也用,那么就在子类的方法中写上:

父类.func(子类对象,其他参数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animal:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print("[%s] is eating..."%self.name)
class People(Animal):
def __init__(self,name,age,sex,race):
Animal.__init__(self,name,age,sex) # 先执行父类方法
self.race = race # 再加上子类的属性
print("初始化了一个人....")
def walk(self):
print("People [%s] is walking..." % self.name)
p = People('老李','35','M','汉')
初始化了一个人....

方法二:

利用super(),super().func(参数)

1
2
3
4
5
6
7
class People(Animal):
def __init__(self,name,age,sex,race):
#Animal.__init__(self,name,age,sex) # 这种方法在Python 2中常用
super(People,self).__init__(name,age,sex) # Python 3中常用
#super().__init__(name,age,sex) # 跟上面这行super语法的效果一样,一般用这种写法的多
self.race = race # 再加上子类的属性
print("初始化了一个人....")

多继承(Multiple Inheritance)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Fairy:
"""神仙类"""
def fly(self):
print("神仙都会飞...")

class Monkey:
def eat_peach(self):
print("猴子都喜欢吃桃子...")

class MonkeyKing(Fairy,Monkey):
def play_goden_stick(self):
print("孙悟空玩金箍棒...")

m = MonkeyKing()
m.eat_peach()
m.fly()
m.play_goden_stick()
猴子都喜欢吃桃子...
神仙都会飞...
孙悟空玩金箍棒...

此时, 孙悟空是一只猴子, 同时也是一个神仙. 那孙悟空继承了这两个类.

孙悟空自然就可以执行这两个类中的方法. 多继承用起来简单. 也很好理解.

假如在继承的多个类里出现了重名的方法怎么办呢? 比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Fairy:
"""神仙类"""
def fly(self):
print("神仙都会飞...")
def fight(self):
print("神仙在打架...")
class Monkey:
def eat_peach(self):
print("猴子都喜欢吃桃子...")
def fight(self):
print("猴子在打架...")
class MonkeyKing(Fairy,Monkey): # 换个位置试一下
def play_goden_stick(self):
print("孙悟空玩金箍棒...")
m = MonkeyKing()
m.fight()
神仙在打架...

调用m.fight()会打印哪个呢?

根据执行结果,我们得知,多继承的顺序是按参数 MonkeyKing(Fairy,Monkey)从左到右。

真的是从左到右么? 如果继承的父类又继承爷爷类,并且还重名了呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class FairyBase:
def fight(self):
print("神仙祖宗在打架....")
class MonkeyBase:
def fight(self):
print("猿猴在打架....")
class Fairy(FairyBase):
"""神仙类"""
def fly(self):
print("神仙都会飞...")
def fight(self):
print("神仙在打架...")
class Monkey(MonkeyBase):
def eat_peach(self):
print("猴子都喜欢吃桃子...")
def fight(self):
print("猴子在打架...")
class MonkeyKing(Fairy,Monkey):
def play_goden_stick(self):
print("孙悟空玩金箍棒...")
m = MonkeyKing()
m.fight()
神仙在打架...

此时,若把Fairy类里的fight注释掉,m.fight()会打印哪个? 是引用Monkey里的fight呢,还是FairyBase里的fight呢?

这个查找顺序是按什么规则呢?

在Python 中,有2种类的写法, 不同写法的采用的继承顺序不同

1
2
3
4
class A: # 经典类
pass
class B(object): # 新式类
pass

在python 2中,经典类采用的是深度优先查找法, 新式类采用的是广度优先

在python 3中,无论是经典类,还是新式类,都是按广度优先查找

其实是按一个叫C3算法来计算继承顺序的

C3算法不太好理解,不过确实也不太重要。 因为很少有人把继承写的这么复杂。 这种简单的多继承,C3算出来跟深度优先效果一样

三大特性之-封装

封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。

要访问该类的代码和数据,必须通过严格的接口控制。

封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。

适当的封装可以让程式码更容易理解与维护,也加强了代码数据的安全性。

1
2
3
4
5
6
7
8
9
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
self.life_val = 100

p = Person('Kim', 8)
p.life_val -= 50
print(p.life_val)

封装的优点

  1. 良好的封装能够减少耦合。

  2. 类内部的结构可以自由修改。

  3. 可以对成员变量进行更精确的控制。

  4. 隐藏信息,实现细节。

封装原则

  1. 将不需要对外提供的内容都隐藏起来;

  2. 把属性都隐藏,提供公共方法对其访问。

私有变量和私有方法

在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)

私有变量

1
2
3
4
5
6
7
8
9
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__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形

这种自动变形的特点:

1.类中定义的x只能在内部使用,如self.x,引用的就是变形的结果。

2.这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。

3.在子类定义的x不会覆盖在父类定义的x,因为子类中变形成了:子类名x,而父类中变形成了:父类名x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。

这种变形需要注意的问题是:

1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:类名_属性,然后就可以访问了,如a._A__N

2.变形的过程只在类的内部生效,在实例化后再定义的赋值操作,不会变形

私用变量的修改方法举例:

1
2
3
4
5
6
7
8
9
10
class Person(object):
def __init__(self,name,age):
self.name = name
self.age = age
self.__life_val = 100
def got_attack(self): # 只能通过方法去修改私有变量
self.__life_val -= 20
print("got attack ....,life val drops 20, got %s left.." %self.__life_val)
p = Person("Jack",22)
p.got_attack()

三大特性之-多态

多态概念

有时一个对象会有多种表现形式,比如网站页面有个button按钮, 这个button的设计可以不一样(单选框、多选框、圆角的点击按钮、直角的点击按钮等),尽管长的不一样,但它们都有一个共同调用方式,就是onClick()方法。我们直要在页面上一点击就会触发这个方法。点完后有的按钮会变成选中状态、有的会提交表单、有的甚至会弹窗。这种多个对象共用同一个接口,又表现的形态不一样的现象,就叫做多态( Polymorphism )。

Polymorphism is based on the greek words Poly (many) and morphism (forms).

通过统一函数接口实现多态:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Dog(object):
def sound(self):
print("汪汪汪.....")
class Cat(object):
def sound(self):
print("喵喵喵.....")
def make_sound(animal_type):
"""统一调用接口"""
animal_type.sound() # 不管你传进来是什么动物,我都调用sound()方法
dogObj = Dog()
catObj = Cat()
make_sound(dogObj)
make_sound(catObj)

通过抽象类实现多态(最常用)

假如你开发一个文本编辑器,支持多种文档类型, 在用户通过你的编辑器打开文件之前,你也不知道准备要打开的是什么类型的文件,可能是pdf,也可能是word.

假如你为每个文件类型都写一个类,每个类都通过show()方法来调用打开对应的文档,为了确保每个类都必须实现show()方法,你可以写一个抽象类。

继承与抽象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Document:
def __init__(self, name):
self.name = name
def show(self): # 子类必须重写方法
raise NotImplementedError("Subclass must implement abstract method")
class Pdf(Document):
def show(self):
return 'Show pdf contents!'
class Word(Document):
def show(self):
return 'Show word contents!'
documents = [Pdf('Document1'),
Pdf('Document2'),
Word('Document3')]
for document in documents:
print(document.name + ': ' + document.show())

继承与抽象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Car:
def __init__(self, name):
self.name = name
def drive(self):
raise NotImplementedError("Subclass must implement abstract method")
def stop(self):
raise NotImplementedError("Subclass must implement abstract method")
class SportsCar(Car):
def drive(self):
return 'Sportscar driving!'
def stop(self):
return 'Sportscar braking!'
class Truck(Car):
def drive(self):
return 'Truck driving slowly because heavily loaded.'
def stop(self):
return 'Truck braking!'
cars = [Truck('东风重卡'),
Truck('三一重工'),
SportsCar('Tesla Roadster')]
for car in cars:
print(car.name + ': ' + car.drive())

练习

编写一个名为Person的类,其中包含姓名、地址和电话号码的数据属性。

然后,编写一个名为Customer的类,它是Person的子类,Customer类有一个客户编号的数据属性和一个布尔属性(表明客户是否为活跃客户)。

以上部分保存在person.py中,以下程序创建了Customer类的实例并显示它。

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
import person

def main():
# Local variables
name = ''
address = ''
phone_number = ''
cust_number = 0
active_flag = False

# Get data attributes.
name = input('Enter the name: ')
address = input('Enter the address: ')
phone_number = input('Enter the phone_number: ')
cust_number = input('Enter the customer number: ')
active = input('Does the customer active?(Yes/No) ')

if active == 'Yes':
active_flag = True
else:
active_flag = False

# Create an instance of Customer.
customer = person.Customer(name, address, phone_number, \
cust_number, active_flag)

# Display information.
print ('Customer information: ')
print ('Name:', customer.get_name())
print ('Address:', customer.get_address())
print ('Phone number:', customer.get_phone_number())
print ('Customer Number:', customer.get_cust_number())
print ('Active:', customer.get_active())

# Call the main function.
main()
---------------------------------------------------------------------------

ModuleNotFoundError                       Traceback (most recent call last)

<ipython-input-8-f3eebd31f2c0> in <module>()
----> 1 import person
      2 
      3 def main():
      4     # Local variables
      5     name = ''


ModuleNotFoundError: No module named 'person'

练习

编写一个名为Employee类,其中包含员工姓名、员工编号信息。

然后,编写一个名为ProductionWorker的类,它是Employee的子类,包含以下信息:

  • 倒班代码(整数)
  • 小时工资

工作日分为两个班次:白班和夜班。班次属性保存代表员工工作班次的整数,白班的班次为1,夜班的班次为2

以上部分保存在emp.py中,以下程序创建了一个ProductionWorker类的实例,提示用户输入相关信息并存储在对象中,然后使用对象的访问器方法提取信息并显示在屏幕上。

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
import emp

def main():
# Local variables
worker_name= ''
worker_id = ''
worker_shift = 0
worker_pay = 0.0

# Get data attributes
worker_name = input('Enter the name: ')
worker_id = input('Enter the ID number: ')
worker_shift = int(input('Enter the shift number: '))
worker_pay = float(input('Enter the hourly pay rate: '))

# Create an instance of ProductionWorker
worker = emp.ProductionWorker(worker_name, worker_id, \
worker_shift, worker_pay)

# Display information
print ('Production worker information:')
print ('Name:', worker.get_name())
print ('ID number:', worker.get_id_number())
print ('Shift:', worker.get_shift_number())
print ('Hourly Pay Rate: $', \
format(worker.get_pay_rate(), ',.2f'), sep='')

# Call the main function.
main()
1
2


评论

:D 一言句子获取中...

加载中,最新评论有1分钟缓存...