zoukankan      html  css  js  c++  java
  • Python基础学习笔记(25)super方法 封装 property装饰器 反射

    Python基础学习(25)super方法 封装 property装饰器 反射

    一、今日内容大纲

    • super 方法(继承内容补充)
    • 封装
    • property 装饰器
    • 反射

    二、super 方法

    super 方法会按照 MRO(Method Resolusion Order) 顺序来寻找当前类的下一个类,因为经典类不具有 mro 方法,所以经典类不支持 super 方法。由于单继承情况的 MRO 就是从父至子依次排序,所以在单继承时 super 方法在单继承情况下就是寻找父类。

    # super方法
    class A:
        def func(self):
            print('A')
    class B(A):
        def func(self):
            super().func()
            print('B')
    class C(B):
        def func(self):
            super().func()
            print('C')
    
    
    C().func()  # A B C
    
    class A:
        def func(self):
            print('A')
    class B(A):
        def func(self):
            super().func()
            print('B')
    class C(A):
        def func(self):
            super().func()
            print('C')
    class D(B, C):
        def func(self):
            super(C, C()).func()
            print('D')
    
    D().func()  # A B C D
    
    # 日常使用情况
    class User:
        def __init__(self, name):
            self.name = name
    
    class VIPUser(User):
        def __init__(self, name, level):
            # User.__init__(self, name)  # 这样的话可以不用继承User类
            # super().__init__(name)
            super(VIPUser, self).__init__(name)
            self.level = level
    
    tiabai = VIPUser('太白',6)
    print(tiabai.__dict__)  # {'name': '太白', 'level': 6}
    

    三、封装

    封装,其实就是把属性或者方法装起来;可以细分为广义封装和狭义封装:

    • 广义:把属性和方法装起来,外面就不能直接调用了,要通过类的名字来调用(类其实就属于广义上的封装);
    • 狭义:把属性和方法藏起来,外面不能调用,只能在内部偷偷调用。

    下面我们来主要介绍狭义的封装,狭义的封装具有三种使用情况:

    • 不让用户看到也不让用户修改;
    • 可以让用户看到但不让用户修改;
    • 可以让用户看到也可以让用户修改,但必须遵守一系列规则。

    类中变量具有三种级别:

    • public 公有的 类内外都可以使用,父类子类也都可以使用(Python 支持);
    • protect 保护的 类内能用,父类子类也都可以使用,但类外不可以使用(Python 不支持);
    • private 私有的 类内能用,其他地方都不可以使用(Python 支持)。

    我们今天介绍的狭义封装就是依靠私有变量实现的,那么类都可以四有哪些内容呢?

    • 私有静态变量
    • 私有实例变量
    • 私有绑定方法

    如在平时的用户登录操作中,用户的密码不能随随便便被外部调用,所以我们利用私有变量隐藏起这个属性:

    # 隐藏属性方法:
    class User:
        def __init__(self, name, password):
            self.user = name
            self.__password = password  # 给一个属性前面加上了双下划线的时候,这个属性就变成了私有的
    
    
    # 官方称其为私有变量
    alex = User('alex', 'sbsbsb')
    # print(alex.__password)  # AttributeError: 'User' object has no attribute '__password'
    

    如果我们希望实现用户只能看到不能修改,可以在类内定义方法来实现读取私有变量:

    # 所有的私有内容或者名字都不能在类的外部调用,只能在内部使用了
    # 如果一定要返回私有变量,可以通过定义绑定方法的方式返回
    # 这种绑定方法返回私有变量的情况,主要是想让用户看,但是不让用户该的情况
    class User:
        def __init__(self, name, password):
            self.user = name
            self.__password = password  # 私有的实例变量/私有的对象属性
    
        def get_pwd(self):  # 表示的时用户不能改只能看
            return self.__password
    
        def change_pwd(self):  # 表示用户必须调用我们自定义的修改方式来进行变量的修改
            pass
    
    
    alex = User('alex', 'sbsbsb')
    print(alex.get_pwd())  # sbsbsb
    
    
    # 返回经hashlib加密的变量
    import hashlib
    
    
    class User:
        def __init__(self, name, password):
            self.user = name
            self.__pwd = password  # 私有的实例变量
    
        def __get_md5(self):  # 私有的变量
            md5 = hashlib.md5(self.user.encode('utf-8'))
            md5.update(self.__pwd.encode('utf-8'))
            return md5.hexdigest()
    
        def getpwd(self):
            return self.__get_md5()
    
    
    alex = User('alex', 'sbsbsb')
    print(alex.getpwd())
    

    那么,从内部实现的角度来说,为什么类内的变量名字加了下划线就不能从外部调用了呢?

    class User:
        __Country = 'China'
    
        def func(self):
            print(self.__Country)  # 在类的内部使用的时候,自动地把当前这句话所在的类的名字拼在私有变量钱完成变形
    
    
    print(User.__dict__)
    
    
    # {'__module__': '__main__', '_User__Country': 'China', 'a': 'b', '__dict__': <attribute '__dict__' of 'User' objects>, '__weakref__': <attribute '__weakref__' of 'User' objects>, '__doc__': None}
    # __Country -> _User__Country
    # 所以其实非要找的话是找得到的
    # print(User._User__Country)  # China
    
    
    # 在类的外部根本不能定义私有的概念
    # User.__aaa = 'bbb'  # 这是不可以运行的
    

    我们知道,私有的内容是不可被继承的,那么这是什么原因呢?这是因为私有的内容在类内调用的时候会自动填补上_classname所以即使子类定义了同名私有变量,但是其实实际上定义的变量/函数名是不同的,所以私有的变量/函数只能在类内使用:

    # 私有的内容能不能被子类使用呢?
    class Foo(object):
        def __init__(self):
            self.func()
    
        def func(self):
            print('in Foo')
    
    
    class Son(Foo):
        def func(self):
            print('in Son')
    
    
    Son()  # in Son
    
    
    # 私有的内容不可以被继承!
    class Foo(object):
        def __init__(self):
            self.__func()  # 表面上在寻找__func,实际上是在寻找_Foo__func
    
        def __func(self):
            print('in Foo')
    
    
    class Son(Foo):
        def __func(self):
            print('in Son')
    
    
    Son()  # in Foo
    

    四、property 装饰器

    1. property 装饰器

      为了方便 property 装饰器的理解,我们先引入一个之前举过的例子,圆类:

      from math import pi
      
      class Circle:
          def __init__(self, radius):
              self.radius = radius
      
          def area(self):
              return pi * self.radius ** 2
      
      
      c1 = Circle(5)
      print(c1.radius)  # 获取半径
      print(c1.area())  # 获取面积
      

      我们可以看到,圆在此类中获取半径和面积的方法是截然不同的,因为一个是内部方法,一个是实例属性;但是在我们正常逻辑理解中,圆的半径和面积应该都是属于名词,都应该是圆的属性,这样我们就可以利用 property 装饰器,将一个类内的内部方法伪装成属性在外部使用:

      from math import pi
      
      class Circle:
          def __init__(self, radius):
              self.radius = radius
      
          @property
          def area(self):
              return pi * self.radius ** 2
      
      
      c1 = Circle(5)
      print(c1.radius)  # 获取半径
      print(c1.area)  # 获取面积
      

      为了能更轻松理解 property 装饰器,我们再举两个例子:

      # property装饰器的应用场景1:
      import time
      
      
      class Person:
          def __init__(self, name, birth):
              self.name = name
              self.birth = birth
      
          @property
          def age(self):  # 装饰的这个方法,不可以有参数
              return time.localtime().tm_year - self.birth
      
      
      taibai = Person('taibai', 1998)
      print(taibai.age)
      
      
      # property的应用场景2:和私有属性合作
      class User:
          def __init__(self, user, password):
              self.user = user
              self.__password = password
      
          @property
          def pwd(self):
              return self.__password
      
      
      alex = User('alex', 'sbsbsb')
      print(alex.pwd)  # sbsbsb
      
      # 售货例子:苹果的打折
      class Goods:
          discount = 0.8
      
          def __init__(self, name, origin_price):
              self.name = name
              self.__price = origin_price
      
          @property
          def price(self):
              return self.__price * self.discount
      
      
      apple = Goods('apple', 5)
      print(apple.price)  # 4.0
      
    2. property_name.setter 装饰器

      当我们需要在外部修改苹果的价格时,单纯的使用 property 装饰器伪装的内部方法会暴露,所以我们必须再伪装一个同名内部方法来完成其修改的操作:

      class Goods:
          discount = 0.8
      
          def __init__(self, name, origin_price):
              self.name = name
              self.__price = origin_price
      
          @property
          def price(self):
              return self.__price * self.discount
      
          @price.setter
          def price(self, new_value):
              if isinstance(new_value, float):  # 起到约束数据类型的作用
                  self.__price = new_value
      
      
      apple = Goods('apple', 5)
      print(apple.price)  # 调用的是被@property装饰的price
      apple.price = 10.0  # 调用的是被setter装饰的price
      print(apple.price)  # 8.0
      
    3. property_name.deleter 装饰器

      虽然已经支持修改和调用,但是如果我们在外部删除苹果的价格时,内部方法仍然会暴露,所以我们可以再伪装一个同名内部方法来完成其删除的操作:

      # 我们要删除苹果的价格的时候,又会产生新需求,这时我们需要deleter
      class Goods:
          discount = 0.8
      
          def __init__(self, name, origin_price):
              self.name = name
              self.__price = origin_price
      
          @property
          def price(self):
              return self.__price * self.discount
      
          @price.setter
          def price(self, new_value):
              if isinstance(new_value, float):  # 起到约束数据类型的作用
                  self.__price = new_value
      
          @price.deleter
          def price(self):
              if isinstance():  # 起到约束数据类型的作用
                  del self.__price
      
      apple = Goods('apple', 5)
      del apple.price  # 并不能真的删除什么东西,只是调用被装饰的方法而已
      

    五、反射

    1. getattr()内部函数

      反射其实就是利用字符串数据类型的名字,来操作这个名字对应地函数/实例变量/绑定方法等。如我们举一个日常编写代码中的问题:

      name = 'alex'
      age = 123
      # birth
      # sex
      # job
      # phone
      # ...这么多怎么办?
      
      n = input('>>>')
      if n == 'name': print(name)
      elif n == 'age': print(age)
      # ...
      

      有些时候,你明明知道一个变量的字符串数据类型的名字,你想直接调用它但是调用不到,这时可以利用反射,反射支持以下多种情况:

      • 反射对象的实例变量;
      • 反射对象的静态变量/绑定方法等;
      • 反射模块中的所有变量:包括被导入模块,当前执行的 py 文件(脚本)。
      class Person:
          def __init__(self, name, age):
              self.name = name
              self.age = age
      
          def qqxing(self):
              print('qqxing')
      
      
      alex = Person('alex', 83)
      wusir = Person('wusir', 74)
      print(alex.name)
      print(alex.age)
      ret1 = getattr(alex, 'name')
      ret2 = getattr(wusir, 'name')
      print(ret1)  # alex
      print(ret2)  # wusir
      ret = getattr(wusir, 'qqxing')
      print(ret)  # <bound method Person.qqxing of <__main__.Person object at 0x00000226CAC4DBE0>>
      ret()  # qqxing
      

      这样,反射的基本用法已经介绍完了,我们现在举一个之前写过的例子:支付程序

      # 实际使用情况:归一化设计
      class Payment:
          def pay(self, money):
              raise NotImplementedError('请在子类中重写同名pay方法')
      
      
      # 然后将其他的支付函数继承这个Payment类
      class Alipay(Payment):
          def __init__(self, name):
              self.name = name
      
          def pay(self, money):
              dic = {'uname': self.name, 'price': money}
              # 想办法调用支付宝支付 url链接 把dic传过去
              print('%s通过支付宝支付%s成功' % (self.name, money))
      
      
      class Wechat(Payment):
          def __init__(self, name):
              self.name = name
      
          def pay(self, money):
              dic = {'username': 'alex', 'money': 200}
              # 想办法调用微信支付 url链接 把dic传过去
              print('%s通过微信支付%s成功' % (self.name, money))
      
      
      class Applepay(Payment):
          def __init__(self, name):
              self.name = name
      
          def pay(self, money):
              dic = {'name': self.name, 'qian': money}
              # 想办法调用苹果支付 url链接 把dic传过去
              print('%s通过苹果支付%s成功' % (self.name, money))
      
      
      def pay(name, price, mode):
          if mode == 'Wechat':
              obj = Wechat(name)
          elif mode == 'Alipay':
              obj = Alipay(name)
          elif mode == 'Applepay':
              obj = Applepay(name)
          obj.pay(price)
      
      
      pay('alex', 400, 'Wechat')  # alex通过微信支付400成功
      pay('alex', 500, 'Alipay')  # alex通过支付宝支付500成功
      pay('alex', 500, 'Applepay')  # alex通过苹果支付500成功
      

      我们现在要利用反射来解决pay()函数中冗长的代码,但是这些变量并没有封装在模块里,该如何反射呢?我们先研究个小问题抛砖引玉:

      # 在本模块里前面没有点要怎么解决呢?
      import sys
      import temp  # 这是我们随手自己写的一个模块
      
      # 两者等价
      print(sys.modules['temp'].Alipay)  # <class 'temp.Alipay'>
      print(temp.Alipay)  # <class 'temp.Alipay'>
      # 两者等价
      print(getattr(temp, 'Alipay'))
      print(getattr(sys.modules['a'], 'Alipay'))
      

      所以我们从上面的小例子可以知道,如果想反射当前执行的脚本,可以使用下面的方法:

      wahaha = 'wahaha'
      print(getattr(sys.modules['__main__'], 'wahaha'))  # wahaha
      

      这样,pay()函数的优化问题就很好解决了:

      # 所以可对原先的归一化函数进行这样的修改
      def pay(name, price, mode):
          ret = getattr(sys.modules['__main__'], mode)
          obj = ret(name)
          obj.pay(price)
      
      pay('alex', 400, 'Wechat')  # alex通过微信支付400成功
      pay('alex', 500, 'Alipay')  # alex通过支付宝支付500成功
      pay('alex', 500, 'Applepay')  # alex通过苹果支付500成功
      

      要点小结:

      # 使用反射
      
      # 1.反射对象的实例变量
      # 2.反射对象的静态变量/绑定方法等
      # 3.反射模块中的所有变量:包括被导入的模块,当前执行的py文件-脚本
      class A:
          Role = 'fashi'
          def __init__(self):
              self.name = 'alex'
              self.age = 84
          def func(self):
              print('wahaha')
              return 666
      a = A()
      # 实例变量
      print(getattr(a, 'name'))  # alex
      # 静态变量/绑定方法
      print(getattr(a, 'func')())  # wahaha 666
      print(getattr(a, 'Role'))  # fashi
      # 反射导入模块
      # import module_name
      # getattr(module_name, 'variable_name')
      # 反射当前文件
      getattr(sys.modules['__main__'], 'variable_name')
      
    2. hasattr()内部函数和callable()内部函数

      反射在日常使用中,我们会发现,因为无法判断该变量/方法是否存在的原因,总会出现报错:

      class A:
          Role = 'fashi'
          def __init__(self):
              self.name = 'alex'
              self.age = 84
          def func(self):
              print('wahaha')
              return 666
      
      a = A()
      # print(getattr(a, 'gender'))  # AttributeError: 'A' object has no attribute 'gender'
      

      这时我们可以在反射的使用中假如hasattr()callable()来判断反射内容是否存在以及是否可以调用,来防止报错的发生:

      class A:
          Role = 'fashi'
          def __init__(self):
              self.name = 'alex'
              self.age = 84
          def func(self):
              print('wahaha')
              return 666
      
      a = A()
      if hasattr(a, 'gender'):  # 判断是否拥有属性,防止报错
          print(getattr(a, 'gender'))
      if hasattr(a, 'func'):
          if callable(getattr(a, 'func')):  # 判断是否可以调用,防止报错
              print(getattr(a, 'func')())  # wahaha 666
      
  • 相关阅读:
    jmeter的基本功能使用详解
    服务器资源监控插件(jmeter)
    前端技术之--CSS
    前端技术之--HTML
    TCP/IP基础知识
    TCP/IP、Http的区别
    关于性能调优
    如何修改Docker已运行实例的端口映射
    Mysql 主从同步配置
    Presto的基本概念
  • 原文地址:https://www.cnblogs.com/raygor/p/13371768.html
Copyright © 2011-2022 走看看