多态:
1、多态意思是"有多种形式"。多态意味着就算不知道变量所引用的对象类是什么,还是能对它进行操作,而它也会根据对象(或类)类型的不同而表现出不同的行为。2、多态就是多种表现形态的意思。它是一种机制、一种能力,而非某个关键字。它在类的继承中得以实现,在类的方法调用中得以体现。多态意味着变量并不知道引用的对象是什么,根据引用对象的不同表现不同的行为方式。
例1:
def add(x,y):
print(x + y)
add(1,2)
add("hello"," siri")
"""
上面代码的输出结果为:
3
hello siri
"""
我们不知道( + )加法运算符左右两个变量是什么类型,当我们给的是int类型时,它就进行加法运算。当我们给的是字符串类型时,它就返回的是两个字符串拼接的结果。也就是根据变量类型的不同,表现出不同的形态。
方法重写:
子类继承父类,会继承父类的所有方法,当父类方法无法满足需求,可在子类中定义一个同名方法覆盖父类的方法,这就叫方法重写。当子类的实例调用该方法时,优先调用子类自身定义的方法,因为它被重写了。(作用域:调用方法时现在对应的子类中找,有则调用子类中的方法,无则调用父类中的方法)
例2:
class People:
def speak(self):
print("people is speaking")
class Student(People):
# 方法重写。重写父类的speak方法
def speak(self):
print("student is speaking")
class Teacher(People):
pass
# Student类的实例s
s = Student() #子类中有speak方法,因此先调用子类中的方法
s.speak()
# Teacher类的实例t
t = Teacher() #子类中无speak方法,因此调用父类中的方法
t.speak()
"""
上面代码的输出结果为:
student is speaking
people is speaking
"""
当子类和父类都存在相同的方法时,子类的方法就会覆盖父类的方法。在代码运行时,会先调用子类的方法。这样,我们就获得了继承的另一个好处:多态(类的多态)
类的多态性:
1、多态性是指 具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。2、在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数, 不同的行为就是指不同的实现,即执行不同的函数。
例3:
class Person(object):
def __init__(self,name,sex):
self.name = name
self.sex = sex
def print_title(self):
if self.sex == "male":
print("man")
elif self.sex == "female":
print("woman")
class Child(Person): # Child 继承 Person
def print_title(self):
if self.sex == "male":
print("boy")
elif self.sex == "female":
print("girl")
May = Child("May","female")
Peter = Person("Peter","male")
print(May.name,May.sex,Peter.name,Peter.sex)
May.print_title()
Peter.print_title()
"""
上面代码的输出结果为:
May female Peter male
girl
man
子类重写父类的方法时较常遇见的多态
例4:
class Animal:
def __init__(self, name):
self.name = name
def talk(self):
raise NotImplementedError("Subclass must implement abstract method") #调用就会报错
class Cat(Animal):
def talk(self):
print('%s: 喵喵喵!' %self.name)
class Dog(Animal):
def talk(self):
print('%s: 汪!汪!汪!' %self.name)
a = Animal("Lichuang")
d = Dog("d1")
c = Cat("c1")
#定义一个接口,多种形态(定义eatting_double方法,参数为Animal类型)
def animal_talk(Animal):
Animal.talk()
animal_talk(c) #接收的参数必须是拥有talk方法的对象,否则执行报错
animal_talk(d)
"""
上面代码的输出结果为:
c1: 喵喵喵!
d1: 汪!汪!汪!
"""
当我们要新增一个Animal的子类,不必对animal_talk( )做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。
如:
class Tiger(Animal):
def talk(self):
print('%s: 嗷 嗷 嗷!' %self.name)
e = Tiger("e1")
def animal_talk(Animal):
Animal.talk()
animal_talk(e)
#上面代码的输出结果为:e1: 嗷 嗷 嗷!
多态的好处就是:当我们需要传入Dog、Cat、Tiger……时,我们只需要接收Animal类型就可以了,因为Dog、Cat、Tiger……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有talk( )方法,因此,传入的任意类型,只要是Animal类或其子类,就会自动调用实际类型的talk( )方法,这就是多态的意思:
对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用talk( )方法,而具体调用的talk( )方法是作用在Animal、Dog、Cat还是Tiger对象上,由运行时该对象的确切类型决定。
这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保talk( )方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:
1、 对扩展开放:允许新增Animal子类;2、对修改封闭:不需要修改依赖Animal类型的animal_talk( )等函数(不重写,直接继承父类方法函数 )
静态语言 vs 动态语言
1、对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用talk()方法。2、对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个talk( )方法就可以了
多态的好处:
1.增加了程序的灵活性:以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如Animal.talk( )2.增加了程序额可扩展性:通过继承Animal类创建了一个新的类,使用者无需更改自己的代码,还是用Animal.talk( )去调用
例5:
lass Animal:
def run(self):
print("Animal is running")
def jump(self):
print("Animal is jumping")
def swim(self):
print("i can swimming ")
class Dog(Animal):
def run(self):
print("Dog is running")
class Cat(Animal):
def jump(self):
print("Cat is running")
class Fish(Animal):
pass
def run_two_times(num):
num.run()
num.run()
def animal_swim(Animal):
Animal.swim()
dog = Dog()
fish = Fish()
run_two_times(Dog())
animal_swim(Fish())
"""
上面代码的输出结果为
Dog is running
Dog is running
i can swimming
"""
封装:
1、封装是全局作用域中其他区域隐藏多余信息的原则。听起来有点像多态,使用对象而不用知道其内部细节。他们都是抽象原则,都会帮忙处理程序组件而不用过多关心细节,就像函数一样
2、封装并不等同于多态。多态可以让用户不知道类(或对象类型)的对象下进行方法的调用,而封装可以不用关心对象是如何构建的,直接使用即可
例6:class Student:
def __init__(self,name,score):
self.name = name
self.score = score
student = Student("xiaohong","99") #对于有构造函数的类需要在实例化类时传入对应参数
def student_info(student): #定义的类也是一种数据类型,可以通过函数调用
print("学生:%s,分数:%s" % (student.name,student.score)) #在类外部调用实例属性:实例名.属性名
student_info(student)
#上面代码的输出结果为:学生:xiaohong,分数:99
例6_1:
class Student:
def __init__(self,name,score):
self.name = name
self.score = score
def student_info(self):
print("学生:%s,分数:%s" % (self.name, self.score)) #在类中调用实例属性:self.属性名
student = Student("xiaohong","99") #对于有构造函数的类需要在实例化类时传入对应参数
student.student_info()
在上面的例子中我们将student_info( )方法封装进了类中,从外部Student类,只需要知道在创建实例时需要给出name和score,如何输出是在Student类的内部定义的,这些数据和逻辑被"封装"了,调用很容易,但却不用知道内部是怎么实现的
为什么要封装:
1、封装数据的主要原因是: 保护隐私(作为男人的你,脸上就写着:我喜欢男人,你害怕么?)2、封装方法的主要原因是: 隔离复杂度(电视机,我们看见的就是一个黑匣子,其实里面有很多电器元件,对于用户来说,我们不需要清楚里面都有些元件,电视机把那些电器元件封装在黑匣子里,提供给用户的只是几个按钮接口,通过按钮就能实现对电视机的操作。)
提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),就是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。
封装分为两个层面:
封装其实分为两个层面,但无论哪种层面的封装,都要对外界提供好访问你内部隐藏内容的接口(接口可以理解为入口,有了这个入口,使用者无需且不能够直接访问到内部隐藏的细节,只能走接口,并且我们可以在接口的实现上附加更多的处理逻辑,从而严格控制使用者的访问)第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用 类名.或者 实例名.的方式去访问里面的名字,这本身就是一种封装
如:
>>> r1.nickname
'草丛伦'
>>> Riven.camp
'Noxus'
注:对于这一层面的封装(隐藏),
类名.和
实例名.就是访问隐藏属性的接口
第二个层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。
在python中用双下划线的方式实现隐藏属性(设置成私有的)。类中所有双下划线开头的名称如__x都会自动变形成: _类名__x的形式(类外访问私有变量):
例7:
class A:
__N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
def __init__(self):
self.__X=10 #类外访问私有属性时:实例名._类名__属性名
def __foo(self): #类外访问私有方法时:实例名._类名__方法名
print('from A')
print(self.__X) #类中访问私有属性时:self.属性名
def bar(self):
self.__foo() #类中访问私有方法时:self.方法名
a = A()
a._A__foo()
print(a._A__X)
print(a._A__N)
"""
上面代码的输出结果为:
from A
10
10
0
"""
这种自动变形的特点:
1、类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
2、这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
3、 在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
4、在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
注意:对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了
封装与扩展性:
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础或者说,只要接口这个基础约定不变,则代码改变不足为虑。例8:
#类的设计者
class room: #定义一个房间的类
def __init__(self,name,owner,length,width,high):
self.name = name
self.owner = owner
self.__length = length #房间的长
self.__width = width #房间的宽
self.__high = high #房间的高
def area(self): #求房间的平方的功能
return self.__length * self.__width #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是房间的面积就是:长x宽
#类的使用者
r1 = room("客厅","michael",20,30,9) #实例化一个对象r1
print(r1.area()) #通过接口使用(area方法),使用者得到了客厅的面积
#上面代码的输出结果为:600
例8_1:
class room: #定义一个房间的类
def __init__(self,name,owner,length,width,high):
self.name = name #房间名
self.owner = owner #房子的主人
self.__length = length #房间的长
self.__width = width #房间的宽
self.__high = high #房间的高
def area(self): #对外提供的接口,隐藏内部实现
return self.__length * self.__width,\
self.__length * self.__width * self.__high #此时我们增加了求体积,
# 内部逻辑变了,只需增加这行代码就能简单实现,而且外部调用感知不到,仍然使用该方法,但是功能已经增加了
#类的使用者
r1 = room("客厅","michael",20,30,9) #实例化一个对象r1
print(r1.area()) #通过接口使用(area),使用者得到了客厅的面积
#上面代码的输出结果为:600,5400
拓展:
当我们定义一个类时,实际上就 定义了一种数据类型。定义的数据类型和python自带的数据类型(如:string,list,dict)一样例9:
class People:
def speak(self):
print("people is speaking")
class Student(People):
def speak(self):
print("student is speaking")
class Teacher(People):
pass
p = People() #p是People类型
s = Student() #s是Student类型
t = Teacher() #s是Teacher类型
a = list() #a是list类型
#isinstance(a,b):判断一个变量是否是吗,某个类型,是返回True,否返回False
print("a是否是list类型:",isinstance(a,list))
print("p是否是People类型:",isinstance(p,People))
print("s是否是People类型:",isinstance(s,People))
print("s是否是Student类型:",isinstance(s,Student))
print("p是否是Student类型:",isinstance(p,Student))
"""
上面代码的输出结果为:
a是否是list类型: True
p是否是People类型: True
s是否是People类型: True
s是否是Student类型: True
p是否是Student类型: False
"""
从上面代码可以看出s既是Student类型也是People类型。这是因为Student类是从People类继承下来的,当我们创建了一个Student的实例s时,我们认为s的数据类型是Student,但s同时也是People也没错
所以,在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行(父类的数据类型不是子类的数据类型)