26.Python之面向对象的三大特性(继承、封装、多态)

Python之面向对象的三大特性(继承、封装、多态)


  1. 继承与派生

    • 继承

      • 继承是一种新建类的方式,在Python中支持一个子类继承多个父类。继承是类与类之间的从属关系,寻找这种关系需要先抽象,再继承。新建的类称为子类或派生类,父类又称为基类或超类。子类会继承父类的属性,可以使用父类的静态变量和方法。继承其实增加了类的耦合性。
    • 使用继承的意义

      • 减少代码冗余。
    • 继承的使用方法

      • 在Python 2中类分为两种,一种叫经典类(没有继承了object类,以及该类的子类,查找顺序遵从深度优先),一种叫新式类(继承了object类,以及该类的子类,查找顺序遵从广度优先,新式类中使用类名.mro()可以查看C3算法)。但在Python 3中全都为新式类。类的继承分为两种,一种是单继承,单继承可以继承多次;一种是多继承,Python中支持多继承,但并不是所有语言都支持,使用多继承可以同时继承多个父类,查找顺序为先继承的先找。

        # For Example:
        class ParentClass1:
            pass
        
        class ParentClass2:
            pass
        
        class SubClass1(ParentClass1):    # 单继承
            pass
        
        class SubClass2(ParentClass1,ParentClass2):    #多继承,先从写在前面的父类中找
        pass
        
        print(SubClass1.__bases__)    # 查看SubClass的父类
        print(SubClass2.__bases__)    # 查看SubClass的父类
        
        # For Example:
        class Father(object):   # 继承object这种写法可以兼容Python 2
            def __init__(self):
                self.func()
        
            def func(self):
                print('The func from Father')
        
        class Son(Father):  # 子类Son会继承父类Father的所有属性和方法
            def func(self): 
                print('The func from Son')
        
        son = Son() # The func from Son
        # 注意:代码自上而下执行,定义Father类的时候,会开辟一个名称空间,将__init__和func放入Father类的名称空间;定义Son类的时候,也会开辟一个名称空间,程序读到class Son(Father)时会产生一个类指针,指向Father类的名称空间,同时将类指针和func存入Son的名称空间中;当程序读到son = Son()进行实例化产生对象的时候,同样也会为对象son开辟一个名称空间,产生一个类指针,指向Son类,并且实例化会自动触发__init__,首先会在对象son的名称空间找__init__,找不到,再去Son类中找,也没有,再通过类指针指向的Father中查找,找到了__init__,把son作为self传入到__init__中,此时,father中__init__的self实际上指向的时son的名称空间,当执行self.func()时,首先会在son的名称空间查找func(),没有,再去Son类中查找,发现有一个func()方法,执行,因此,son = Son()得到的结果是:The func from Son。
        
        print(Son.mro())    # 查看广度优先的查找顺序,只在新式类中有
        
    • 派生

      • 如果子类的类体中只有一个pass,意味着全部继承父类。如果子类中定义了自己新的属性,称之为派生。如果子类中的属性或方法与父类重名,子类永远优先调用自己的属性或方法。如果需要在子类的方法中调用父类的方法,需要通过 父类名.方法名(self) 的方式。

        class Chinese(object):
            country = 'China' # 类的属性
            def __init__(self, name, birthday, sex):
                self.name = name
                self.birthday = birthday
                self.sex = sex
        
            def attribute(self): # 类的方法
                print('这是父类的方法')
        
        class Northerners(Chinese):
            def northerners_diet(self):
                print(F'{self.name} is northerners,Eat pasta')
        
            def attribute(self):    # 子类中定义自己的attribute方法,称之为派生
                Chinese.attribute(self) # 在子类的方法中调用父类的方法
                print('这是子类的方法')
        
        class Southerners(Chinese):
            pass    # Southerners类全部继承Chinese类
        
        n1 = Northerners('Northerner', '2000-01-01', 'male')
        n1.attribute()    # 这是子类的方法
        
      • 在子类派生出的新方法中重用父类功能方法

        方式一:指名道姓的调用(其实与继承没有什么关系)

        # For Example:
        class User(object):
            def __init__(self, name, password, mobile):
                self.name = name
                self.password = password
                self.mobile = mobile
        
        class VIPUser(object):
            def __init__(self, name, password, mobile, effective_date, expiring_date):
                # self.name = name
                # self.password = password
                # self.mobile
                User.__init__(self, name, password, mobile)    # 使用类名调用
                self.effective_date = effective_date
                self.expiring_date = expiring_date
        
        user = VIPUser('老王', '123', 'xxx', '2020-01-01', '2021-01-01')
        print(user.__dict__)
        

        方式二: super()调用(严格依赖于继承)

        super()的返回值是一个特殊的对象,该对象专门用来调用父类中的属性。

        class User(object):
            def __init__(self, name, password, mobile):
                self.name = name
                self.password = password
                self.mobile = mobile
        
        class VIPUser(User):    #  使用super()必须继承父类
            def __init__(self, name, password, mobile, effective_date, expiring_date):
                # self.name = name
                # self.password = password
                # self.mobile
                super().__init__(name, password, mobile)    # super()专门调取父类的方法,super(VIPUser, self)
                self.effective_date = effective_date
                self.expiring_date = expiring_date
        
        user = VIPUser('老王', '123', 'xxx', '2020-01-01', '2021-01-01')
        print(user.__dict__)
        
        • 以上两种方式用哪种都可以,但不要混合使用

    • 抽象类

      • 抽象类是一个开发规范,或者说是用来规范开发代码的,通过抽象类可以约束它所有的子类实现相同的方法。一般用于多人协同开发时进行开发代码规范。

      • 抽象类的实现方式一:

        # 定义支付抽象类
        class Payment(object):
            '''
            Payment为抽象类,用于规范子类的pay方法。
            '''
            def __init__(self, name):
                self.name = name
        
            def pay(self, amount):  # 严格限定子类方法的命名,如果不一样就主动抛出异常
                raise NotImplementedError('请在子类中规范pay方法的命名')
        
        # 定义一个通过支付宝支付的类
        class AliPay(Payment):  #
            def pay(self, amount):  # 由于父类抽象类的限制,此方法必须命名为:pay
                print(F'{self.name}通过支付宝成功支付:{amount}元')
        
        # 定义一个通过微信支付的类
        class WeChatPay(Payment):
            def pay(self, amount):  # 由于父类抽象类的限制,此方法必须命名为:pay
                print(F'{self.name}通过微信成功支付:{amount}元')
        
        # 定义一个通过苹果支付的类
        class ApplePay(Payment):
            def pay(self, amount):  # 由于父类抽象类的限制,此方法必须命名为:pay
                print(F'{self.name}通过苹果成功支付:{amount}元')
        
        # 归一化设计:让调用者不用实例化对象
        def pay(name, payment_method, amount):
            if payment_method == 'AliPay':
                obj = AliPay(name)
                obj.pay(amount)
        
            elif payment_method == 'WeChatPay':
                obj = WeChatPay(name)
                obj.pay(amount)
        
            elif payment_method == 'ApplePay':
                obj = ApplePay(name)
                obj.pay(amount)
        
        # 其他程序员调用归一化设计的支付接口
        pay('马云', 'AliPay', 10000)  # 马云通过支付宝成功支付:10000元
        pay('马化腾', 'WeChatPay', 10000)  # 马化腾通过微信成功支付:10000元
        pay('乔布斯', 'ApplePay', 10000)   # 乔布斯通过苹果成功支付:10000元
        
        # 原理:以ApplePay为例,当ApplePay实例化产生对象obj后,obj调用ApplePay类中的pay方法,如果ApplePay中的pay方法不是以‘pay’命名,则在ApplePay类中找不到pay方法,通过类指针去父类Payment中进行查找,找到了pay方法,但pay方法中的功能为主动抛出异常,借此来实现开发代码的规范。
        
      • 抽象类的实现方式二:

        # 导入相关模块
        from abc import ABCMeta, abstractclassmethod
        # 定义支付抽象类
        class Payment(metaclass=ABCMeta):
            '''
            Payment为抽象类,用于规范子类的pay方法。
            '''
            def __init__(self, name):
                self.name = name
            @abstractclassmethod    # 为pay加装饰器
            def pay(self):
                pass
        
        # 定义一个通过支付宝支付的类
        class AliPay(Payment):  
            def pay(self, amount):  # 由于父类抽象类的限制,此方法必须命名为:pay
                print(F'{self.name}通过支付宝成功支付:{amount}元')
        
        # 定义一个通过微信支付的类
        class WeChatPay(Payment):
            def pay(self, amount):  # 由于父类抽象类的限制,此方法必须命名为:pay
                print(F'{self.name}通过微信成功支付:{amount}元')
        
        # 定义一个通过苹果支付的类
        class ApplePay(Payment):
            def pay(self, amount):  # 由于父类抽象类的限制,此方法必须命名为:pay
                print(F'{self.name}通过苹果成功支付:{amount}元')
        
        # 归一化设计:让调用者不用实例化对象
        def pay(name, payment_method, amount):
            if payment_method == 'AliPay':
                obj = AliPay(name)
                obj.pay(amount)
        
            elif payment_method == 'WeChatPay':
                obj = WeChatPay(name)
                obj.pay(amount)
        
            elif payment_method == 'ApplePay':
                obj = ApplePay(name)
                obj.pay(amount)
        
        # 其他程序员调用归一化设计的支付接口
        pay('马云', 'AliPay', 10000)  # 马云通过支付宝成功支付:10000元
        pay('马化腾', 'WeChatPay', 10000)  # 马化腾通过微信成功支付:10000元
        pay('乔布斯', 'ApplePay', 10000)   # 乔布斯通过苹果成功支付:10000元
        

  1. 封装

    • 封装

      • 装就是把属性存起来,封就是把这些存起来的属性隐藏,封装的终极奥义:明确的区分内外,对外是隐藏的,对内是开放的;隐藏对象的属性和实现细节,让类外部的使用者无法直接使用,仅对外提供公共访问方式。
      • 广义的封装:把属性和方法装起来,在外部不能直接调用。装在类中的属性和方法都是广义上的封装。
      • 狭义的封装:把类的属性和方法藏起来,在类的外部不能调用,只能在内部使用。
    • 使用封装的意义

      • 封装数据属性的目的:把数据属性封装起来,然后需要开辟接口给类外部的使用者使用,好处是我们可以在接口处添加逻辑控制,从而严格控制访问者对属性的操作。
      • 使用封装的三个场景:
        • 不想让别人看,也不想让别人改。
        • 可以让别人看,但不想让别人改。
        • 可以看也可以改,但必须按照指定的规则改。
    • 封装的使用方法

      import hashlib
      
      class User(object):
          def __init__(self, username, password):
              self.username = username
              self.__password = password  # 在实例变量前面加上 __ ,就可以把实例变量隐藏起来,实例变量__password名变为:_User_password
      
          def __generate_hash(self):  # 在方法前面加上 __ ,也可以把方法隐藏起来,在类外部无法调取
              '''
              这是一个将用户密码加密生成MD5值的私有方法
              :return: 用户密码加密后的MD5值
              '''
              md5 = hashlib.md5(self.username.encode('utf-8'))    # 使用username加盐
              md5.update(self.__password.encode('utf-8'))
              return md5.hexdigest()
      
          def get_password(self):
              return self.__generate_hash()
      
      user = User('Python', '123')
      # user.__password = '456'    # 无法更改,会报错
      # user. __generate_hash()    # 无法调取,会报错
      res = user.get_password()
      print(res)    # 输出结果:ae35eacb1cb6f6d38c29a04ecb2d7471
      
      # 隐藏属性:其实这种隐藏只是语法上的一种变形,这种语法上的变形,只在类定义阶段发生一次,类定义之后,新增的`__`开头的属性都没有变形效果。
      # 如果父类不想让子类覆盖自己的方法,可以在方法名前加`__`开头。
      # 在类的外部不能定义私有方法。
      # 私有方法子类不能继承使用。
      
      
    • 私有方法的调用

      class Father(object):
          def __init__(self):
              self.__func()   # self.__func 实际上是: self._Father__func()
      
          def __func(self):   # __func 实际上是: _Father__func
              print('The func from Father')
      
      class Son(Father):
          def __func(self):   # __func 实际上是: _Son__func
              print('The func from Son')
      
      son = Son()    # The func from Father
      
    • 数据类型的级别

      • 在其他编程语言中数据类型有三种级别:
        • Public(公有的):类内类外都能用,父类子类都能用。
        • Protect(保护的):类内能用,父类子类都能用,类外不能用。
        • Private(私有的):只有自己的类中能用,其他地方都不能用。
      • 在Python中只支持:Public(公有的)和 Private(私有的)。
    • 封装之property

      property 是一装饰器,访问它时会执行一段功能(函数)然后返回值,用来将类内的方法伪装成一个数据属性。

      # For Example:
      class People(object):
          def __init__(self, name, weight, height):
              self.name = name
              self.weight = weight
              self.height = height
      
      @property  # 装饰器,将类中的方法伪装成一个数据属性
      def bmi(self):  # 被装饰的方法一定不能有参数
          return self.weight / (self.height ** 2)
      
      p1 = People('Python', 80, 1.68)
      p1.name # 调用类的属性
      p1.bmi  # bmi被@property伪装成了一个数据属性,因此调用时不用加()
      # p1.bmi对应的是一个函数,所以不能被赋值
      
      # For Example:
      class User(object):
          def __init__(self, username, password):
              self.username = username
              self.__password = password  # 私有实例变量
      
          @property   # 将password方法伪装成一个属性,达到只能看,不能改的效果
          def password(self):
              '''
              定义一个password方法,只能看,不能改
              :return:
              '''
              return self.__password
      
      user = User('Python', '123')
      pwd = user.password # 调用者以为password是个属性,但其实只能看,不能改
      print(pwd)  # 123
      
    • 封装之property的进阶用法

      class Goods(object):
          '''
           这是一个商品类:
           有商品名称,商品价格以及折扣
           可以进行商品价格的调整,如打折和涨价
          '''
          discount = 0.8  # 折扣
          def __init__(self, name, original_price):
              self.name = name
              self.__orig_price = float(original_price)
      
          @property   # 将price伪装成属性
          def price(self):
              '''
              这是一个实现商品价格折扣的方法
              :return: 商品折扣的价格
              '''
              return self.__orig_price * self.discount
      
          @price.setter   # @后面的名字必须与上面的方法名称price一样,被装饰的方法可以传入一个值
          def price(self, new_value):    # 定义的新方法名字必须与上面的方法名price一样
              '''
              这是一个实现商品改变原价的方法
              :param new_value:
              :return: 原价变动后的价格
              '''
              self.__orig_price = float(new_value)
      
          @price.deleter    # @后面的名字必须与上面的方法名称price一样
          def price(self):
              del self.__orig_price
      
      
      iphone12 = Goods('iPhone12', 12000)
      print(iphone12.price)   # 调用的是被@property装饰的price    输出结果:9600.0
      iphone12.price = 13000.0  # 调用的是被@price.setter装饰的price
      print(iphone12.price)   # 输出结果:10400.0
      del iphone12.price  # 调用的是被@price.deleter装饰的price
      
    • 封装之classmethod

      classmethod是一个装饰器,用于装饰类中的方法,被装饰的方法会成为一个类方法。

      应用场景:定义一个方法,默认传self,但这个self没被使用,并且在这个方法里用到了类名调用属性的时候,需要使用classmethod。

      class Goods(object):
          '''
          这是一个商品类
          '''
          __discount = 0.8
      
          def __init__(self, name, original_price):
              self.name = name
              self.__orig_price = float(original_price)
              self.price = self.__orig_price * Goods.__discount
      
          @classmethod    # 把一个对象的绑定方法,修改成类方法
          def change_discount(cls, new_discount):     # cls会把类本身传进来,cls = Goods
              '''
              这是一个修改折扣的方法
              此方法中self实际上没有被使用
              :param new_discount:
              :return:
              '''
              # Goods.__discount = new_discount
              cls.__discount = new_discount   # 调用类中的静态变量
      
      Goods.change_discount(0.6)  # 不用实例化,直接用类名调用方法
      iPhone = Goods('iPhone', 13000)
      print(iPhone.price)
      
      import time
      
      class Date(object):
          def __init__(self, year, month, day):
              self.year = year
              self.month = month
              self.day = day
      
          @classmethod
          def today(cls):
              struct_time = time.localtime()
              date = cls(struct_time.tm_year, struct_time.tm_mon, struct_time.tm_mday)
              return date
      
      today_obj = Date.today()
      print(today_obj.year)   # 调的是self.year = 2020
      print(today_obj.month)  # 调的是self.month = 3
      print(today_obj.day)    # 调的是self.day = 13
      
    • 封装之staticmethod

      staticmethod是一个装饰器,用于装饰类中的方法,被装饰的方法会成为一个静态方法。

      应用场景:类外部的一个普通函数,需要放到类中,仍然保持普通函数的状态,需要使用staticmethod。一般纯面向对象编程中才会用到,不常用。

-   能定义再类中的内容

    -   静态变量:所有对象共享的变量,由对象或类调用
    -   绑定方法:自带self的函数,由对象调用。
    -   property属性:伪装成属性的方法,由对象调用,但不加()。
    -   类方法:自带cls的函数,由对象或类调用。
    -   静态方法:就是一个普通函数,由对象或类调用。

    ```python
    class People(object):
    
        country = '中国'    # 静态变量
    
        def func(self):    # 绑定方法
            print(self.__dict__)
    
        @property
        def property_func(self):    # property属性
            return 'property属性'
    
        @classmethod
        def class_func(cls):    # 类方法
            print(cls)
    
        @ staticmethod
        def static_func():    # 静态方法
            print('不用穿self,就是一个普通函数')
    ```
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容

  • 抽象类 什么是抽象类 与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的...
    go以恒阅读 604评论 0 3
  • 初识面向对象 楔子 你现在是一家游戏公司的开发人员,现在需要你开发一款叫做<人狗大战>的游戏,你就思考呀,人狗作战...
    go以恒阅读 922评论 0 6
  • 1:继承,顾名思义就是子代继承父辈的一些东西,在程序中也就是子类继承父类的属性和方法。 1 #Author : K...
    Java架构_师阅读 198评论 0 0
  • property、魔法属性和魔法方法、多重继承和多继承 1.5 property 学习目标 1. 能够说出什么要...
    Cestine阅读 791评论 0 1
  • 环境变量是在操作系统中一个具有特定名字的对象, 它包含了一个或者多个应用程序所将使用到的信息。 Path是一个常见...
    hairaaaaaaaaa阅读 1,123评论 0 1