7.27学习 面向对象(下)(继承、多态)
面向对象编程思想的发展历程
面向对象(Object Oriented):是一种编程思想,是一种对现实世界的理解和抽闲的方法,已经从程序设计开发,扩展到了数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理、人工智能等等各个应用方向。
面向对象出现以前,结构化程序设计是程序设计的主流,结构化程序设计又称为面向过程的程序设计。在面向过程程序设计中,问题被看作一系列需要完成的任务,函数(在此泛指例程、函数、过程)用于完成这些任务,解决问题的焦点集中于函数。其中函数是面向过程的,即它关注如何根据规定的条件完成指定的任务。在多函数程序中,许多重要的数据被放置在全局数据区,这样它们可以被所有的函数访问。每个函数都可以具有它们自己的局部数据。
但这种结构很容易造成全局数据在无意中被其他函数改动,因而程序的正确性不易保证。面向对象程序设计的出发点之一就是弥补面向过程程序设计中的一些缺点:对象是程序的基本元素,它将数据和操作紧密地连结在一起,并保护数据不会被外界的函数意外地改变。
面向对象设计方法以对象为基础,利用特定的软件工具直接完成从对象客体的描述到软件结构之间的转换。这是面向对象设计方法最主要的特点和成就。面向对象设计方法的应用解决了传统结构化开发方法中客观世界描述工具与软件结构的不一致性问题,缩短了开发周期,解决了从分析和设计到软件模块结构之间多次转换映射的繁杂过程,是一种很有发展前途的系统开发方法。
最典型的动态语言有JavaScript, Python, Ruby等等。它们一个重大的变化就是将类的信息改变为动态的,并提出了Ducking Type的概念。这在很大程度上提升了编程的生产力。
封装的意义和操作过程
封装?将对象敏感的数据封装在类的内部,不让外界直接访问,而是通过当前类提供的set/get方法间接访问数据,此时就可以在set/get中添加限制条件进行访问数据的控制。
封装实现的步骤:
定义类型,封装零散数据【抽象的属性】
[使用__slots__属性,限制当前类的属性列表->为了代码的统一性]
所有属性私有化【属性使用双下划綫开头,外界就不能直接访问这样的属性了】
给每个属性提供set/get方法,在方法中添加限制条件
高级封装
使用@property和@get_method.setter注解,来注释set/get方法,隐藏set/get方法的实现,让方法的使用方式和属性一致
伪方法重载:python中,默认不提供方法重载,但是在高级封装的过程中,又出现了类似方法重载的代码语法,所以~称之为伪方法重载。
对方法的封装
在封装的过程中,除了封装私有属性,还可以针对一些底层的操作方法进行私有化,将方法封装在类的内部,通过提供的一个公共方法来控制该方法的访问权限
再说各种变量
-
全局变量:程序中任何位置都可以访问的数据;声明在类的外部
-
类变量/类属性:当前类创建的各种对象,可以共享的数据;声明在类的内部,方法的外部
-
成员变量/成员属性:【创建的指定对象的特征】被对象所私有,对象之间不能使用对方的数据
-
局部变量:声明在对象的方法或者函数中,函数中有效,出了函数无效【变量销毁】
继承的意义和操作过程
一个类型继承另一个类型,当前类型就会拥有另一个类型的公共的属性和方法,达到代码的重复使用的目的。
继承是让我们抽象的对象之间存在一定的所属关系
在继承关系中,我们一定要明确会出现这样的一种关系~父类、子类,子类继承自父类,可以继承父类中的公开的属性和方法(不能继承私有的属性或者方法)
python中所有的对象都是直接或者间接继承自object对象的:
class Person(object):
pass
继承的语法:
class 类型(被继承的类型):
pass
继承中出现的术语:
继承是类型之间的关系:
继承中,首先必须是两个或者两个以上的类型之间的关系,注意是类型之间的关系
-
父类:被继承的类,也称为基类、超类
-
子类:当前类,也称为派生类
-
子类继承父类,体现的时A is a B的关系
-
子类继承父类,就可以使用父类中所有的公开的属性和方法
继承链
A继承B,B继承C,C继承D
A直接继承了B,间接继承了C,D;此时A创建的对象,可以同时使用B,C,D中所有公开的属性和方法
多继承
python中,一个类型,可以同时继承多个类型,同时拥有所有继承类型的所有公开的属性和方法
备注:在进行代码设计的过程中,可能会出现多继承,所以尽量不要在父类中定义相同名称的属性或者方法
备注2:如果父类中出现了相同名称的属性和方法,在使用的时候子类调用时会优先继承优先调用。
在python的继承机制中,私有的属性是不允许被继承和互相访问的,子类不能继承和访问父类中私有的属性和方法,父类同样也不能访问子类中私有的属性和方法
子类只能继承父类中公开的属性和方法
子类中可以通过父类的名称或者super()来访问父类的属性和方法
# 父类
class Person(object):
def __init__(self, name, age):
self.__name = name
self.__age = age
def play(self):
print(self.__name + "在玩游戏")
# 子类
class Man(Person):
def __init__(self, name, age):
# 通过父类名称访问父类中初始化的方法
Person.__init__(self, name,age)
# 子类
class Women(Person):
def __init__(self, name, age):
# 通过super()访问父类初始化的方法
# super(Women, self).__init....super中的参数可以省略
super().__init__(self, name, age)
方法重写:
子类在自己类中重新定义父类中已经存在的方法,在执行该方法时,如果子类中没有重写就直接调用父类的方法,如果子类重写了该方法,就直接调用子类重写的方法!
# 父类
class Person(object):
def play(self):
print("Person中玩游戏的方法执行了...")
# 子类
class Children(Person):
# 重写父类中play方法
def play(self):
print("Children中玩游戏的方法执行.....")
# 创建子类对象
c = Children()
c.play()
# 执行结果
~Children中玩游戏的方法执行.....
多态的意义和操作过程
程序在运行的过程中,根据传递的参数的不同,执行不同的函数或者操作不同的代码,这种在运行过程中才确定调用的方式成为运行时多态
方法重写实现的运行时多态,对象在执行具体的方法时,会直接执行父类中继承的对应的方法,如果该方法在子类中重写了,就会执行子类中重写过的方法,实现的是一种运行过程中的多态处理
多态执行过程多态实现代码如下:
class Person:
def __init__(self, name, age, health):
self.name = name
self.age = age
self.health = health
# 治愈康复
def recure(self):
print("[%s]康复了,当前健康值%s\n" % (self.name, self.health))
# 子类
class Man(Person):
def __init__(self, name, age, health):
Person.__init__(self, name, age, health)
def recure(self):
print("[%s]男的我终于康复了,当前健康值%s\n" % (self.name, self.health))
class Woman(Person):
def __init__(self, name, age, health):
Person.__init__(self, name, age, health)
def recure(self):
print("[%s]女的我终于康复了,当前健康值%s\n" % (self.name, self.health))
class Animal:
# name姓名 age年龄 health健康值【0~50极度虚弱,51~70亚健康,71~85健康,86~100强壮】
def __init__(self, name, age, health):
self.name = name
self.age = age
self.health = health
# 康复的方法
def recure(self):
print("[%s]嘿嘿嘿,终于康复了,当前健康值%s\n" % (self.name, self.health))
# 医院
class Hospital:
def __init__(self):
self.__name = "人民医院"
# 医院治疗患者,因此对象是一个人person
# 若不是人,则不治疗并报出错误
def care(self, person):
if isinstance(person, Person):
if (person.health > 0) and (person.health <= 50):
print("手术....")
person.health += 30
person.recure()
elif (person.health > 50) and (person.health < 75):
print("吃药....")
person.health += 15
person.recure()
else:
print("没有生病")
else:
print("不是人")
hospital = Hospital()
Pi = Person("Pi", 27, 40)
Mr_wang = Man("王老菊", 29, 51)
Mian = Woman("免免", 28, 49)
pig = Animal("小猪", 10, 88)
hospital.care(Mr_wang)
hospital.care(Pi)
hospital.care(Mian)
hospital.care(pig)
运行结果
同时如果功能需要扩展,需要多出来一个人物类型:小孩,小孩也会生病,也需要治疗~此时对于功能的扩展非常简洁,值需要添加如下代码就可以搞定:
# 创建一个小孩类型,继承自Person
class Children(Person):
def __init__(self, name):
Person.__init__(self, name)
# 创建具体的小孩对象
c = Children("小家伙")
h.care(c) # 执行结果~小家伙康复了
可以看到这里扩展一个功能变得非常的简单,对象和对象之间的协同关系由于继承多态的存在让功能的扩展实现起来比较快捷了。
通过isinstance()
函数进行变量所属数据类型的判断
这样只有Person类型才可以通过进行care()
__slots__
,该属性的值是一个元组,元组中定义了类中可以出现的所有成员属性的名称
元组中规范了可能出现在类的成员属性列表。
类在创建好对象之后,可以在对象上直接挂在属性,这在一定程度上对于程序处理的灵活度有所提升,但是同样的,过于灵活的代码都会极大的降低代码的可读性,所以python提供了slots这样的类属性进行规范,规范类属性中只能出现的成员属性的列表,防止恶意的扩展。
__str__()
可以更改我们的类的对象的打印输出结果
这个__str__()
方法是从object对象继承而来的,我们这里只是对它进行了方法重写。
另外,在命令行操作过程中,如果不用print()方法打印而是直接输入对象,会发现执行的结果又是让人晦涩难懂的东西了,在命令行直接使用对象调用的不是对象的__str__()
方法,而是__repr__()
方法,只需要简单的修改即可
__call__()
方法,主要用于对象快捷执行而存在的一个魔术方法,方便进行对象中某些重要数据的初始化整理工作等。
多继承时,
当前类继承了一个或者多个父类,当前类就同时继承了父类中的公开的属性和函数,如果不同的父类中出现相同的属性/函数,就需要明确执行的过程
# 定义了一个基础类
class Person(object):
def __init__(self):
self.name = "tom"
class Student(Person):
def eat(self):
print("吃食堂....")
def respect(self):
print("尊师重道")
class Son(Person):
def eat(self):
print("吃美食...")
def fealty(self):
print("尊老爱幼")
# User类型,继承了儿子类型、学生类型
# 同时拥有儿子类型和学生类型中所有的公共属性和方法
class User(Son, Student):
pass
# 创建对象
u = User()
# u是学生角色
u.respect()
# u是儿子角色
u.fealty()
# 吃饭
# 如果继承的多个父类中,出现了相同的属性和方法,就会执行方法或者属性的搜索
# 过程,搜索到对应的属性和方法,立即执行,中断搜索
# 属性和方法的搜索过程,可以通过 类型.__mro__ 魔法属性进行查看
# 优先继承,优先执行
u.eat()
print(User.__mro__)# method from object
#运行结果
~
尊师重道
尊老爱幼
吃美食...
(<class '__main__.User'>, <class '__main__.Son'>, <class '__main__.Student'>, <class '__main__.Person'>, <class 'object'>)
一旦出现多重继承,就会出现这样继承的多个父类中出现了多个相同名称的变量或者方法的情况,使用的这些变量和方法的时候一定要注意一个原则,先继承谁就使用谁的变量或者方法!