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

无奋斗不青春

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

分隔线

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

    • 描述器
      • 作用
      • 常见用法
      • 实现方法
      • 示例代码
      • 描述器的实现原理
      • 描述器与实例属性同名时,操作优先级
      • 描述器中值的存储问题
    • 使用类实现装饰器
      • 装饰器回顾
      • 使用类实现装饰器
      • 通过类实现装饰器的注意点

描述器

作用

  • 描述器是通过实现特殊方法(__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__方法中保存的函数,并且在执行之前进行其他新增的操作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/173199.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java: 无效的目标发行版: 17 问题解决

今天在写完类点击运行后显示java: 无效的目标发行版: 17 网上查询了一番&#xff0c;发现有几个地方需要注意。 还有一个就是设置中&#xff0c;下面的就是我本次问题所在&#xff0c;不知道为什么&#xff0c;他自动添加了下面的东西 一个方法是把目标字节码版本改为正确的&a…

跳台阶游戏(Python排列组合函数itertools.combinations的应用)

给定台阶总数和两种单次可跳级数&#xff0c;编写自定义函数&#xff0c;计算所有的游戏组合方案数量。 (笔记模板由python脚本于2023年11月19日 19:18:48创建&#xff0c;本篇笔记适合熟悉python自定义函数编写&#xff0c;了解排列组合知识的coder翻阅) 【学习的细节是欢悦的…

mysql 查询

-- 多表查询select * from tb_dept,tb_emp; 内来链接 -- 内连接 -- A 查询员工的姓名 &#xff0c; 及所属的部门名称 &#xff08;隐式内连接实现&#xff09;select tb_emp.name,tb_dept.name from tb_emp,tb_dept where tb_emp.idtb_emp.id;-- 推荐使用select a.name,b.n…

VMware——WindowServer2012R2环境安装mysql5.7.14解压版_互为主从(图解版)

目录 一、服务器信息二、192.168.132.35服务器上安装mysql&#xff08;主&#xff09;2.1、环境变量配置2.2、安装2.2.1、修改配置文件内容2.2.2、初始化mysql并指定超级用户密码2.2.3、安装mysql服务2.2.4、启动mysql服务2.2.5、登录用户管理及密码修改2.2.6、开启远程访问 三…

电脑显示msvcp140_1.dll丢失的5个常用解决方法,亲测可修复

常见于计算机操作中的"msvcp140_1.dll丢失"错误警示&#xff0c;往往令部分应用程序无法正常启动。为了解决这个问题&#xff0c;我们需要采取一些措施来修复丢失的文件。本文将介绍6个解决msvcp140_1.dll丢失的方法&#xff0c;帮助大家快速恢复计算机的正常运行。 …

Week-T10 数据增强

文章目录 一、准备环境和数据1.环境2. 数据 二、数据增强&#xff08;增加数据集中样本的多样性&#xff09;三、将增强后的数据添加到模型中四、开始训练五、自定义增强函数六、一些增强函数 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f…

我在CSDN开组会1-蒙特卡洛模拟在矿床学的应用展望

各位老师、同学们&#xff0c;大家好。今天组会的内容是蒙特卡洛模拟在矿床学的应用展望。 为什么要讲蒙特卡洛模拟呢&#xff0c;因为我发现在地质学方面已经有不少应用&#xff0c;但是蒙特卡洛模拟延伸的知识太晦涩了&#xff0c;劝退了很多探究者们。因此&#xff0c;计划…

Django批量插入数据及分页器

文章目录 一、批量插入数据二、分页1.分页器的思路2.用一个案例试试3.自定义分页器 一、批量插入数据 当我们需要大批量创建数据的时候&#xff0c;如果一条一条的去创建或许需要猴年马月 我们可以先试一试for循环试试 我们首先建立一个模型类来创建一个表 models.py&#xff…

有依次对应关系的数组X、Y、Z,如何排序其中一个X数组,使得另外的数组还与排序完成后的数组相对应(C语言实现)

1. 目的 有依次对应关系的数组X、Y、Z&#xff0c;排序其中一个X数组&#xff0c;使得另外的数组还与排序完成后的数组相对应&#xff0c;并打印出排序完成后的X、Y、Z数组。 2. 具体实现 以下面的这个对应关系为例&#xff0c;进行相应编程实现。 X [3.7,7.7,-6.6,1.5,-4.5…

腾讯云HAI域AI作画

目录 &#x1f433;前言&#xff1a; &#x1f680;了解高性能应用服务 HAI &#x1f47b;即插即用 轻松上手 &#x1f47b;横向对比 青出于蓝 &#x1f424;应用场景-AI作画 &#x1f424;应用场景-AI对话 &#x1f424;应用场景-算法研发 &#x1f680;使用HAI进行…

ChatGPT暂时停止开通plus,可能迎来封号高峰期

前言: 前两日,chat gpt的创始人 San Altman在网上发表了,由于注册的使用量超过了他们的承受能力,为了确保每个人的良好使用体验,chat gpt将暂时停止开通gpt plus。 情况: 前段时间好像出现了官网崩溃的情况,就连api key都受到了影响,所以现在就开始了暂时停止plus的注…

【数据结构】栈详解

目录 1. 前言2. 栈2.1 栈的概念及结构2.2 如何实现栈2.3 数组栈实现2.3.1 top怎么确定2.3.2 栈顶插入2.3.2.1 栈顶插入分析2.3.2.2 栈顶插入代码实现 2.3.3 栈顶删除2.3.4 判空2.3.4.1 分析2.3.4.2 代码实现 2.3.5 栈的元素个数2.3.6 栈销毁2.3.7 栈访问数据 3. 源代码3.1 Stac…

苍穹外卖—解决前端时间属性显示问题

项目场景&#xff1a; 点击员工管理 出现显示时间属性问题 输入员工姓名为zhangsan 现实的时间属性是数组类型 问题描述 提示&#xff1a;这里描述项目中遇到的问题&#xff1a; 例如&#xff1a;数据传输过程中数据不时出现丢失的情况&#xff0c;偶尔会丢失一部分数据 APP …

手把手带你在AutoDL上部署InternLM-Chat-7B Transformers

手把手带你在AutoDL上部署InternLM-Chat-7B Transformers 调用 项目地址&#xff1a;https://github.com/KMnO4-zx/self_llm.git 如果大家有其他模型想要部署教程&#xff0c;可以来仓库提交issue哦~ 也可以自己提交PR&#xff01; InternLM-Chat-7B Transformers 部署调用 环…

【代数学习题4.2】从零理解范数与迹 —— 求数域元素的范数与迹

从零理解范数与迹 —— 求数域元素的范数与迹 写在最前面题目解答 2. 范数 N N N思路求解过程python求解 3. 数域 K K K 的范数 N K N_K NK​思路求解过程Python求解分析解题步骤 4. 迹 T T T求解过程共轭元素计算迹 python求解分析解题步骤 5. 数域 K K K 的迹 T K T_K …

利用 React 和 Bootstrap 进行强大的前端开发

文章目录 介绍React 和 Bootstrap设置环境使用 Bootstrap 创建 React 组件React-Bootstrap 组件结论 介绍 创建响应式、交互式和外观引人入胜的 Web 界面是现代前端开发人员的基本技能。幸运的是&#xff0c;借助 React 和 Bootstrap 等工具的出现&#xff0c;制作这些 UI 变得…

算法设计与分析复习--回溯法(二)

文章目录 上一篇0-1背包问题图着色问题n皇后问题下一篇 上一篇 算法设计与分析复习–回溯&#xff08;一&#xff09; 0-1背包问题 问题描述&#xff1a;给定n中物品和一个背包。物品 i i i 的重量是 w i w_i wi​ &#xff0c;其价格为 v i v_i vi​ , 背包容量为 c c …

speech studio-神经网络定制自己的声音

Speech Studio - 神经网络定制声音 - 概述 (microsoft.com)

Java,数据结构与集合源码,数据结构概述

目录 数据结构概念&#xff1a; 数据结构的研究对象&#xff1a; 研究对象一&#xff0c;数据间逻辑关系&#xff1a; 研究对象二&#xff0c;数据的存储结构&#xff08;或物理结构&#xff09;&#xff1a; 研究对象三&#xff1a;运算结构 数据结构的相关介绍&#xff…

C++初阶--类型模板

文章目录 泛型编程函数模板使用通用加法函数多模板参数必须用实例化 函数模板的原理类模板使用 注意事项 泛型编程 先看一个例子&#xff1a; 这是一些对于Swap重载的函数&#xff0c;区别是类型不同&#xff1b; 虽然能够重载使用&#xff0c;但代码复用率比较低&#xff0c…