美文网首页Python全栈
26.Python之面向对象的三大特性(继承、封装、多态)

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

作者: 免跪姓黄 | 来源:发表于2020-03-10 22:03 被阅读0次

    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,就是一个普通函数')
        ```
    

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/ruyndhtx.html