Python基础详解之描述符


Posted in Python onApril 28, 2021

一、描述符定义

描述符是一种类,我们把实现了__get__()、__set__()和__delete__()中的其中任意一种方法的类称之为描述符。

描述符的作用是用来代理一个类的属性,需要注意的是描述符不能定义在被使用类的构造函数中,只能定义为类的属性,它只属于类的,不属于实例,我们可以通过查看实例和类的字典来确认这一点。

描述符是实现大部分Python类特性中最底层的数据结构的实现手段,我们常使用的@classmethod、@staticmethd、@property、甚至是__slots__等属性都是通过描述符来实现的。它是很多高级库和框架的重要工具之一,是使用到装饰器或者元类的大型框架中的一个非常重要组件。

如下示例一个描述符及引用描述符类的代码:

class Descriptors:
 
    def __init__(self, key, value_type):
        self.key = key
        self.value_type = value_type
 
    def __get__(self, instance, owner):
        print("===> 执行Descriptors的 get")
        return instance.__dict__[self.key]
 
    def __set__(self, instance, value):
        print("===> 执行Descriptors的 set")
        if not isinstance(value, self.value_type):
            raise TypeError("参数%s必须为%s" % (self.key, self.value_type))
        instance.__dict__[self.key] = value
 
    def __delete__(self, instance):
        print("===>  执行Descriptors的delete")
        instance.__dict__.pop(self.key)
 
 
class Person:
    name = Descriptors("name", str)
    age = Descriptors("age", int)
 
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
 
person = Person("xu", 15)
print(person.__dict__)
person.name
person.name = "xu-1"
print(person.__dict__)
# 结果:
#     ===> 执行Descriptors的 set
#     ===> 执行Descriptors的 set
#     {'name': 'xu', 'age': 15}
#     ===> 执行Descriptors的 get
#     ===> 执行Descriptors的 set
#     {'name': 'xu-1', 'age': 15}

其中,Descriptors类就是一个描述符,Person是使用描述符的类。

类的__dict__属性是类的一个内置属性,类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类__dict__里。

在输出描述符的变量时,会调用描述符中的__get__方法,在设置描述符变量时,会调用描述符中的__set__方法。

二、描述符的种类和优先级

描述符分为数据描述符和非数据描述符。

至少实现了内置__set__()和__get__()方法的描述符称为数据描述符;实现了除__set__()以外的方法的描述符称为非数据描述符。

描述符的优先级的高低顺序:类属性 > 数据描述符 > 实例属性 > 非数据描述符 > 找不到的属性触发__getattr__()。

在上述“描述符定义”章节的例子中,实例person的属性优先级低于数据描述符Descriptors,所以在赋值或获取值过程中,均调用了描述符的方法。

class Descriptors:
    def __get__(self, instance, owner):
        print("===> 执行 Descriptors get")
 
    def __set__(self, instance, value):
        print("===> 执行 Descriptors set")
 
    def __delete__(self, instance):
        print("===> 执行 Descriptors delete")
 
 
class University:
    name = Descriptors()
 
    def __init__(self, name):
        self.name = name

 

类属性 > 数据描述符

# 类属性 > 数据描述符
# 在调用类属性时,原来字典中的数据描述法被覆盖为 XX-XX
print(University.__dict__)  # {..., 'name': <__main__.Descriptors object at 0x7ff8c0eda278>,}
 
University.name = "XX-XX"
print(University.__dict__)  # {..., 'name': 'XX-XX',}

数据描述符 > 实例属性

# 数据描述符 > 实例属性
# 调用时会现在实例里面找,找不到name属性,到类里面找,在类的字典里面找到 'name': <__main__.Descriptors object at 0x7fce16180a58>
# 初始化时调用 Descriptors 的 __set__; un.name 调用  __get__
print(University.__dict__)
un = University("xx-xx")
un.name
# 结果:
#     {..., 'name': <__main__.Descriptors object at 0x7ff8c0eda278>,}
#     ===> 执行 Descriptors set
#     ===> 执行 Descriptors get

下面是测试 实例属性、 非数据描述符、__getattr__ 使用代码

class Descriptors:
    def __get__(self, instance, owner):
        print("===>2 执行 Descriptors get")
 
 
class University:
    name = Descriptors()
 
    def __init__(self, name):
        self.name = name
 
    def __getattr__(self, item):
        print("---> 查找 item = {}".format(item))

实例属性 > 非数据描述符

# 实例属性 > 非数据描述符
# 在创建实例的时候,会在属性字典中添加 name,后面调用 un2.name 访问,都是访问实例字典中的 name
un2 = University("xu2")
print(un2.name)  # xu    并没有调用到  Descriptors 的 __get__
print(un2.__dict__)  # {'name': 'xu2'}
un2.name = "xu-2"
print(un2.__dict__)  # {'name': 'xu-2'}

非数据描述符 > 找不到的属性触发__getattr__

# 非数据描述符 > 找不到的属性触发__getattr__
# 找不到 name1 使用 __getattr__
un3 = University("xu3")
print(un3.name1)  # ---> 查找 item = name1

三、描述符的应用

使用描述符检验数据类型

class Typed:
    def __init__(self, key, type):
        self.key = key
        self.type = type
 
    def __get__(self, instance, owner):
        print("---> get 方法")
        # print("instance = {}, owner = {}".format(instance, owner))
        return instance.__dict__[self.key]
 
    def __set__(self, instance, value):
        print("---> set 方法")
        # print("instance = {}, value = {}".format(instance, value))
        if not isinstance(value, self.type):
            # print("设置name的值不是字符串: type = {}".format(type(value)))
            # return
            raise TypeError("设置{}的值不是{},当前传入数据的类型是{}".format(self.key, self.type, type(value)))
        instance.__dict__[self.key] = value
 
    def __delete__(self, instance):
        print("---> delete 方法")
        # print("instance = {}".format(instance))
        instance.__dict__.pop(self.key)
 
 
class Person:
    name = Typed("name", str)
    age = Typed("age", int)
 
    def __init__(self, name, age, salary):
        self.name = name
        self.age = age
        self.salary = salary
 
 
p1 = Person("xu", 99, 100.0)
print(p1.__dict__)
p1.name = "XU1"
print(p1.__dict__)
del p1.name
print(p1.__dict__)
# 结果:
#     ---> set 方法
#     ---> set 方法
#     {'name': 'xu', 'age': 99, 'salary': 100.0}
#     ---> set 方法
#     {'name': 'XU1', 'age': 99, 'salary': 100.0}
#     ---> delete 方法
#     {'age': 99, 'salary': 100.0}

四、描述符 + 类装饰器  (给 Person类添加类属性)

类装饰器,道理和函数装饰器一样

def Typed(**kwargs):
    def deco(obj):
        for k, v in kwargs.items():
            setattr(obj, k, v)
        return obj
    return deco
 
 
@Typed(x=1, y=2)  # 1、Typed(x=1, y=2) ==> deco   2、@deco ==> Foo = deco(Foo)
class Foo:
    pass
 
 
# 通过类装饰器给类添加属性
print(Foo.__dict__)  # {......, 'x': 1, 'y': 2}
print(Foo.x)

使用描述符和类装饰器给 Person类添加类属性

"""
描述符 + 类装饰器
"""
class Typed:
    def __init__(self, key, type):
        self.key = key
        self.type = type
 
    def __get__(self, instance, owner):
        print("---> get 方法")
        # print("instance = {}, owner = {}".format(instance, owner))
        return instance.__dict__[self.key]
 
    def __set__(self, instance, value):
        print("---> set 方法")
        # print("instance = {}, value = {}".format(instance, value))
        if not isinstance(value, self.type):
            # print("设置name的值不是字符串: type = {}".format(type(value)))
            # return
            raise TypeError("设置{}的值不是{},当前传入数据的类型是{}".format(self.key, self.type, type(value)))
        instance.__dict__[self.key] = value
 
    def __delete__(self, instance):
        print("---> delete 方法")
        # print("instance = {}".format(instance))
        instance.__dict__.pop(self.key)
 
 
def deco(**kwargs):
    def wrapper(obj):
        for k, v in kwargs.items():
            setattr(obj, k, Typed(k, v))
        return obj
    return wrapper
 
 
@deco(name=str, age=int)
class Person:
    # name = Typed("name", str)
    # age = Typed("age", int)
 
    def __init__(self, name, age, salary):
        self.name = name
        self.age = age
        self.salary = salary
 
 
p1 = Person("xu", 99, 100.0)
print(Person.__dict__)
print(p1.__dict__)
p1.name = "XU1"
print(p1.__dict__)
del p1.name
print(p1.__dict__)
# 结果:
#     ---> set 方法
#     ---> set 方法
#     {..., 'name': <__main__.Typed object at 0x7f3d79729dd8>, 'age': <__main__.Typed object at 0x7f3d79729e48>}
#     {'name': 'xu', 'age': 99, 'salary': 100.0}
#     ---> set 方法
#     {'name': 'XU1', 'age': 99, 'salary': 100.0}
#     ---> delete 方法
#     {'age': 99, 'salary': 100.0}

五、利用描述符自定义 @property

class Lazyproperty:
    def __init__(self, func):
        self.func = func
 
    def __get__(self, instance, owner):
        print("===> Lazypropertt.__get__ 参数: instance = {}, owner = {}".format(instance, owner))
        if instance is None:
            return self
        res = self.func(instance)
        setattr(instance, self.func.__name__, res)
        return self.func(instance)
 
    # def __set__(self, instance, value):
    #     pass
 
 
class Room:
 
    def __init__(self, name, width, height):
        self.name = name
        self.width = width
        self.height = height
 
    # @property  # area=property(area)
    @Lazyproperty  # # area=Lazyproperty(area)
    def area(self):
        return self.width * self.height
 
#  @property 测试代码
# r = Room("房间", 2, 3)
# print(Room.__dict__)  # {..., 'area': <property object at 0x7f8b42de5ea8>,}
# print(r.area)  # 6
 
# r2 = Room("房间2", 2, 3)
# print(r2.__dict__)  # {'name': '房间2', 'width': 2, 'height': 3}
# print(r2.area)
 
# print(Room.area)  # <__main__.Lazyproperty object at 0x7faabd589a58>
 
r3 = Room("房间3", 2, 3)
print(r3.area)
print(r3.area)
# 结果(只计算一次):
#     ===> Lazypropertt.__get__ 参数: instance = <__main__.Room object at 0x7fd98e3757b8>, owner = <class '__main__.Room'>
#     6
#     6

六、property 补充

class Foo:
 
    @property
    def A(self):
        print("===> get A")
 
    @A.setter
    def A(self, val):
        print("===> set A, val = {}".format(val))
 
    @A.deleter
    def A(self):
        print("===> del A")
 
 
f = Foo()
f.A         # ===> get A
f.A = "a"   # ===> set A, val = a
del f.A     # ===> del A
 
 
 
class Foo:
 
    def get_A(self):
        print("===> get_A")
 
    def set_A(self, val):
        print("===> set_A, val = {}".format(val))
 
    def del_A(self):
        print("===> del_A")
 
    A = property(get_A, set_A, del_A)
 
 
f = Foo()
f.A         # ===> get_A
f.A = "a"   # ===> set_A, val = a
del f.A     # ===> del_A

到此这篇关于Python基础详解之描述符的文章就介绍到这了,更多相关Python描述符内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
python实现堆栈与队列的方法
Jan 15 Python
python使用Image处理图片常用技巧分析
Jun 01 Python
Python常用内置模块之xml模块(详解)
May 23 Python
Python3.6中Twisted模块安装的问题与解决
Apr 15 Python
安装好Pycharm后如何配置Python解释器简易教程
Jun 28 Python
使用python实现哈希表、字典、集合操作
Dec 22 Python
Ranorex通过Python将报告发送到邮箱的方法
Jan 12 Python
django模型动态修改参数,增加 filter 字段的方式
Mar 16 Python
python opencv把一张图片嵌入(叠加)到另一张图片上的实现代码
Jun 11 Python
用Python爬取LOL所有的英雄信息以及英雄皮肤的示例代码
Jul 13 Python
利用python清除移动硬盘中的临时文件
Oct 28 Python
Python Matplotlib绘制等高线图与渐变色扇形图
Apr 14 Python
详解Python 3.10 中的新功能和变化
Apr 28 #Python
Python基础之数据结构详解
Apr 28 #Python
Python基础详解之邮件处理
python实现黄金分割法的示例代码
Apr 28 #Python
Python 流媒体播放器的实现(基于VLC)
tensorflow+k-means聚类简单实现猫狗图像分类的方法
python实现三阶魔方还原的示例代码
You might like
PHP数组操作汇总 php数组的使用技巧
2011/07/17 PHP
php在服务器执行exec命令失败的解决方法
2012/03/03 PHP
10款PHP开源商城系统汇总介绍
2015/07/23 PHP
初学prototype,发个JS接受URL参数的代码
2006/09/25 Javascript
Tips 带三角可关闭的文字提示
2010/10/06 Javascript
让你的博文自动带上缩址的实现代码,方便发到微博客上
2010/12/28 Javascript
js 可拖动列表实现代码
2011/12/13 Javascript
javascript数字格式化通用类 accounting.js使用
2012/08/24 Javascript
网站404页面3秒后跳到首页的实例代码
2013/08/16 Javascript
javascript字符串替换及字符串分割示例代码
2013/12/12 Javascript
jQuery绑定事件on()与弹窗的简要概述
2016/04/27 Javascript
node.js 抓取代理ip实例代码
2017/04/30 Javascript
jQuery插件select2利用ajax高效查询大数据列表(可搜索、可分页)
2017/05/19 jQuery
微信小程序promsie.all和promise顺序执行
2017/10/27 Javascript
vue-router配合ElementUI实现导航的实例
2018/02/11 Javascript
Vue仿支付宝支付功能
2018/05/25 Javascript
微信小程序下拉框组件使用方法详解
2018/12/28 Javascript
ES6 Iterator遍历器原理,应用场景及相关常用知识拓展详解
2020/02/15 Javascript
如何解决vue在ios微信&quot;复制链接&quot;功能问题
2020/03/26 Javascript
jQuery 图片查看器插件 Viewer.js用法简单示例
2020/04/04 jQuery
[01:06:12]VP vs NIP 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/17 DOTA
python 装饰器功能以及函数参数使用介绍
2012/01/27 Python
python实现socket端口重定向示例
2014/02/10 Python
深入解析Python中的urllib2模块
2015/11/13 Python
使用python绘制常用的图表
2016/08/27 Python
python数据类型_元组、字典常用操作方法(介绍)
2017/05/30 Python
python pandas实现excel转为html格式的方法
2018/10/23 Python
python笔记之mean()函数实现求取均值的功能代码
2019/07/05 Python
Django models文件模型变更错误解决
2020/05/11 Python
Python HTMLTestRunner测试报告view按钮失效解决方案
2020/05/25 Python
SQL中where和having的区别
2012/06/17 面试题
个人找工作自荐信格式
2013/09/21 职场文书
财务会计毕业生自荐信
2013/11/02 职场文书
社区健康教育实施方案
2014/03/18 职场文书
感恩小明星事迹材料
2014/05/23 职场文书
2014年副班长工作总结
2014/12/10 职场文书