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

相关文章

  • Python基础-day12

    list ​ 封装 ​ 继承 面向对象的三大特性 ​ 封装 继承 多态 封装 ​ 生活里 ​ ...

  • 面向对象的多态性

    简单说说面向对象的多态性。 a. 面向对象的三大特性:封装、继承、多态。从一定角度来看,封装和继承几乎都是为多态而...

  • Class 的封装及调用-Python教学【StudyQuant

    前言:面向对象的三大特性是指:封装、继承和多态。论Class 的封装及调用 面向对象三大特性 面向对象的三大特性是...

  • Objective-C初始化方法

    一、继承 面向对象的三大特性:封装,继承,多态。⾯向对象提供了继承特性。继承既能保证类的完整,又能简化代码。面向对...

  • OC面向对象

    OC面向对象—封装 一、面向对象和封装 面向对象的三大特性:封装(成员变量)、继承和多态 在OC语言中,使用@in...

  • Java面试之基础篇

    面向对象的特性 面向对象的三大特性是:封装、继承、多态。 封装 封装就是将一个对象的属性和方法进行封装。同时私有化...

  • 面向对象的三大特性

    面向对象的三大特性:{ 封装、继承、多态 } 封装复用|信息隐蔽 代码示例 继承** 获取已经存在的对象...

  • 面向对象

    面向对象有3大特性:封装、继承、多态1、介绍面向对象编程 面向对象编程(Object Oriented Progr...

  • python 高级 面向对象编程(OOP)

    面向对象有3大特性:封装、继承、多态 1、介绍面向对象编程 面向对象编程(Object Oriented Pro...

  • 面向对象的五大基本原则

    以前一直认为程序中的类有使用到封装继承多态就是面向对象设计,其实不然。 封装、继承、多态只是面向对象的三大特性,但...

网友评论

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

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