python:多态、封装

多态:

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也没错

所以,在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行(父类的数据类型不是子类的数据类型)

猜你喜欢

转载自blog.csdn.net/qq_39314932/article/details/80877837