练习40--模块、类和对象——面向对象的三大特性

面向对象的三大特性是指:封装、继承和多态。

一 封装

1 封装的基本概念

  • 不同定义——不管什么形式,封装的都是数据和方法,无论哪种层面的封装,都要对外界提供好访问内部隐藏内容的接口。
    • 代码封装,其实就是隐藏实现功能的具体代码,仅留给用户使用的接口,就好像使用计算机,用户只需要使用键盘、鼠标就可以实现一些功能,而根本不需要知道其内部是如何工作的
    • 类的封装,即在设计类时,刻意地将一些属性和方法隐藏在类的内部,这样在使用此类时,将无法直接以“类对象.属性名”(或者“类对象.方法名(参数)”)的形式调用这些属性(或方法),而只能用未隐藏的类方法间接操作这些隐藏的属性和方法。
    • 模块,可以理解为是对代码更高级的封装,即把能够实现某一特定功能的代码编写在同一个 .py 文件中,并将其作为一个独立的模块,这样既可以方便其它程序或脚本导入并使用,同时还能有效避免函数名和变量名发生冲突。
  • 一般意义上的封装:我们一般讲的封装都是指类的封装
  • 注意:封装绝不是将类中所有的方法都隐藏起来,一定要留一些像键盘、鼠标这样可供外界使用的类方法。
  • 原因:封装不是单纯意义的隐藏
    • 封装数据的主要原因是:保护隐私
    • 封装方法的主要原因是:隔离复杂度
  • 两个层面:
    • 第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装。
      • 注意:对于这一层面的封装(隐藏),类名.和实例名.就是访问隐藏属性的接口
    • 第二个层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。

2 类封装的好处

  • 可维护性:封装机制保证了类内部数据结构的完整性,因为使用类的用户无法直接看到类中的数据结构,只能使用类允许公开的数据,很好地避免了外部对内部数据的影响,提高了程序的可维护性。
  • 复杂度:对一个类实现良好的封装,用户只能借助暴露出来的类方法来访问数据,我们只需要在这些暴露的方法中加入适当的控制逻辑,即可轻松实现用户对类中属性或方法的不合理操作。
  • 代码可移植性:对类进行良好的封装,还可以提高代码的复用性。

3 具有封装特性的结构:

  • 诸多容器,例如列表、元组、字符串、字典等,它们都是对数据的封装;
  • 函数是对 Python 代码的封装;
  • 类是对方法和属性的封装,也可以说是对函数和数据的封装。
  • 模块是对代码更高级的封装,将代码封装成一个文件,方便其它程序或脚本导入使用。

4 python类如何进行封装

    • 原理:Python 类中的变量和函数,不是公有的(类似 public 属性),就是私有的(类似 private),因此可通过设置类中变量和函数的该属性来实现类的封装
    • 区别:
      • public:公有属性的类变量和类函数,在类的外部、类内部以及子类(后续讲继承特性时会做详细介绍)中,都可以正常访问;
      • private:私有属性的类变量和类函数,只能在本类内部使用,类的外部以及子类都无法使用。
    • 方法:
      • 名称前没有下划线:默认这些python类中的变量和方法都是公有(public)的,可以通过类对象正常访问;
      • 名称以双下划线“__”开头:表示这些python类中的变量和方法都是私有(private)的,只能在类内部使用,在类外部不能通过类对象进行访问;
        • 类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:
          • 类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
          • 这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
          • 在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
          • 注意:对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了。
        • 这种变形需要注意的问题是:
          • 这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
          • 变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形
      • 名称以单下划线“_”开头的类属性和类方法:通常将他们看作私有(private)的,但是它们可以通过类对象正常访问;
      • 名称前后分别以双下划线开头和结尾:这些类方法都是python内部定义的,用于python内部调用,我们自己定义类属性或者类方法时,禁止使用这种形式。

5  调用被封装的内容(第一个层面的封装)

  • 通过对象直接调用
    •  1 class Foo:
       2 
       3     def __init__(self, name, age):
       4         self.name = name
       5         self.age = age
       6 
       7 obj1 = Foo('wupeiqi', 18)
       8 print obj1.name    # 直接调用obj1对象的name属性
       9 print obj1.age     # 直接调用obj1对象的age属性
      10 
      11 obj2 = Foo('alex', 73)
      12 print obj2.name    # 直接调用obj2对象的name属性
      13 print obj2.age     # 直接调用obj2对象的age属性
      对象名直接调用
  • 通过self间接调用
    •  1 class Foo:
       2 
       3     def __init__(self, name, age):
       4         self.name = name
       5         self.age = age
       6 
       7     def detail(self):
       8         print self.name
       9         print self.age
      10 
      11 obj1 = Foo('wupeiqi', 18)
      12 obj1.detail()  # Python默认会将obj1传给self参数,即:obj1.detail(obj1),所以,此时方法内部的 self = obj1,即:self.name 是 wupeiqi ;self.age 是 18
      13 
      14 obj2 = Foo('alex', 73)
      15 obj2.detail()  # Python默认会将obj2传给self参数,即:obj1.detail(obj2),所以,此时方法内部的 self = obj2,即:self.name 是 alex ; self.age 是 78
      self参数间接调用

二 继承

1 继承的基本概念

  • 定义:继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类。
  • 分类:python中类的继承分为:单继承和多继承
  • 语法:
    1 class 类名(父类1, 父类2, ...):
    2     #类定义部分
  • 特点:
    • 如果该类没有显式指定继承自哪个类,则默认继承 object 类(object 类是 Python 中所有类的父类/基类,即要么是直接父类,要么是间接父类)。
    • Python 的继承是多继承机制(和 C++ 一样),即一个子类可以同时拥有多个直接父类。
  • 查看:
    • 类名.__base__:只查看从左到右继承的第一个子类
    • 类名.__bases__:查看所有继承的父类
  • 经典类与新式类:
    • 只有在python2中才分新式类和经典类,python3中统一都是新式类
    • 在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类
    • 在python2中,显式地声明继承object的类,以及该类的子类,都是新式类
    • 在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类

 

2 继承与抽象(先抽象再继承)

  • 抽象:
    • 即抽取类似或者说比较像的部分。
    • 抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
    • 抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
  • 继承:
    • 是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
    • 继承描述的是子类与父类之间的关系,是一种什么是什么的关系。要找出这种关系,必须先抽象再继承

3 继承与重用性

  • 代码重用:通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用
  • 开发过程中,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大.

4 派生

  • 定义:它和继承是一个意思,只是观察角度不同而已。换句话说,继承是相对子类来说的,即子类继承自父类;而派生是相对于父类来说的,即父类派生出子类。
  • python父类方法的重写:
    • 适用情况:对于父类中的一些方法,子类对象不能直接使用时,必须将这些方法在子类中重新定义一遍,即重写
    • 定义:重写,有时又称覆盖,是一个意思,指的是对类中已有方法的内部实现进行修改。
    • 举例:
       1 class Bird:
       2     #鸟有翅膀
       3     def isWing(self):
       4         print("鸟有翅膀")
       5     #鸟会飞
       6     def fly(self):
       7         print("鸟会飞")
       8 class Ostrich(Bird):
       9     # 重写Bird类的fly()方法
      10     def fly(self):
      11         print("鸵鸟不会飞")
      12 
      13 # 创建Ostrich对象
      14 ostrich = Ostrich()
      15 #调用 Ostrich 类中重写的 fly() 类方法
      16 ostrich.fly()
      方法重写
    • 调用重写之后的方法:子类对象名.重写后的方法,例如"ostrich.fly()"语句
    • 调用被重写方法:父类名.被重写的方法名(子类对象名)
      • 1 # 创建Ostrich对象
        2 ostrich = Ostrich()
        3 #调用 Bird 类中的 fly() 方法
        4 Bird.fly(ostrich)
        View Code
      • Python 中的类可以看做是一个独立空间,而类方法其实就是出于该空间中的一个函数。
      • 而如果想要全局空间中,调用类空间中的函数,只需要在调用该函数时备注类名即可。
      • 使用类名调用其类方法,Python 不会为该方法的第一个 self 参数自定绑定值,因此采用这种调用方法,需要手动为 self 参数赋值。

5 关于python的多继承

  • 大部分面向对象的编程语言,都只支持单继承,即子类有且只能有一个父类。而Python 却支持多继承(C++也支持多继承)
  • 和单继承相比,多继承容易让代码逻辑复杂、思路混乱,一般较少使用
  • 使用多继承经常需要面临的问题是,多个父类中包含同名的类方法
    • 对于这种情况,Python 的处置措施是:根据子类继承多个父类时这些父类的前后次序决定,即排在前面父类中的类方法会覆盖排在后面父类中的同名类方法。
    • 代码:
    •  1 class People:
       2     def __init__(self):
       3         self.name = People
       4     def say(self):
       5         print("People类",self.name)
       6 
       7 class Animal:
       8     def __init__(self):
       9         self.name = Animal
      10     def say(self):
      11         print("Animal类",self.name)
      12 #People中的 name 属性和 say() 会遮蔽 Animal 类中的
      13 class Person(People, Animal):
      14     pass
      15 
      16 zhangsan = Person()
      17 zhangsan.name = "张三"
      18 zhangsan.say()
      多个父类中包含同名类方法解决办法
    • 运行结果:
    • 1 People类 张三

6 继承的实现原理

  • 继承顺序问题(继承了多个类):
    • Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先广度优先
  • 经典类和新式类的区别:
    • 语法区分:是否继承了object类
    • 多继承查找方式区分:
      • 当类是经典类时,多继承情况下,会按照深度优先方式查找
        •  1 class D:
           2 
           3     def bar(self):
           4         print 'D.bar'
           5 
           6 
           7 class C(D):
           8 
           9     def bar(self):
          10         print 'C.bar'
          11 
          12 
          13 class B(D):
          14 
          15     def bar(self):
          16         print 'B.bar'
          17 
          18 
          19 class A(B, C):
          20 
          21     def bar(self):
          22         print 'A.bar'
          23 
          24 a = A()
          25 # 执行bar方法时
          26 # 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
          27 # 所以,查找顺序:A --> B --> D --> C
          28 # 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
          29 a.bar()
          30 
          31 经典类多继承
          经典类多继承
        • 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
      • 当类是新式类时,多继承情况下,会按照广度优先方式查找
        •  1 class D(object):
           2 
           3     def bar(self):
           4         print 'D.bar'
           5 
           6 
           7 class C(D):
           8 
           9     def bar(self):
          10         print 'C.bar'
          11 
          12 
          13 class B(D):
          14 
          15     def bar(self):
          16         print 'B.bar'
          17 
          18 
          19 class A(B, C):
          20 
          21     def bar(self):
          22         print 'A.bar'
          23 
          24 a = A()
          25 # 执行bar方法时
          26 # 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错
          27 # 所以,查找顺序:A --> B --> C --> D
          28 # 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
          29 a.bar()
          30 
          31 新式类多继承
          新式类多继承
        • 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错
  • 继承原理(python如何实现继承)
    • 原理:
      • python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表——F.mro() #等同于F.__mro__
      • 为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
    • MRO列表
      • 构造:通过一个C3线性化算法来实现
      • 实质:它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
        • 子类会先于父类被检查
        • 多个父类会根据它们在列表中的顺序被检查
        • 如果对下一个类存在两个合法的选择,选择第一个父类

7 子类中调用父类的方法

  • 问题:
    • 当一个子类继承多个类时,在创建子类的对象后,程序运行可能会出错;
    • 因为不同父类的构造方法之间会被覆盖,导致有些父类的构造方法没有得到正确的参数;
    • 为解决该问题,正确的做法是在子类中定义自己的构造方法(等同于重写第一个直接父类的构造方法),并且必须在该方法中调用第一个直接父类的构造方法
  • 调用第一个父类的构造方法:
    • 方法一:父类名.父类构造方法()
    • 方法二:super()
      • Python 2.x 语法:
        1 super(Class, obj).__init__(self,...)
      • python 3.x 语法:
        1 super().__init__(self,...)
  • 上述两种方式也可用于调用父类中的其它方法:
    • 方法一:父类名.父类方法()
      •  1 #_*_coding:utf-8_*_
         2 __author__ = 'Linhaifeng'
         3 
         4 class Vehicle: #定义交通工具类
         5      Country='China'
         6      def __init__(self,name,speed,load,power):
         7          self.name=name
         8          self.speed=speed
         9          self.load=load
        10          self.power=power
        11 
        12      def run(self):
        13          print('开动啦...')
        14 
        15 class Subway(Vehicle): #地铁
        16     def __init__(self,name,speed,load,power,line):
        17         Vehicle.__init__(self,name,speed,load,power)
        18         self.line=line
        19 
        20     def run(self):
        21         print('地铁%s号线欢迎您' %self.line)
        22         Vehicle.run(self)
        23 
        24 line13=Subway('中国地铁','180m/s','1000人/箱','',13)
        25 line13.run()
        非绑定方法
    • 方法二:super()
      •  1 class Vehicle: #定义交通工具类
         2      Country='China'
         3      def __init__(self,name,speed,load,power):
         4          self.name=name
         5          self.speed=speed
         6          self.load=load
         7          self.power=power
         8 
         9      def run(self):
        10          print('开动啦...')
        11 
        12 class Subway(Vehicle): #地铁
        13     def __init__(self,name,speed,load,power,line):
        14         #super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self)
        15         super().__init__(name,speed,load,power)
        16         self.line=line
        17 
        18     def run(self):
        19         print('地铁%s号线欢迎您' %self.line)
        20         super(Subway,self).run()
        21 
        22 class Mobike(Vehicle):#摩拜单车
        23     pass
        24 
        25 line13=Subway('中国地铁','180m/s','1000人/箱','',13)
        26 line13.run()
        super()函数

三 多态

1  多态

    • 定义:多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承)
      • 序列类型有多种形态:字符串,列表,元组
      • 动物有多种形态:人,狗,猪
    • 满足条件:
      • 继承:多态一定是发生在子类和父类之间;
      • 重写:子类重写了父类的方法

 

  • 代码举例:
    •  1 #多态:同一种事物的多种形态,动物分为人类,猪类(在定义角度)
       2 class Animal:
       3     def run(self):
       4         raise AttributeError('子类必须实现这个方法')
       5 
       6 
       7 class People(Animal):
       8     def run(self):
       9         print('人正在走')
      10 
      11 class Pig(Animal):
      12     def run(self):
      13         print('pig is walking')
      14 
      15 
      16 class Dog(Animal):
      17     def run(self):
      18         print('dog is running')
      19 
      20 peo1=People()
      21 pig1=Pig()
      22 d1=Dog()
      23 
      24 peo1.run()
      25 pig1.run()
      26 d1.run()
      多态

2 多态性

  • 定义:多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。
    • 在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。
    • 也就是说,每个对象可以用自己的方式去响应共同的消息。
    • 所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
  • 代码举例:
    •  1 #多态性:一种调用方式,不同的执行效果(多态性)
       2 # 多态性依赖于:继承
       3 ##多态性:定义统一的接口,
       4 def func(obj): #obj这个参数没有类型限制,可以传入不同类型的值
       5     obj.run() #调用的逻辑都一样,执行的结果却不一样
       6 
       7 func(peo1)
       8 func(pig1)
       9 func(d1)
      10 
      11 # peo1.run()
      12 # pig1.run()
      13 # d1.run()
      多态性
    • 综上可以说,多态性是一个接口(函数func),多种实现(如people.talk())
  • 多态性的好处:
    • 增加了程序的灵活性
      • 以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
    • 增加了程序的可扩展性
      •  1 >>> class Cat(Animal): #属于动物的另外一种形态:猫
         2 ...     def talk(self):
         3 ...         print('say miao')
         4 ...
         5 >>> def func(animal): #对于使用者来说,自己的代码根本无需改动
         6 ...     animal.talk()
         7 ...
         8 >>> cat1=Cat() #实例出一只猫
         9 >>> func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能
        10 say miao
        11 
        12 '''
        13 这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1)
        14 '''
        多态性的好处

        通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用

  • 总结:
    • 多态:同一种事物的多种形态,动物分为人类,猪类(在定义角度) 多态性:一种调用方式,不同的执行效果(多态性)

3 鸭子模型

  • 定义:Python 这种由多态衍生出的更灵活的编程机制,又称为“鸭子模型”或“鸭子类型”。
  • 代码举例:
     1 class WhoSay:
     2     def say(self,who):
     3         who.say()
     4 class CLanguage:
     5     def say(self):
     6         print("调用的是 Clanguage 类的say方法")
     7 
     8 class CPython(CLanguage):
     9     def say(self):
    10         print("调用的是 CPython 类的say方法")
    11 
    12 class CLinux(CLanguage):
    13     def say(self):
    14         print("调用的是 CLinux 类的say方法")
    15 a = WhoSay()
    16 #调用 CLanguage 类的 say() 方法
    17 a.say(CLanguage())
    18 #调用 CPython 类的 say() 方法
    19 a.say(CPython())
    20 #调用 CLinux 类的 say() 方法
    21 a.say(CLinux())
    鸭子类型

    此程序中,通过给 WhoSay 类中的 say() 函数添加一个 who 参数,其内部利用传入的 who 调用 say() 方法。这意味着,当调用 WhoSay 类中的 say() 方法时,我们传给 who 参数的是哪个类的实例对象,它就会调用那个类中的 say() 方法。

四 参考内容

教学网站:http://c.biancheng.net/view/2288.html

博客园:https://www.cnblogs.com/linhaifeng/articles/7340153.html

博客园:https://www.cnblogs.com/linupython/p/9388610.html

posted @ 2020-08-10 11:41  洛兰123  阅读(240)  评论(0编辑  收藏  举报