14 面向对象

14.1 面向对象编程介绍

  • 如今主流的软件开发思想有两种:一个是面向过程,另一个是面向对象。面向过程出现得较早,典型代表为C语言,开发中小型项目的效率很高,但是很难适用于如今主流的大中型项目开发场景。面向对象则出现得更晚一些,典型代表为Java或C++等语言,更加适合用于大型开发场景。两种开发思想各有长短
  • 对于面向过程的思想: 需要实现一个功能的时候,看重的是开发的步骤和过程,每一个步骤都需要自己亲力亲为,需要自己编写代码(自己来做)
  • 对于面向对象的思想:当需要实现一个功能的时候,看重的并不是过程和步骤,而是关心谁帮我做这件事(偷懒,找人帮我做)
  • 面向对象的三大特征有:封装性、继承性、多态性

14.2 类和对象

14.2.1 类和对象介绍

  • 面向对象编程的2个非常重要的概念:类和对象。对象是面向对象编程的核心,在使用对象的过程中,为了将具有共同特征和行为的一组对象抽象定义,提出了另外一个新的概念——类
  • 面向对象的语言当中,“类”就是用来模拟现实事物的。那么模拟现实世界的事物通常从两方面模拟:
    • 属性:事物的特征描述信息,用于描述某个特征“是什么”
    • 行为:事物的能力行动方案,用于说明事物“能做什么”
  • 类中也有属性、行为两个组成部分,而“对象”是类的具体实例。例如:
    • 类:抽象的,是一张“手机设计图”
    • 对象:具体的,是一个“真正的手机实例”
  • 对象:某一个具体事物的存在 ,在现实世界中可以是看得见摸得着的
  • 类和对象之间的关系:类就是创建对象的模板
  • 类的构成:
    • 类的名称:类名
    • 类的属性:一组数据
    • 类的方法:允许对进行操作的方法 (行为)

14.2.2 定义类

  • 定义一个类,格式如下:

      class 类名:
          方法列表
    
      # class Hero:  # 经典类(旧式类)定义形式
      # class Hero():
    
      class Hero(object):  # 新式类定义形式
          def info(self):
              print("英雄各有见,何必问出处。")
    
  • 说明:
    • 定义类时有2种形式:新式类和经典类,上面代码中的Hero为新式类,前两行注释部分则为经典类;
    • object 是Python 里所有类的最顶级父类;
    • 类名 的命名规则按照"大驼峰命名法";
    • info 是一个实例方法,第一个参数一般是self,表示实例对象本身,当然了可以将self换为其它的名字,其作用是一个变量 这个变量指向了实例对象

14.2.3 创建对象

  • python中,可以根据已经定义的类去创建出一个或多个对象。创建对象的格式为:

      对象名1 = 类名()
      对象名2 = 类名()
      对象名3 = 类名()
    
      class Hero(object):  # 新式类定义形式
          """info 是一个实例方法,类对象可以调用实例方法,实例方法的第一个参数一定是self"""
          def info(self):
              """当对象调用实例方法时,Python会自动将对象本身的引用做为参数,传递到实例方法的第一个参数self里"""
              print(self) 
              print("self各不同,对象是出处。")
    
      # Hero这个类 实例化了一个对象  taidamier(泰达米尔)
      taidamier = Hero()
    
      # 对象调用实例方法info(),执行info()里的代码
      # . 表示选择属性或者方法
      taidamier.info()
    
      print(taidamier)  # 打印对象,则默认打印对象在内存的地址,结果等同于info里的print(self)
      print(id(taidamier))  # id(taidamier) 则是内存地址的十进制形式表示
    
      # 1. 定义类
      class Washer():
          def wash(self):
              print('我会洗衣服')
              # <__main__.Washer object at 0x0000024BA2B34240>
              print(self)
    
      # 2. 创建对象
      haier1 = Washer()
      # <__main__.Washer object at 0x0000018B7B224240>
      print(haier1)
      # haier1对象调用实例方法
      haier1.wash()
    
      haier2 = Washer()
      # <__main__.Washer object at 0x0000022005857EF0>
      print(haier2)
    
    • 当创建一个对象时,就是用一个模子,来制造一个实物
    • self 指的是调用该函数的对象
    • 打印对象和self得到的结果是一致的,都是当前对象的内存中存储地址

14.3 对象属性操作

  • 属性即是特征,对象属性既可以在类外面添加和获取,也能在类里面添加和获取
  • 添加和获取对象的属性

      class Hero(object):
          """定义了一个英雄类,可以移动和攻击"""
          def move(self):
              """实例方法"""
              print("正在前往事发地点...")
    
          def attack(self):
              """实例方法"""
              print("发出了一招强力的普通攻击...")
    
      # 实例化了一个英雄对象 泰达米尔
      taidamier = Hero()
    
      # 给对象添加属性,以及对应的属性值
      taidamier.name = "泰达米尔"  # 姓名
      taidamier.hp = 2600  # 生命值
      taidamier.atk = 450  # 攻击力
      taidamier.armor = 200  # 护甲值
    
      # 通过.成员选择运算符,获取对象的属性值
      print("英雄 %s 的生命值 :%d" % (taidamier.name, taidamier.hp))
      print("英雄 %s 的攻击力 :%d" % (taidamier.name, taidamier.atk))
      print("英雄 %s 的护甲值 :%d" % (taidamier.name, taidamier.armor))
    
      # 通过.成员选择运算符,获取对象的实例方法
      taidamier.move()
      taidamier.attack()
    
  • 在方法内通过self获取对象属性

      class Hero(object):
          """定义了一个英雄类,可以移动和攻击"""
          def move(self):
              """实例方法"""
              print("正在前往事发地点...")
    
          def attack(self):
              """实例方法"""
              print("发出了一招强力的普通攻击...")
    
          def info(self):
              """在类的实例方法中,通过self获取该对象的属性"""
              print("英雄 %s 的生命值 :%d" % (self.name, self.hp))
              print("英雄 %s 的攻击力 :%d" % (self.name, self.atk))
              print("英雄 %s 的护甲值 :%d" % (self.name, self.armor))
    
      # 实例化了一个英雄对象 泰达米尔
      taidamier = Hero()
    
      # 给对象添加属性,以及对应的属性值
      taidamier.name = "泰达米尔"  # 姓名
      taidamier.hp = 2600  # 生命值
      taidamier.atk = 450  # 攻击力
      taidamier.armor = 200  # 护甲值
    
      # 通过.成员选择运算符,获取对象的实例方法
      taidamier.info()  # 只需要调用实例方法info(),即可获取英雄的属性
      taidamier.move()
      taidamier.attack()
    

14.4 魔法方法

14.4.1 __init__()

  • __init__()方法的作用:初始化对象
  • 无参数的__init__()方法

      class Hero(object):
          """定义了一个英雄类,可以移动和攻击"""
          # Python 的类里提供的,两个下划线开始,两个下划线结束的方法,就是魔法方法,__init__()就是一个魔法方法,通常用来做属性初始化 或 赋值 操作。
          # 如果类面没有写__init__方法,Python会自动创建,但是不执行任何操作,
          # 如果为了能够在完成自己想要的功能,可以自己定义__init__方法,
          # 所以一个类里无论自己是否编写__init__方法 一定有__init__方法。
    
          def __init__(self):
              """ 方法,用来做变量初始化 或 赋值 操作,在类实例化对象的时候,会被自动调用"""
              self.name = "泰达米尔" # 姓名
              self.hp = 2600 # 生命值
              self.atk = 450  # 攻击力
              self.armor = 200  # 护甲值
    
          def move(self):
              """实例方法"""
              print("正在前往事发地点...")
    
          def attack(self):
              """实例方法"""
              print("发出了一招强力的普通攻击...")
    
      # 实例化了一个英雄对象,并自动调用__init__()方法
      taidamier = Hero()
    
      # 通过.成员选择运算符,获取对象的实例方法
      taidamier.info() # 只需要调用实例方法info(),即可获取英雄的属性
      taidamier.move()
      taidamier.attack()
    
    • __init__()方法,在创建一个对象时默认被调用,不需要手动调用
    • __init__(self)中的self参数,不需要开发者传递,python解释器会自动把当前的对象引用传递过去
  • 有参数的__init__()方法

      class Hero(object):
          """定义了一个英雄类,可以移动和攻击"""
    
          def __init__(self, name, skill, hp, atk, armor):
              """ __init__() 方法,用来做变量初始化 或 赋值 操作"""
              # 英雄名
              self.name = name
              # 技能
              self.skill = skill
              # 生命值:
              self.hp = hp
              # 攻击力
              self.atk = atk
              # 护甲值
              self.armor = armor
    
          def move(self):
              """实例方法"""
              print("%s 正在前往事发地点..." % self.name)
    
          def attack(self):
              """实例方法"""
              print("发出了一招强力的%s..." % self.skill)
    
          def info(self):
              print("英雄 %s 的生命值 :%d" % (self.name, self.hp))
              print("英雄 %s 的攻击力 :%d" % (self.name, self.atk))
              print("英雄 %s 的护甲值 :%d" % (self.name, self.armor))
    
      # 实例化英雄对象时,参数会传递到对象的__init__()方法里
      taidamier = Hero("泰达米尔", "旋风斩", 2600, 450, 200)
      gailun = Hero("盖伦", "大宝剑", 4200, 260, 400)
    
      # print(gailun)
      # print(taidamier)
    
      # 不同对象的属性值的单独保存
      print(id(taidamier.name)) # 2642693247312
      print(id(gailun.name))    # 2642693248368
    
      # 同一个类的不同对象,实例方法共享
      print(id(taidamier.move())) # 140712585542880
      print(id(gailun.move()))    # 140712585542880
    
    • 通过一个类,可以创建多个对象,就好比 通过一个模具创建多个实体一样
    • __init__(self)中,默认有1个参数名字为self,如果在创建对象时传递了2个实参,那么__init__(self)中出了self作为第一个形参外还需要2个形参,例如__init__(self,x,y)
  • 注意:
    • 在类内部获取属性和实例方法,通过self获取;在类外部获取属性和实例方法,通过对象名获取
    • 如果一个类有多个对象,每个对象的属性是各自保存的,都有各自独立的地址;但是实例方法是所有对象共享的,只占用一份内存空间。类会通过self来判断是哪个对象调用了实例方法

14.4.2 __str__()

  • 当使用print输出对象的时候,默认打印对象的内存地址。如果类定义了__str__()方法,那么就会打印从在这个方法中 return 的数据
  • 这个方法是一个魔法方法 (Magic Method) ,用来显示信息
  • 该方法需要 return 一个数据,并且只有self一个参数,当在类的外部 print(对象) 则打印这个数据

      class Hero(object):
          """定义了一个英雄类,可以移动和攻击"""
    
          def __init__(self, name, skill, hp, atk, armor):
              """ __init__() 方法,用来做变量初始化 或 赋值 操作"""
              # 英雄名
              self.name = name  # 实例变量
              # 技能
              self.skill = skill
              # 生命值:
              self.hp = hp   # 实例变量
              # 攻击力
              self.atk = atk
              # 护甲值
              self.armor = armor
    
          def move(self):
              """实例方法"""
              print("%s 正在前往事发地点..." % self.name)
    
          def attack(self):
              """实例方法"""
              print("发出了一招强力的%s..." % self.skill)
    
          # def info(self):
          #     print("英雄 %s 的生命值 :%d" % (self.name, self.hp))
          #     print("英雄 %s 的攻击力 :%d" % (self.name, self.atk))
          #     print("英雄 %s 的护甲值 :%d" % (self.name, self.armor))
    
          def __str__(self):
              return "英雄 <%s> 数据: 生命值 %d, 攻击力 %d, 护甲值 %d" % (self.name, self.hp, self.atk, self.armor)
    
      taidamier = Hero("泰达米尔", "旋风斩", 2600, 450, 200)
      gailun = Hero("盖伦", "大宝剑", 4200, 260, 400)
    
      # 如果没有__str__ 则默认打印 对象在内存的地址。
      # 当类的实例化对象 拥有 __str__ 方法后,那么打印对象则打印 __str__ 的返回值。
      print(taidamier)
      print(gailun)
    
      # 查看类的文档说明,也就是类的注释
      print(Hero.__doc__)
    
    • 在python中方法名如果是__xxxx__()的,那么就有特殊的功能,因此叫做“魔法”方法
    • 当使用print输出对象的时候,默认打印对象的内存地址。如果类定义了__str__(self)方法,那么就会打印从在这个方法中 return 的数据
    • __str__()方法通常返回一个字符串,作为这个对象的描述信息

14.4.3 __del__()

  • 创建对象后,python解释器默认调用init()方法;当删除对象时,python解释器也会默认调用一个方法,这个方法为del()方法

      class Hero(object):
          # 初始化方法
          # 创建完对象后会自动被调用
          def __init__(self, name):
              print('__init__方法被调用')
              self.name = name
    
          # 当对象被删除时,会自动被调用
          def __del__(self):
              print("__del__方法被调用")
              print("%s 被 GM 干掉了..." % self.name)
    
      # 创建对象
      taidamier = Hero("泰达米尔")
    
      # 删除对象
      print("%d 被删除1次" % id(taidamier)) # 1588291319304 被删除1次
      del(taidamier)
    
      print("--" * 10)
    
      gailun = Hero("盖伦")
      gailun1 = gailun
      gailun2 = gailun
    
      print("%d 被删除1次" % id(gailun)) # 1588291319304 被删除1次
      del(gailun)
    
      print("%d 被删除1次" % id(gailun1)) # 1588291319304 被删除1次
      del(gailun1)
    
      print("%d 被删除1次" % id(gailun2)) # 1588291319304 被删除1次
      del(gailun2)
    
    • 当有变量保存了一个对象的引用时,此对象的引用计数就会加1
    • 当使用del() 删除变量指向的对象时,则会减少对象的引用计数。如果对象的引用计数不为1,那么会让这个对象的引用计数减1,当对象的引用计数为0的时候,则对象才会被真正删除(内存被回收)

14.5 继承

14.5.1 继承的概念

  • 在程序中,继承描述的是多个类之间的所属关系
  • 如果一个类A里面的属性和方法可以复用,则可以通过继承的方式,传递到类B里
  • 那么类A就是基类,也叫做父类;类B就是派生类,也叫做子类

      # 父类
      class A(object):
          def __init__(self):
              self.num = 10
    
          def print_num(self):
              print(self.num + 10)
    
      # 子类
      class B(A):
          pass
    
      b = B()
      print(b.num) 
      b.print_num()
    
    • pass:当你在编写一个程序时,执行语句部分思路还没有完成,这时你可以用 pass 语句来占位,也可以当做是一个标记,是要过后来完成的代码

      14.5.2 单继承

  • 单继承:子类只继承一个父类

      # 定义一个Master类
      class Master(object):
          def __init__(self):
              # 属性
              self.kongfu = "古法煎饼果子配方" 
    
          # 实例方法
          def make_cake(self):
              print("按照 <%s> 制作了一份煎饼果子..." % self.kongfu)
    
      # 定义Prentice类,继承了 Master,则Prentice是子类,Master是父类。
      class Prentice(Master): 
          # 子类可以继承父类所有的属性和方法,哪怕子类没有自己的属性和方法,也可以使用父类的属性和方法。
          pass                
    
      # laoli = Master()
      # print(laoli.kongfu)
      # laoli.make_cake()
    
      damao = Prentice()  # 创建子类实例对象
      print(damao.kongfu) # 子类对象可以直接使用父类的属性
      damao.make_cake()   # 子类对象可以直接使用父类的方法
    
    • 虽然子类没有定义__init__方法初始化属性,也没有定义实例方法,但是父类有。所以只要创建子类的对象,就默认执行了那个继承过来的__init__方法
    • 子类在继承的时候,在定义类时,小括号()中为父类的名字;父类的属性、方法,会被继承给子类

14.5.3 多继承

  • 多继承:子类继承多个父类

      class Master(object):
          def __init__(self):
              self.kongfu = "古法煎饼果子配方"  # 实例变量,属性
    
          def make_cake(self):                    # 实例方法,方法
              print("[古法] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)
    
          def dayandai(self):
              print("师傅的大烟袋..")
    
      class School(object):
          def __init__(self):
              self.kongfu = "现代煎饼果子配方"
    
          def make_cake(self):
              print("[现代] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)
    
          def xiaoyandai(self):
              print("学校的小烟袋..")
    
      # class Prentice(School, Master):  # 多继承,继承了多个父类(School在前)
      #     pass
    
      # damao = Prentice()
      # print(damao.kongfu)
      # damao.make_cake()
      # damao.dayandai()
      # damao.xiaoyandai()
    
      class Prentice(Master, School):  # 多继承,继承了多个父类(Master在前)
          pass
    
      damao = Prentice()
      print(damao.kongfu) # 执行Master的属性
      damao.make_cake() # 执行Master的实例方法
    
      # 子类的魔法属性__mro__决定了属性和方法的查找顺序
      print(Prentice.__mro__)
    
      damao.dayandai() # 不重名不受影响
      damao.xiaoyandai()
    
    • 多继承可以继承多个父类,也继承了所有父类的属性和方法
    • 注意:如果多个父类中有同名的 属性和方法,则默认使用第一个父类的属性和方法(根据类的魔法属性mro的顺序来查找)
    • 多个父类中,不重名的属性和方法,不会有任何影响

14.5.4 子类重写父类属性和方法

class Master(object):
    def __init__(self):
        self.kongfu = "古法煎饼果子配方" 

    def make_cake(self): 
        print("[古法] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

class School(object):
    def __init__(self):
        self.kongfu = "现代煎饼果子配方"

    def make_cake(self):
        print("[现代] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

class Prentice(School, Master):  # 多继承,继承了多个父类
    def __init__(self):
        self.kongfu = "猫氏煎饼果子配方"

    def make_cake(self):
        print("[猫氏] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

# 如果子类和父类的方法名和属性名相同,则默认使用子类的
# 叫 子类重写父类的同名方法和属性
damao = Prentice()
print(damao.kongfu) # 子类和父类有同名属性,则默认使用子类的
damao.make_cake() # 子类和父类有同名方法,则默认使用子类的

# 子类的魔法属性__mro__决定了属性和方法的查找顺序
print(Prentice.__mro__)

14.5.5 子类调用父类同名属性和方法

class Master(object):
    def __init__(self):
        self.kongfu = "古法煎饼果子配方"  # 实例变量,属性

    def make_cake(self):                    # 实例方法,方法
        print("[古法] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

class School(object):
    def __init__(self):
        self.kongfu = "现代煎饼果子配方"

    def make_cake(self):
        print("[现代] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

class Prentice(School, Master):  # 多继承,继承了多个父类
    def __init__(self):
        self.kongfu = "猫氏煎饼果子配方"

    def make_cake(self):
        print("执行子类的__init__方法前,self.kongfu属性:%s" % self.kongfu)
        self.__init__() # 执行本类的__init__方法,做属性初始化 self.kongfu = "猫氏...."
        print("执行子类的__init__方法前,self.kongfu属性:%s" % self.kongfu)
        print("[猫氏] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

    # 调用父类方法格式:父类类名.父类方法(self)
    def make_old_cake(self):
        # 不推荐这样访问父类的实例属性,相当于创建了一个新的父类对象
        # print("直接调用Master类的kongfu属性:%s" % Master().kongfu)

        # 可以通过执行Master类的__init__方法,来修改self的属性值
        print("执行Master类的__init__方法前,self.kongfu属性:%s" % self.kongfu)
        Master.__init__(self) # 调用了父类Master的__init__方法 self.kongfu = "古法...."
        print("执行Master类的__init__方法后,self.kongfu属性:%s" % self.kongfu)
        Master.make_cake(self) # 调用父类Master的实例方法

    def make_new_cake(self):
        # 不推荐这样访问类的实例属性,相当于创建了一个新的父类对象
        # print("直接调用School类的kongfu属性:%s" % School().kongfu)

        # 可以通过执行School类的__init__方法,来修改self的属性值
        print("执行School类的__init__方法前,self.kongfu属性:%s" % self.kongfu)
        School.__init__(self) # 调用了父类School的__init__方法 self.kongfu = "现代...."
        print("执行School类的__init__方法后,self.kongfu属性:%s" % self.kongfu)
        School.make_cake(self) # 调用父类School的实例方法

# 实例化对象,自动执行子类的__init__方法
damao = Prentice()

damao.make_cake() # 调用子类的方法(默认重写了父类的同名方法)

print("--" * 10)
damao.make_old_cake() # 进入实例方法去调用父类Master的方法

print("--" * 10)
damao.make_new_cake() # 进入实例方法去调用父类School的方法

print("--" * 10)
damao.make_cake() # 调用本类的实例方法
执行子类的__init__方法前,self.kongfu属性:猫氏煎饼果子配方
执行子类的__init__方法前,self.kongfu属性:猫氏煎饼果子配方
[猫氏] 按照 <猫氏煎饼果子配方> 制作了一份煎饼果子...
--------------------
执行Master类的__init__方法前,self.kongfu属性:猫氏煎饼果子配方
执行Master类的__init__方法后,self.kongfu属性:古法煎饼果子配方
[古法] 按照 <古法煎饼果子配方> 制作了一份煎饼果子...
--------------------
执行School类的__init__方法前,self.kongfu属性:古法煎饼果子配方
执行School类的__init__方法后,self.kongfu属性:现代煎饼果子配方
[现代] 按照 <现代煎饼果子配方> 制作了一份煎饼果子...
--------------------
执行子类的__init__方法前,self.kongfu属性:现代煎饼果子配方
执行子类的__init__方法前,self.kongfu属性:猫氏煎饼果子配方
[猫氏] 按照 <猫氏煎饼果子配方> 制作了一份煎饼果子...

14.5.6 多层继承

class Master(object):
    def __init__(self):
        self.kongfu = "古法煎饼果子配方"  

    def make_cake(self):
        print("[古法] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

class School(object):
    def __init__(self):
        self.kongfu = "现代煎饼果子配方"

    def make_cake(self):
        print("[现代] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

class Prentice(School, Master):  # 多继承,继承了多个父类
    def __init__(self):
        self.kongfu = "猫氏煎饼果子配方"
        self.money = 10000  # 亿美金

    def make_cake(self):
        self.__init__() # 执行本类的__init__方法,做属性初始化 self.kongfu = "猫氏...."
        print("[猫氏] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

    # 调用父类方法格式:父类类名.父类方法(self)
    def make_old_cake(self):
        Master.__init__(self) # 调用了父类Master的__init__方法 self.kongfu = "古法...."
        Master.make_cake(self) # 调用了父类Master的实例方法

    def make_new_cake(self):
        School.__init__(self) # 调用了父类School的__init__方法 self.kongfu = "现代...."
        School.make_cake(self) # 调用父类School的实例方法,

class PrenticePrentice(Prentice): # 多层继承
    pass

pp = PrenticePrentice()
pp.make_cake() # 调用父类的实例方法
pp.make_new_cake() 
pp.make_old_cake()

print(pp.money)

14.5.7 super() 方法

  • 子类继承了多个父类,如果父类类名修改了,那么子类也要涉及多次修改。而且需要重复写多次调用,显得代码臃肿。

  • 使用 super() 可以逐一调用所有的父类方法,并且只执行一次。调用顺序遵循 mro 类属性的顺序。

  • 注意:如果继承了多个父类,且父类都有同名方法,则默认只执行第一个父类的(同名方法只执行一次,目前super()不支持执行多个父类的同名方法)

  • super() 在Python2.3之后才有的机制,用于通常单继承的多层继承

class Master(object):
    def __init__(self):
        self.kongfu = "古法煎饼果子配方"  # 实例变量,属性

    def make_cake(self):  # 实例方法,方法
        print("[古法] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

# 父类是 Master类
class School(Master):
    def __init__(self):
        self.kongfu = "现代煎饼果子配方"

    def make_cake(self):
        print("[现代] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)
        super().__init__()  # 执行父类的构造方法
        super().make_cake()  # 执行父类的实例方法

# 父类是 School 和 Master
class Prentice(School, Master):  # 多继承,继承了多个父类
    def __init__(self):
        self.kongfu = "猫氏煎饼果子配方"

    def make_cake(self):
        self.__init__()  # 执行本类的__init__方法,做属性初始化 self.kongfu = "猫氏...."
        print("[猫氏] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

    def make_all_cake(self):
        # 方式1. 指定执行父类的方法(代码臃肿)
        # School.__init__(self)
        # School.make_cake(self)
        #
        # Master.__init__(self)
        # Master.make_cake(self)
        #
        # self.__init__()
        # self.make_cake()

        # 方法2. super() 带参数版本,只支持新式类
        # super(Prentice, self).__init__() # 执行父类的 __init__方法 
        # super(Prentice, self).make_cake()
        # self.make_cake()

        # 方法3. super()的简化版,只支持新式类
        super().__init__()  # 执行父类的 __init__方法 
        super().make_cake()  # 执行父类的 实例方法
        self.make_cake()  # 执行本类的实例方法

damao = Prentice()
damao.make_cake()
damao.make_all_cake()

print(Prentice.__mro__)
[猫氏] 按照 <猫氏煎饼果子配方> 制作了一份煎饼果子...
[现代] 按照 <现代煎饼果子配方> 制作了一份煎饼果子...
[古法] 按照 <古法煎饼果子配方> 制作了一份煎饼果子...
[猫氏] 按照 <猫氏煎饼果子配方> 制作了一份煎饼果子...
(<class '__main__.Prentice'>, <class '__main__.School'>, <class '__main__.Master'>, <class 'object'>)

14.6 封装

  • 封装的意义:
    • 将属性和方法放到一起做为一个整体,然后通过实例化对象来处理
    • 隐藏内部实现细节,只需要和对象及其属性和方法交互就可以了
    • 对类的属性和方法增加 访问权限控制

14.6.1 私有属性和方法

  • 私有权限:在属性名和方法名 前面加上两个下划线
    • 类的私有属性和私有方法,都不能通过对象直接访问,但是可以在本类内部访问
    • 类的私有属性和私有方法,都不会被子类继承,子类也无法访问
    • 私有属性和私有方法 往往用来处理类的内部事情,不通过对象处理,起到安全作用
class Master(object):
    def __init__(self):
        self.kongfu = "古法煎饼果子配方" 
    def make_cake(self):          
        print("[古法] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

class School(object):
    def __init__(self):
        self.kongfu = "现代煎饼果子配方"

    def make_cake(self):
        print("[现代] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

class Prentice(School, Master):
    def __init__(self):
        self.kongfu = "猫氏煎饼果子配方"
        # 私有属性,可以在类内部通过self调用,但不能通过对象访问
        self.__money = 10000  

    # 私有方法,可以在类内部通过self调用,但不能通过对象访问
    def __print_info(self):
        print(self.kongfu)
        print(self.__money)

    def make_cake(self):
        self.__init__()
        print("[猫氏] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)

    def make_old_cake(self):
        Master.__init__(self) 
        Master.make_cake(self)

    def make_new_cake(self):
        School.__init__(self) 
        School.make_cake(self)

class PrenticePrentice(Prentice):
    pass

damao = Prentice()
# 对象不能访问私有权限的属性和方法
# print(damao.__money)
# damao.__print_info()

pp = PrenticePrentice()

# 子类不能继承父类私有权限的属性和方法
print(pp.__money) # 会报错
pp.__print_info()
  • Python中没有像C++中 public 和 private 这些关键字来区别公有属性和私有属性
  • Python是以属性命名方式来区分,如果在属性和方法名前面加了2个下划线'__',则表明该属性和方法是私有权限,否则为公有权限

14.6.2 修改私有属性

  • 如果需要修改一个对象的属性值,通常有2种方法
    • 对象名.属性名 = 数据 → 直接修改
    • 对象名.方法名() → 间接修改
  • 私有属性不能直接访问,所以无法通过第一种方式修改,一般的通过第二种方式修改私有属性的值:定义一个可以调用的公有方法,在这个公有方法内访问修改

      class Master(object):
          def __init__(self):
              self.kongfu = "古法煎饼果子配方" 
          def make_cake(self):          
              print("[古法] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)
    
      class School(object):
          def __init__(self):
              self.kongfu = "现代煎饼果子配方"
    
          def make_cake(self):
              print("[现代] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)
    
      class Prentice(School, Master):
          def __init__(self):
              self.kongfu = "猫氏煎饼果子配方"
              # 私有属性,可以在类内部通过self调用,但不能通过对象访问
              self.__money = 10000  
    
          # 现代软件开发中,通常会定义get_xxx()方法和set_xxx()方法来获取和修改私有属性值。
    
          # 返回私有属性的值
          def get_money(self):
              return self.__money
    
          # 接收参数,修改私有属性的值
          def set_money(self, num):
              self.__money = num
    
          def make_cake(self):
              self.__init__()
              print("[猫氏] 按照 <%s> 制作了一份煎饼果子..." % self.kongfu)
    
          def make_old_cake(self):
              Master.__init__(self) 
              Master.make_cake(self)
    
          def make_new_cake(self):
              School.__init__(self) 
              School.make_cake(self)
    
      class PrenticePrentice(Prentice):
          pass
    
      damao = Prentice()
    
      # 对象不能访问私有权限的属性和方法
      # print(damao.__money)
      # damao.__print_info()
    
      # 可以通过访问公有方法set_money()来修改私有属性的值
      damao.set_money(100)
    
      # 可以通过访问公有方法get_money()来获取私有属性的值
      print(damao.get_money())
    

14.7 多态

  • 什么是多态?
    • 在需要使用父类对象的地方,也可以使用子类对象, 这种情况就叫多态
    • 比如, 在函数中,我需要调用 某一个父类对象的方法, 那么我们也可以在这个地方调用子类对象的方法
  • 如何在程序中使用多态?

      可以按照以下几个步骤来写代码:
          1.子类继承父类
          2.子类重写父类中的方法
          3.通过对象调用这个方法
    
      # 定义父类
      class Father:
          def cure(self):
              print("父亲给病人治病...")
    
      # 定义子类继承父类
      class Son(Father):
          # 重写父类中的方法
          def cure(self):
              print("儿子给病人治病...")
    
      # 定义函数,在里面 调用 医生的cure函数
      def call_cure(doctor):
          # 调用医生治病的方法
          doctor.cure()
    
      # 创建父类对象
      father = Father()
      # 调用函数,把父类对象传递函数
      call_cure(father)
    
      # 创建子类对象
      son = Son()
      # 调用函数,把子类对象传递函数
      call_cure(son)
    
  • 使用多态的好处:给 call_cure(doctor) 函数传递哪个对象,在它里面就会调用哪个对象的 cure() 方法,也就是说在它里面既可以调用 son 对象的 cure()方法 ,也能调用 father 对象的 cure() 方法,当然了也可以在它里面调用Father 类其它子类对象的 cure() 方法,这样可以让 call_cure(doctor) 函数变得更加灵活,额外增加了它的功能,提高了它的扩展性

14.8 类属性及相关方法

  • 类属性就是类对象所拥有的属性,它被所有类对象的实例对象所共有,在内存中只存在一个副本,这个和C++中类的静态成员变量有点类似。对于公有的类属性,在类外可以通过类对象和实例对象访问

14.8.1 类属性和实例属性

  • 类属性

      class People(object):
          name = 'Tom'  # 公有的类属性
          __age = 12  # 私有的类属性
    
      p = People()
    
      print(p.name)  # 正确
      print(People.name)  # 正确
      print(p.__age)  # 错误,不能在类外通过实例对象访问私有的类属性
      print(People.__age) # 错误,不能在类外通过类对象访问私有的类属性
    
  • 实例属性(对象属性)

      class People(object):
          address = '山东'  # 类属性
          def __init__(self):
              self.name = 'xiaowang'  # 实例属性
              self.age = 20  # 实例属性
    
      p = People()
      p.age = 12  # 实例属性
      print(p.address)  # 正确
      print(p.name)  # 正确
      print(p.age)  # 正确
    
      print(People.address)  # 正确
      print(People.name)  # 错误
      print(People.age)  # 错误
    
  • 修改类属性:如果需要在类外修改类属性,必须通过类对象去引用然后进行修改。如果通过实例对象去引用,会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性,并且之后如果通过实例对象去引用该名称的属性,实例属性会强制屏蔽掉类属性,即引用的是实例属性,除非删除了该实例属性

      class People(object):
          country = 'china' #类属性
    
      print(People.country)
      p = People()
      print(p.country)
      p.country = 'japan' 
      print(p.country)  # 实例属性会屏蔽掉同名的类属性
      print(People.country)
      del p.country  # 删除实例属性
      print(p.country)
    
      china
      china
      japan
      china
      china
    

14.8.2 类方法和静态方法

  • 类方法:是类对象所拥有的方法,需要用修饰器@classmethod来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数(当然可以用其他名称的变量作为其第一个参数,但是大部分人都习惯以'cls'作为第一个参数的名字,就最好用'cls'了),能够通过实例对象和类对象去访问

      class People(object):
          country = 'china'
    
          #类方法,用classmethod来进行修饰
          @classmethod
          def get_country(cls):
              return cls.country
    
      p = People()
      print(p.get_country())    #可以用过实例对象引用
      print(People.get_country())    #可以通过类对象引用
    
    • 类方法还有一个用途就是可以对类属性进行修改:

      class People(object):
          country = 'china'
      
          #类方法,用classmethod来进行修饰
          @classmethod
          def get_country(cls):
              return cls.country
      
          @classmethod
          def set_country(cls,country):
              cls.country = country
      
      p = People()
      print(p.get_country())   #可以用过实例对象访问
      print(People.get_country())    #可以通过类访问
      
      p.set_country('japan')   
      
      print(p.get_country())
      print(People.get_country())
      
      china
      china
      japan
      japan
      
    • 结果显示在用类方法对类属性修改之后,通过类对象和实例对象访问都发生了改变
  • 静态方法:要通过修饰器@staticmethod来进行修饰,静态方法不需要多定义参数,可以通过对象和类来访问

      class People(object):
          country = 'china'
    
          @staticmethod
          #静态方法
          def get_country():
              return People.country
    
      p = People()
      # 通过对象访问静态方法
      p.get_contry()
    
      # 通过类访问静态方法
      print(People.get_country())
    
  • 总结:
    • 从类方法和实例方法以及静态方法的定义形式就可以看出来,类方法的第一个参数是类对象cls,那么通过cls引用的必定是类对象的属性和方法
    • 实例方法的第一个参数是实例对象self,那么通过self引用的可能是类属性、也有可能是实例属性(这个需要具体分析),不过在存在相同名称的类属性和实例属性的情况下,实例属性优先级更高
    • 静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类对象来引用

results matching ""

    No results matching ""