首页 > 编程语言 >Python描述符

Python描述符

时间:2023-12-25 17:15:44浏览次数:29  
标签:__ name Python self instance 描述符 def

1、描述符定义

先看下描述符的定义。

如果在一个新式类中至少实现了 __ get__ (),__ set __ (), __ delete __ ()中的一个,则称作这个的新式类为描述符,也称为描述符协议。

  • __ get __ ():调用一个属性时触发;
  • __ set __():一个属性赋值时触发;
  • __ delete __ ():采用del删除属性时触发;

定义一个描述符:

class Foo:  # 在python3中Foo是新式类,它实现了__get__(),__set__(),__delete__()中的一个三种方法的一个,这个类就被称作一个描述符
    def __get__(self, instance, owner):
        pass
        print("__get__() is called")

    def __set__(self, instance, value):
        pass

    def __delete__(self, instance):
        pass
    

2、描述符作用

使用描述符能够干什么?

描述符的作用是用来代理另外一个类的属性的,必须把描述符定义成这个类的类属性,不能定义到构造函数中

class Foo:
    def __get__(self, instance, owner):
        print("__get__() is called")

    def __set__(self, instance, value):
        print("__set__() is called")

    def __delete__(self, instance):
        print("__delete__() is called")


f = Foo()

上面的代码定义了一个描述符,由这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法。

f.name = "jack"
f.name
del f.name
# 并不会触发Foo类中的任何方法

什么时候会触发这三个方法的执行呢?看下面的示例:

class Str:
    """描述符Str"""

    def __get__(self, instance, owner):
        print('Str调用')

    def __set__(self, instance, value):
        print('Str设置...')

    def __delete__(self, instance):
        print('Str删除...')


class People:
    name = Str() # 必须定义在类属性中

    def __init__(self, name):  # name被Str类代理
        self.name = name

p = People("alex") # 打印出 “Str设置...”,即调用了Str类中的__set__方法

p.name # 打印 “Str调用”
p.name = "jack" # 打印 “Str设置...”
del p.name # 打印 “Str删除...”

从上面示例可以看出,只有当描述符被定义成另一个类的类属性时,并且不能定义到另一个类中的构造方法中

3、两种描述符

  • 数据描述符

至少实现了__ get __ () 和 __ set__ ()

class Foo:
    def __set__(self, instance, value):
        print('set')

    def __get__(self, instance, owner):
        print('get')
  • 非数据描述符

没有实现 __ set __()

class Foo:
    def __get__(self, instance, owner):
        print('get')

4、描述符注意事项

  1. 描述符本身应该定义成新式类,被代理的类也应该是新式类;
  2. 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
  3. 要严格遵循该优先级,优先级由高到底分别是
    • 类属性
    • 数据描述符
    • 实例属性
    • 非数据描述符
    • 找不到的属性触发 __ getattr __ ()

5、使用描述符

class Str:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        print('get--->', instance, owner)
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print('set--->', instance, value)
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        print('delete--->', instance)
        instance.__dict__.pop(self.name)


class People:
    name = Str('name')

    def __init__(self, name, age, salary):
        self.name = name
        self.age = age
        self.salary = salary


# 疑问:如果我用类名去操作属性呢
try:
    People.name  # 报错,错误的根源在于类去操作属性时,会把None传给instance
except Exception as e:
    print(e)

输出:

get---> None <class '__main__.People'>
'NoneType' object has no attribute '__dict__'

修改代码,解决上述错误

class Str:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        print('get--->', instance, owner)
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print('set--->', instance, value)
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        print('delete--->', instance)
        instance.__dict__.pop(self.name)


class People:
    name = Str('name')

    def __init__(self, name, age, salary):
        self.name = name
        self.age = age
        self.salary = salary


print(People.name)  # 完美,解决

输出:

get---> None <class '__main__.People'>
<__main__.Str object at 0x107a86da0>

6、自定制@property

利用描述符原理完成一个自定制@property,实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)

  1. property回顾
    class Room:
        def __init__(self, name, width, length):
            self.name = name
            self.width = width
            self.length = length
    
        @property
        def area(self):
            return self.width * self.length
    
    
    r1 = Room('alex', 1, 1)
    
    print(r1.area) # 调用area方法
    #输出
    1
    
  2. 自定制property
    class Lazyproperty:
        def __init__(self, func):
            self.func = func
    
        def __get__(self, instance, owner):
            print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()')
            if instance is None:
                return self
            return self.func(instance)  # 此时你应该明白,到底是谁在为你做自动传递self的事情
    
    
    class Room:
        def __init__(self, name, width, length):
            self.name = name
            self.width = width
            self.length = length
    
        @Lazyproperty  # area=Lazyproperty(area) 相当于定义了一个类属性,即描述符
        def area(self): # 此时的self参数就是描述符中的instance
            return self.width * self.length
    
    
    r1 = Room('alex', 1, 1)
    
    print(r1.area)
    # 输出
    这是我们自己定制的静态属性,r1.area实际是要执行r1.area()
    1
    
  3. 实现延迟计算功能
    class Lazyproperty:
        def __init__(self, func):
            self.func = func
    
        def __get__(self, instance, owner):
            print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()')
            if instance is None:
                return self
            else:
                print('--->')
                value = self.func(instance)
                setattr(instance, self.func.__name__, value)  # 计算一次就缓存到实例的属性字典中
                return value
    
    
    class Room:
        def __init__(self, name, width, length):
            self.name = name
            self.width = width
            self.length = length
    
        @Lazyproperty  # area=Lazyproperty(area) 相当于'定义了一个类属性,即描述符'
        def area(self):
            return self.width * self.length
    
    
    r1 = Room('alex', 1, 1)
    
    print(r1.area)  # 先从自己的属性字典找,没有再去类的中找,然后出发了area的__get__方法
    
    
    #这是我们自己定制的静态属性,r1.area实际是要执行r1.area()
    --->
    1
    
    print(r1.area)  # 先从自己的属性字典找,找到了,是上次计算的结果,这样就不用每执行一次都去计算
    1
    

    此结果的依据是因为上述描述符属于非数据描述符,而获取属性的优先级顺序是,实例属性 > 非数据描述符;

  4. 打破延时计算

    一个小小的改动,延时计算的的美梦就破碎了!

    class Lazyproperty:
        def __init__(self, func):
            self.func = func
    
        def __get__(self, instance, owner):
            print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()')
            if instance is None:
                return self
            else:
                value = self.func(instance)
                instance.__dict__[self.func.__name__] = value
                return value
            # return self.func(instance) # 此时你应该明白,到底是谁在为你做自动传递self的事情
        def __set__(self, instance, value):
            print('hahahahahah')
    
    
    class Room:
        def __init__(self, name, width, length):
            self.name = name
            self.width = width
            self.length = length
    
        @Lazyproperty  # area=Lazyproperty(area) 相当于定义了一个类属性,即描述符
        def area(self):
            return self.width * self.length
    
    print(r1.area)
    print(r1.area)
    print(r1.area)
    #输出
    这是我们自己定制的静态属性,r1.area实际是要执行r1.area()
    1
    这是我们自己定制的静态属性,r1.area实际是要执行r1.area()
    1
    这是我们自己定制的静态属性,r1.area实际是要执行r1.area()
    1
    

    此时再调用r1.area时,每次都会访问描述符,缓存功能失效。原因就是上述的描述符实现了 __ set __()方法,它由非数据描述符变成了数据描述符,数据描述符比实例属性有更高的优先级,因而所有的属性操作都去找描述符了

7、描述符总结

描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethod,@property甚至是__slots__属性

描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件

原文出自:https://blog.csdn.net/qdPython/article/details/129203306

标签:__,name,Python,self,instance,描述符,def
From: https://www.cnblogs.com/zhangchangchang/p/17926496.html

相关文章

  • Python 爬虫在数据分析方面有什么潜力
    在当今信息爆炸的时代,大量的数据被生成和存储,这给企业、学术界和个人提供了巨大的机会和挑战。爬虫作为一种数据获取的技术手段,可以帮助我们从互联网上获取大量的数据。结合数据分析技术,爬虫在数据分析方面具有巨大的潜力。本文将介绍一些爬虫在数据分析方面的潜力和应用场景。1.获......
  • python的任何题目开头加上一句class的语句就是面向对象程序设计吗
    Python的任何题目开头加上一句class的语句并不意味着是面向对象程序设计(Object-OrientedProgramming,OOP)。面向对象程序设计是一种编程范式,它将程序组织为对象的集合,每个对象都有自己的状态和行为,并且可以与其他对象进行交互。在Python中,使用class关键字可以定义类,类是对象的蓝图,描......
  • 如何将 python 升级
    要升级Python,您可以采取以下步骤:备份数据:在进行升级之前,建议备份您的Python项目和数据以防万一。选择Python版本:决定要升级到的Python版本。访问Python官方网站,查看可用的稳定版本。下载新版本:使用以下命令下载并安装您选择的Python版本。请替换x.y.z为实际的版本号。......
  • python Django项目在jenkins中部署
    1.在jenkins中创建一个自由风格的job: 配置git源代码仓库:构建机制的配置: 配置执行shell脚本: ps-ef|grep0.0.0.0:8088|grep-vgrep|awk'{print$2}'|xargskill-9echo"=============Finishtokillreleasewikirealprocess=================="BUIL......
  • Python - pandas 报错:ValueError: 'HIS_批准文号' is both an index level and a colu
    问题描述file:[Terminal]ValueError:'HIS_批准文号'isbothanindexlevelandacolumnlabel,whichisambiguous.ValueError:cannotinsert招采_批准文号,alreadyexists有这两个错误,使用函数merge合并的时候出现第一个错误,将两个DataFrame的索引reset_index......
  • Windows环境 CMake 配置C++调用Python
    #CMakeLists.txtadd_library(python3STATICIMPORTED)#这里是使用python的安装路径set_target_properties(python3PROPERTIESIMPORTED_LOCATION"D:/python/libs/python39.lib")#使用python的静态库target_link_libraries(TestDemo......
  • Python——第五章:csv模块
    未来我们会使用爬虫获取到一些json文件,例如去英雄联盟官方爬取英雄的数据库查看代码{"hero":[{"heroId":"1","name":"\u9ed1\u6697\u4e4b\u5973","alias":"Annie","title":"\u5b89\u59ae","roles"......
  • python3 多线程ping当前网段主机是否存活
    1.python3多线程#主线程只负责生成工作线程#工作线程只做具体的工作#多线程共享进程里的内存块#多进程不共享importthreadingdefHello(world,tedu):print('Hello%s%s!'%(world,tedu))if__name__=='__main__':foriinrange(3):th......
  • 【Mathematical Model】Python拟合一元一/二次方程(线性回归)
    ​        Python中可以使用多种库进行拟合方程,其中最常用的是NumPy和SciPy。NumPy是一个用于处理数组和矩阵的库,而SciPy则提供了大量的科学计算函数,包括拟合算法。1一元一次方程拟合    需要注意的是我们这里的方程需要我们自己定义好,然后再通过curve_fit......
  • python使用Gemini API
    谷歌免费开放了Gemini(https://ai.google.dev)的API,每分钟可发出60个请求(RPM)。这样我们除了免费体验Bard:https://bard.google.com/外,还可以写程序来调用。安装依赖pipinstall-q-Ugoogle-generativeai-q或--quiet:这个参数用于减少安装过程中输出的信息量。通常,pip......