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属性没有使用描述器,所以在外部我们可以随意修改,无法对属性的操作进行拦截控制
    # 对于用户输入的年龄是否合格,只能在实例化对象之前进行判断,但是每次实例化对象的时候都要加这个判断,会出现很多冗余代码
    
  • 添加描述器示例(以描述器的一种定义方式为例)
    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、 惰性计算
    • 描述器可以用于实现惰性计算,例如将一个方法的返回值缓存起来,避免重复计算。下面是一个惰性计算的示例代码:

实现方法

  • 方式一
    • 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
    
  • 方式一示例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
    
  • 方式二示例:
    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__` 方法
    

描述器的实现原理

  • 了解描述器的实现原理有助于更深入地理解它的使用和限制。描述器的实现基于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__方法    
      
    • 非资料描述器
      class Age:
          def __get__(self, instance, owner):
              print('__get__方法')
      
      
      class Person(object):
          age = Age()
      
      
      p = Person()
      p.age               # __get__方法
      p.age = 10
      
  • 实例对象操作类属性的优先级
    • 资料描述器 > 实例属性 > 非资料描述器
  • 操作优先级示例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:实例属性与非资料描述器的优先级
    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__方法
                        
    # 整个执行过程中优先执行了实例属性,并没有执行非资料描述器
    

描述器中值的存储问题

  • 上面我们讲的这些案例中,仅仅只是实现了描述器中的__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
    
  • 上面的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: <class '__main__.Person'>
    # value: 199
    
    • 在这里插入图片描述
  • 从输出结果可以看出来四个参数分别代表:

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

    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
    

使用类实现装饰器

装饰器回顾

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

使用类实现装饰器

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

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

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

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

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

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

  • 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()
    
  • 哦豁,没有报错,运行成功了!那么接下来我们就是要实现具体的功能了

  • 那么,我们最终要执行的是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()
    
  • 最后用语法糖的模式优化一下

    class check:
        def __init__(self, func):
            self.f = func
    
        def __call__(self, *args, **kwargs):
            print('登录验证操作......')
            self.f()
    
    
    @check
    def fashuoshuo():
        print('发说说操作')
    
    
    fashuoshuo()
    
    

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

  • 1、必须在__init__方法中保存要装饰函数的函数体
  • 2、在__call__方法中执行__init__方法中保存的函数,并且在执行之前进行其他新增的操作
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失心疯_2023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值