参考的文章资源是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方法,但是最终的运行结果却不一样,这就是多态,同一类事物有多种形态