序言
参考的文章资源是http://www.coolpython.net/python_primary/oop/polymorphism.html
面向对象编程——Object Oriented Programming,简称OOP。与之相对应的另一种程序设计思想是面向过程编程。
OOP是从自然而来的,自然中,我们了解类和实例,比如爬行类、哺乳类、昆虫类等等。在哺乳类下面还有个灵长类。而我们人类属于灵长类。每一个人便是人类的一个实例。
面对对象的最核心的三个特征是:封装+继承+多态
类的定义与实例化
类的定义
因为python也是一种面对对象编程语言。所以也有类的概念。提供了面对对象的所有标准特性。比如允许一个类继承多个基类。 子类可以覆盖父类的方法等等。
使用class来定义一个类
class Stu:
def __init__(self, name, age):
self.name = name
self.age = age
def run(self):
print("{name} is running".format(name=self.name))
理解:
(1)上面的Stu 是类的名字。你可以使用__name__ 来访问
print(Stu.__name__)
输出:
Stu
(2)在类里面,用def 关键词 来定义函数。但在类中 我们管这种函数为方法。称为实例方法。
(3)__init__是初始化函数。当实例被构造出来以后,使用__init__方法来初始化实例属性。self是实例, name 和 age都是实例的属性。 再看看为什么self是 实例。
s = Stu('小明', 18)
s.run()
输出:<class '__main__.Stu'>
小明 is running
这里我们看到s便是创建出来的实例。也就是之前所设置的self。
由于在初始化函数的时候,有name和age这两个属性。因为我们在创建实例的时候,必须输入这两个参数,在这里是’小明’, 18。
而s.run() 这行代码是在调用实例的run方法。
总结来说,我们上面创立了 实例的两个属性 name和age;也定义了实例的run方法。
所以说实例有相对应的方法和属性。
(4)对对象的属性进行修改
class Stu:
def __init__(self, name, age):
self.name = name
self.age = age
def info(self):
print("{name}今年{age}岁".format(name=self.name, age=self.age))
def run(self):
print("{name} is running".format(name=self.name))
def print(self):
print("ok")
s = Stu("小刚", 18)
s.info()
print(s.name)
s.age = 20
s.info()
输出:
小刚今年18岁
小刚
小刚今年20岁
(5)使用类来组合数据和方法
使用一个例子说明:
一个名为 成绩单 的文件中保存了学生的考试成绩,内容如下
姓名 语文 数学 英语
小红 90 95 90
小刚 91 94 93
编写程序读取该文件,使用合适的数据类型保存这些数据,输出每一个人的各个科目和分数,并计算每个学生的总的分数。
如果是面对对象的编程,则这样子写:
stus = []
with open('成绩单', 'r', encoding='utf-8') as file:
lines = file.readlines()
for i in range(1, len(lines)):
line = lines[i]
arrs = line.split()
stus.append(arrs)
for stu in stus:
msg = "{name}的各科成绩如下: 语文:{yw_score}, " \
"数学:{sx_score}, 英语:{en_score}".format(name=stu[0],
yw_score=stu[1],
sx_score=stu[2],
en_score = stu[3]
)
print(msg)
msg = "{name}的总成绩是{socre}".format(name=stu[0],
socre=int(stu[1])+int(stu[2])+int(stu[3]))
print(msg)
但如果用面对对象的编程,则写成:
class Stu:
def __init__(self, name, yw, sx, en):
self.name = name
self.yw = yw
self.sx = sx
self.en = en
def score_sum(self):
return self.yw + self.sx + self.en
def print_score(self):
msg = "{name}的各科成绩如下: 语文:{yw_score}, " \
"数学:{sx_score}, 英语:{en_score}".format(name=self.name,
yw_score = self.yw,
sx_score = self.sx,
en_score = self.en
)
print(msg)
msg = "{name}的总成绩是{score}".format(name=self.name, score=self.score_sum())
print(msg)
stus = []
with open('成绩单', 'r', encoding='utf-8') as file:
lines = file.readlines()
for i in range(1, len(lines)):
line = lines[i]
arrs = line.split()
s = Stu(arrs[0], int(arrs[1]), int(arrs[2]), int(arrs[3]))
stus.append(s)
for stu in stus:
stu.print_score()
从代码的可阅读性上看,使用类组织数据和方法显然更具有优势,而且,类这种概念,更加符合我们人类的思维。
使用类组织数据和方法,扩展性更好,可以随时添加新的属性和方法,比如,我想输出学生最高的科目分数,那么,我只需要修改类就可以了。
而面对过程的编程则需要大量的修改,可维护性很差。
类的封装
隐藏实现的细节,只对外公开我们想让他们使用的属性和方法,这就叫做封装。
封装的目的在于保护类内部数据结构的完整性, 因为使用类的用户无法直接看到类中的数据结构,只能使用类允许公开的数据,很好地避免了外部对内部数据的影响,提高了程序的可维护性。
(1)私有属性和方法
假设,你和你的同事一起做项目,现在,需要你写一个类让同事使用,这个类,你定义了5个属性,10个方法,但是呢,你的同事其实只会用到其中一个方法,剩下那9个,都是用到的那个方法在执行过程中调用的方法。
那么问题来了,你明确告诉他,你就调用那个A方法就好了,其他方法,你不要使用,不然,可能会影响结果。如果你的同事是个很安分守己的人,他听从了你的建议,老老实实的调用A方法,其他方法,他一概不动,这样就是安全的。
可是过了几天,来了新同事,他偏偏不听话,非得调用剩余的9个方法,它觉得,自己调用那9个方法,可以更好的实现功能,结果呢,出了大问题了,那9个方法,他用的不好,导致程序出错了。
我们写了一个类,有些属性,有些方法,我们不希望被其他人使用,因为那样很容易就产生错误,那么这时,我们就需隐藏实现的细节,只对外公开我们想让他们使用的属性和方法,这就叫做封装。就好比把一些东西用一个盒子封装起来,只留一个口,内部让你看不见
如何才能做到这一点呢?
在python里,如果属性和方法前面是双下划线,那么这个属性和方法就变成了私有的属性。下面是一个例子。
class Animal:
def __init__(self, name, age):
self.__name = name
self.__age = age
def __run(self):
print("run")
a = Animal('猫', 2)
a.__run()
print(a.__name, a.__age)
这样就会发生错误,你没法使用 run的这个方法和name、age这两个属性。
还有一个问题是 如果设置年龄是10000岁呢,这就显然不对。所以在类中也设置这样的方法,避免这种情况。
class Animal:
def __init__(self, name, age):
self.__name = name
self.__age = age
def set_age(self, age):
if age > 100 or age < 1:
raise Exception("年龄范围错误")
self.__age = age
def get_age(self):
return self.__age
def __run(self):
print("run")
a = Animal('猫', 2)
a.set_age(3)
print(a.get_age())
继承
继承是一种创建新类的方式,python中的继承,可以继承一个或者继承多个父类,新建的类被称之为派生类或者子类,被继承的类是父类。
(1)单继承
class Car(object):
def __init__(self, speed,brand):
self.speed = speed
self.brand = brand
def run(self):
print("{brand}在行驶".format(brand = self.brand))
# 燃油车
class Gasolinecar(Car):
def __init__(self,speed,brand,price):
super().__init__(speed,brand)
self.price = price
class Audi(Gasolinecar):
pass
honda = Gasolinecar(130,'本田', 13000)
honda.run()
audi_car = Audi(100,'奥迪',10000)
audi_car.run()
输出:
本田在行驶
奥迪在行驶
继承是指 子类即将拥有父类的方法和属性。同时新增子类的属性和方法。
Gasolinecar类里,我没有写run方法,但是Gasolinecar的父类定义了run方法,因此,Gasolinecar也有这个方法,因此这个类的对象honda可以使用run方法。
Audi类没有定义任何方法,但是它继承了Gasolinecar,因此,Gasolinecar有的属性和方法,它都拥有,这里就包括了__init__方法。
super()可以用来调用父类的方法,Gasolinecar多传了一个price属性,其父类的__init__方法里有两个参数,因此,可以先调用父类的__init__方法初始化speed, brand,然后在初始化price。
多态
(1)重写
多态这个概念依赖于继承,因为继承,使得子类拥有了父类的方法,这里就产生了一个问题,如果子类有一个方法和父类的方法同名,那么子类在调用这个方法时,究竟是调用子类自己的方法,还是父类的方法?
class Base():
def print(self):
print("base")
class A(Base):
def print(self):
print("A")
a = A()
a.print()
输出:
A
父类和子类都有print方法,那么子类A的对象a调用print方法时,调用的是谁的print方法呢?
答案是子类的print方法,如果A类没有定义print方法,那么a.print()调用的是父类的print方法,但是A类定义了print方法,这种情况称之为重写,A类重写了父类的print方法
(2)多态的表现形式
class Animal:
def run(self):
raise NotImplementedError
class People(Animal):
def run(self):
print("人在行走")
class Pig(Animal):
def run(self):
print("猪在跑")
p1 = People()
p1.run()
p2 = Pig()
p2.run()
输出:
人在行走
猪在跑
People和 Pig 都继承了Animal,都是动物,是同一类事物,他们都有run方法,但是最终的运行结果却不一样,这就是多态,同一类事物有多种形态