• 032.Python面向对象_类&补充_描述器


    无奋斗不青春

    我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈
    入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈
    虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈
    PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)文章合集 👈👈
    Oracle数据库教程:👉👉 Oracle数据库文章合集 👈👈
    优 质 资 源 下 载 :👉👉 资源下载合集 👈👈

    分隔线

    描述器

    作用

    • 描述器是通过实现特殊方法(__get__, __set__, __delete__)来拦截属性访问行为的对象
    • 描述器是封装了描述(控制)一个属性操作(增/改、删、查)的多个方法的对象
    • 当一个描述器对象作为类属性被访问时
      • Python会自动调用描述器的__get__方法来获取属性值(p[age])
      • 如果同时定义了__set__方法,则可以通过赋值操作修改属性的值(p[age] = 36)
      • 如果定义了__delete__方法,则可以使用del语句删除属性(del p[age])
    • 对比示例
      class Person(object):
          def __init__(self):
              self.age = 18
      
      
      a = int(input('请输入年龄:'))
      
      p = Person()
      print('你当前的年龄是:', p.age)
      p.age = a
      print('你当前的年龄是:', p.age)
      
      # 输出
      # 你当前的年龄是:18
      # 请输入年龄:-100
      # 你当前的年龄是: -100
      
      # 这个示例中,age属性没有使用描述器,所以在外部我们可以随意修改,无法对属性的操作进行拦截控制
      # 对于用户输入的年龄是否合格,只能在实例化对象之前进行判断,但是每次实例化对象的时候都要加这个判断,会出现很多冗余代码
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
    • 添加描述器示例(以描述器的一种定义方式为例)
      class Person(object):
          def __init__(self):
              self.__age = 18
      
          def get_age(self):
              return self.__age
      
          def set_age(self, value):
              # 可以在这里对属性设置的值进行判断
              if value < 0 or value > 180:
                  print('你输入的年龄太小或者太大了!')
              else:
                  self.__age = value
      
          def del_age(self):
              del self.__age
      
          age = property(get_age, set_age, del_age)
      
      
      p = Person()
      
      print(p.age)
      p.age = -100
      print(p.age)
      
      # 输出
      # 18
      # 你输入的年龄太小或者太大了!
      # 18
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
    • 图解
      • 在这里插入图片描述
      • 在这里插入图片描述

    常见用法

    • 1、 属性访问控制 >> 只读属性 的实现
      • 描述器可以用于实现属性访问控制,例如只读属性、只写属性、私有属性等等。下面是一个只读属性的
    • 2、类型检查
      • 描述器可以用于实现类型检查,例如强制属性值为整数、字符串等等。下面是一个强制属性值为整数的示例代
    • 3、 惰性计算
      • 描述器可以用于实现惰性计算,例如将一个方法的返回值缓存起来,避免重复计算。下面是一个惰性计算的示例代码:

    实现方法

    • 方式一
      • property(get_xxx, set_xxx, del_xxx)实例对象
      • @property、@xxx.setter、@xxx.deleter装饰器
      • 缺点:每1个属性就需要定义3个方法来控制,那3个属性就需要9个方法,很繁琐!
    • 方式二
      • 自定义类实现__get__, __set__, __delete__
      • 注意:
        • 在经典类中并不会调用这三个方法,而是创建一个实例属性
        • 通过类.属性的方式操作属性时,只会调用 __get__ 方法,不会调用 __set____delete__ 方法

    示例代码

    • 方式一示例1:property(get_xxx, set_xxx, del_xxx)实例对象
      class Person(object):
          def __init__(self):
              self.__age = 18
      
          def get_age(self):
              return self.__age
      
          def set_age(self, value):
              # 可以在这里对属性设置的值进行判断
              if value < 0 or value > 180:
                  print('你输入的年龄太小或者太大了!')
              else:
                  self.__age = value
      
          def del_age(self):
              del self.__age
      
          age = property(get_age, set_age, del_age)
      
      
      p = Person()
      
      print(p.age)                # 18
      p.age = -100                # 你输入的年龄太小或者太大了!
      print(p.age)                # 18
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
    • 方式一示例2:@property、@xxx.setter、@xxx.deleter装饰器
      class Person(object):
          def __init__(self):
              self.__age = 18
      
          @property
          def age(self):
              return self.__age
      
          @age.setter
          def age(self, value):
              if value < 0 or value > 100:
                  print('你输入的年龄太小或者太大了!')
              else:
                  self.__age = value
      
          @age.deleter
          def age(self):
              print('删除前可进行删除判断')
              del self.__age
      
      
      p = Person()
      
      print(p.age)                # 18
      p.age = -100                # 你输入的年龄太小或者太大了!
      print(p.age)                # 18
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
    • 方式二示例:
      class Age:
          def __get__(self, instance, owner):
              print('__get__方法')
      
          def __set__(self, instance, value):
              print('__set__方法')
      
          def __delete__(self, instance):
              print('__delete__方法')
      
      
      class Person(object):
          age = Age()
      
      
      # p = Person()
      #
      # p.age                   # __get__方法
      # p.age = 100             # __set__方法
      # p.age                   # __get__方法
      # del p.age               # __delete__方法
      
      
      Person.age                  # __get__方法
      Person.age = 100            # 不会调用__set__方法
      del Person.age              # 不会调用__delete__方法
      # Person.age
      
      # 通过`类.属性`的方式操作属性时,只会调用 `__get__` 方法,不会调用 `__set__` 和 `__delete__` 方法
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29

    描述器的实现原理

    • 了解描述器的实现原理有助于更深入地理解它的使用和限制。描述器的实现基于Python的属性查找机制。当我们访问一个属性时,Python会按照以下顺序查找属性:
      • 1、 在实例对象本身的__dict__字典中查找属性,如果存在则返回。
      • 2、 在对应类对象的__dict__字典中查找属性,如果存在则返回。
      • 3、 如果有父类,会再到父类的__dict__字典中查找属性,如果存在则返回。
      • 4、 重复步骤3,直到找到基类为object
      • 5、 如果没找到,有定义了__getattr__方法,就会调用这个方法
    • 可以看到,在整个查找机制中并没有涉及到描述器中的__get__方法
    • 描述器的__get__方法被优先调用的原因
      • 主要是通过__getattnibute__方法来实现
      • __getattnibute__方法内部实现过程
        • 首先会检测一下,是否实现了描述器的__get__方法,如果有就会直接调用
        • 如果没有实现__get__方法,则按照上面的查找机制进行查找
      • 如果重写了__getattnibute__方法,则不会执行描述器的__get__方法了
    • 需要注意的是,描述器只对类属性起作用,对实例属性不起作用。如果我们直接访问实例属性,则不会触发描述器的行为。

    描述器与实例属性同名时,操作优先级

    • 描述器分类
      • 资料描述器:实现了__get____set__方法的描述器
      • 非资料描述器:只实现了__get__方法的描述器
      • 资料描述器
        class Age:
            def __get__(self, instance, owner):
                print('__get__方法')
        
            def __set__(self, instance, value):
                print('__set__方法')
        
        
        class Person(object):
            age = Age()
        
        
        p = Person()
        p.age               # __get__方法
        p.age = 10          # __set__方法    
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
      • 非资料描述器
        class Age:
            def __get__(self, instance, owner):
                print('__get__方法')
        
        
        class Person(object):
            age = Age()
        
        
        p = Person()
        p.age               # __get__方法
        p.age = 10
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
    • 实例对象操作类属性的优先级
      • 资料描述器 > 实例属性 > 非资料描述器
    • 操作优先级示例1:资料描述器与实例属性的优先级
      class Age:
          def __get__(self, instance, owner):
              print('__get__方法')
      
          def __set__(self, instance, value):
              print('__set__方法')
      
      
      class Person(object):
          age = Age()
      
          def __init__(self):
              self.age = 10
      
      
      p = Person()
      p.age = 100
      p.age
      print(p.__dict__)
      
      # 输出结果
      # __set__方法     # p = Person() 实例化对象时会自动执行__init__初始化方法,执行 self.age = 10 调用了描述器里面的__set__方法
      # __set__方法     # 执行 p.age = 100 时,又自动调用描述器里面的__set__方法
      # __get__方法     # 执行 p.age 时,自动调用描述器里面的 __get__方法
      # {}             # 执行 self.age = 10 时,调用了描述器里重写的 __set__ 方法,仅仅只是打印了__set__字符,没有做其他操作,所以实例中并没有添加 age 属性
      
      # 整个过程都是优先执行了资料描述器里面的__get__方法和__set__方法,并没有执行实例属性
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
    • 操作优先级示例1:实例属性与非资料描述器的优先级
      class Person(object):
          age = Age()
      
          def __init__(self):
              self.age = 10
      
      
      p = Person()
      p.age = 100
      p.age
      print(p.__dict__)
      
      # 输出结果
      {'age': 100}        # p = Person() 实例化对象时会自动执行__init__初始化方法,执行了 self.age = 10 ,将age属性直接添加到了实例对象的__dict__字典中,
                          # 并没有调用描述器内部的__set__方法
                          
      # 整个执行过程中优先执行了实例属性,并没有执行非资料描述器
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

    描述器中值的存储问题

    • 上面我们讲的这些案例中,仅仅只是实现了描述器中的__get____set____delete__方法,但是并没有真正实现值的存储,那么我们来看一下如何实现值的传递和存储

    • 描述器示例

      class Age(object):
          """描述器"""
          def __get__(self, instance, owner):
              pass
          
          def __set__(self, instance, value):
              pass
          
          def __delete__(self, instance):
              pass
          
          
      class Person(object):
          age = Age()
          
          def __init__(self):
              self.age = 10
              
      
      p = Person()
      p.age = 100
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
    • 上面的Age描述器中实现了__get____set____delete__三个方法,在这三个方法中有四个参数,我们来看一下这四个参数分别代表什么

      # 描述器中值的存储
      
      class Age(object):
          """描述器"""
          def __get__(self, instance, owner):
              print('self:', self)
              print('instance:', instance)
              print('owner:', owner)
      
          def __set__(self, instance, value):
              print('value:', value)
      
          def __delete__(self, instance):
              pass
      
      
      class Person(object):
          age = Age()
      
      
      p = Person()
      p.age
      p.age = 199
      
      # 输出结果
      # self: <__main__.Age object at 0x000002A15828DFD0>
      # instance: <__main__.Person object at 0x000002A15828DFA0>
      # owner: 
      # value: 199
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 在这里插入图片描述
    • 从输出结果可以看出来四个参数分别代表:

      self:      # 描述器Age类的实例化对象
      instance:  # Person类的实例化对象
      owner:     # Person类
      value:     # Person类实例化对象的属性值发生变化时的值
      
      • 1
      • 2
      • 3
      • 4
      • 在这里插入图片描述
    • 上面的示例可能还有点抽象,那么看下面这个示例

      class Age(object):
          """描述器"""
          a = 'Age类属性'
      
          def __init__(self):
              self.a = 'Age实例属性'
      
          def __get__(self, instance, owner):
              print('self:', self.a)
              print('instance:', instance.a)
              print('owner:', owner.a)
      
          def __set__(self, instance, value):
              print('value:', value)
      
          def __delete__(self, instance):
              pass
      
      
      class Person(object):
      
          age = Age()
          a = 'Person类属性'
      
          def __init__(self):
              self.a = 'Person实例属性'
      
      
      p = Person()
      
      p.age
      p.age = 199
      
      
      # 输出结果
      # self: Age实例属性
      # instance: Person实例属性
      # owner: Person类属性
      # value: 199
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39

    使用类实现装饰器

    装饰器回顾

    • Python基础进阶_装饰
    • 有一段代码可以执行发说说的操作
      def fashuoshuo()
          print('发说说操作')
          
      fashuoshuo()
      
      • 1
      • 2
      • 3
      • 4
    • 新要求:在发说说之前,先做登录验证
    • 前提:不能修改原方法的调用方式
    • 实现:
      • 通过装饰器实现(语法糖模式)
        def check(func):
            def inner():
                print('进行登录验证...')
                func()
            return inner
        
        @check
        def fashuoshuo():
            print('发说说操作')
        
        
        fashuoshuo()
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
      • 语法糖模式原理分解
        def check(func):
            def inner():
                print('进行登录验证...')
                func()
            return inner
        
        # @check
        def fashuoshuo():
            print('发说说操作')
        
        
        fashuoshuo = check(fashuoshuo)      # @check语法糖原理
        # fashuoshuo这个变量接收到的是check这个方法的返回值:inner函数体
        
        fashuoshuo()
        # 此时这里执行的fashuoshuo(),实际上是执行的inner()
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16

    使用类实现装饰器

    • 我们通过上面的函数装饰器可以看到@check这种语法糖模式,实际上就是执行了fashuoshuo = check(fashuoshuo)

    • 那么我们现在通过反推的方式来理解类实现装饰器

    • 我们先将前面的def check函数替换成class check

      class check:
          pass
      
      # @check
      def fashuoshuo():
          print('发说说操作')
      
      # @check语法糖的执行原理
      fashuoshuo = check(fashuoshuo)
      # 这里实际上是实例化check类对象,会自动执行类内部的__init__方法
      
      fashuoshuo()
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • 在这个案例中fashuoshuo = check(fashuoshuo)这一句实际上是实例化check类的实例对象

    • 在实例化示例对象的时候,就会默认调用类中的__init__方法,此时,我们还不知道在这个方法中执行什么,所有先不实现任何操作,用占位关键字占位

      class check:
          def __init__(self, func):
              pass
      
      # @check
      def fashuoshuo():
          print('发说说操作')
      
      # @check语法糖的执行原理
      fashuoshuo = check(fashuoshuo)
      # 这里实际上是实例化check类对象,会自动执行类内部的__init__方法
      
      fashuoshuo()
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    • 看样子,这样就搞定了…

    • But…

      • 在这里插入图片描述
    • 报错了!报错在第13行! fashuoshuo()… why???

    • 哦!哦!哦!

    • fashuoshuo()这里的fashuoshuo已经不是一个函数了,这里是一个实例对象

    • 实例对象默认是不允许通过加()调用执行的,要想通过加()调用执行,必须在类内部实现__call__方法

      class check:
          def __init__(self, func):
              pass
      
          def __call__(self, *args, **kwargs):
              pass
      
      # @check
      def fashuoshuo():
          print('发说说操作')
      
      # @check语法糖的执行原理
      fashuoshuo = check(fashuoshuo)
      # 这里实际上是实例化check类对象,会自动执行类内部的__init__方法
      
      fashuoshuo()
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
    • 哦豁,没有报错,运行成功了!那么接下来我们就是要实现具体的功能了

    • 那么,我们最终要执行的是def fashuoshuo()这个函数体,即在__call__这个方法中执行这个函数体

    • 但是,在__call__这个方法中怎么拿到这个函数体呢?

    • 我们在实例化check类的实例对象的时候,把fashuoshuo()这个函数体作为参数传入了进去

    • 那么在__init__方法中就可以接收到这个函数体,我们把这个函数体保存在实例属性中

    • __call__方法再通过实例属性就可以执行这个函数体了

    • 同样的,最后把要增加的操作执行在调用这个函数体之前就行了

      class check:
          def __init__(self, func):
              self.f = func
      
          def __call__(self, *args, **kwargs):
              print('登录验证操作......')
              self.f()
      
      # @check
      def fashuoshuo():
          print('发说说操作')
      
      # @check语法糖的执行原理
      fashuoshuo = check(fashuoshuo)
      # 这里实际上是实例化check类对象,会自动执行类内部的__init__方法
      
      fashuoshuo()
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
    • 最后用语法糖的模式优化一下

      class check:
          def __init__(self, func):
              self.f = func
      
          def __call__(self, *args, **kwargs):
              print('登录验证操作......')
              self.f()
      
      
      @check
      def fashuoshuo():
          print('发说说操作')
      
      
      fashuoshuo()
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16

    通过类实现装饰器的注意点

    • 1、必须在__init__方法中保存要装饰函数的函数体
    • 2、在__call__方法中执行__init__方法中保存的函数,并且在执行之前进行其他新增的操作
  • 相关阅读:
    Go入门-Java学者
    A Survey on Trustworthy Recommender Systems 25 Jul 2022
    Android开发基础:Activity的生命周期 Activity中的数据保持
    用c++写平均分
    nginx代理本地服务请求,避免跨域;前端图片压缩并上传
    【附源码】计算机毕业设计java员工工资管理系统设计与实现
    毕业季--写给大学毕业生的一番话
    MySQL增删查改(初阶)
    概念科普(一):数据脱敏
    Gopher必读:HttpClient的两个坑位
  • 原文地址:https://blog.csdn.net/weixin_50296259/article/details/134517323