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动态性强类型用法实例
May 09 Python
Python实现批量读取word中表格信息的方法
Jul 30 Python
python基于隐马尔可夫模型实现中文拼音输入
Apr 01 Python
Python Tkinter实现简易计算器功能
Jan 30 Python
python如何为创建大量实例节省内存
Mar 20 Python
解决Pycharm无法import自己安装的第三方module问题
May 18 Python
pandas通过索引进行排序的示例
Nov 16 Python
pytorch 模拟关系拟合——回归实例
Jan 14 Python
Django中使用Json返回数据的实现方法
Jun 03 Python
python对 MySQL 数据库进行增删改查的脚本
Oct 22 Python
Python利用socket模块开发简单的端口扫描工具的实现
Jan 27 Python
opencv用VS2013调试时用Image Watch插件查看图片
Jul 26 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动态生成静态HTML网页的代码
2010/03/04 PHP
php中url传递中文字符,特殊危险字符的解决方法
2013/08/17 PHP
php+mysql实现的二级联动菜单效果详解
2016/05/10 PHP
jquery 插件 web2.0分格的分页脚本,可用于ajax无刷新分页
2008/12/25 Javascript
jQuery 1.5 源码解读 面向中高阶JSER
2011/04/05 Javascript
jquery中:input和input的区别分析
2011/07/13 Javascript
js日期、星座的级联显示代码
2014/01/23 Javascript
node.js中的console.error方法使用说明
2014/12/10 Javascript
基于jQuery下拉选择框插件支持单选多选功能代码
2016/06/07 Javascript
AngularJS基础 ng-mousemove 指令简单示例
2016/08/02 Javascript
jQuery实现点击某个div打开层,点击其他div关闭层实例分析(阻止冒泡)
2016/11/18 Javascript
jQuery焦点图左右转换效果
2016/12/12 Javascript
Vue.js搭建移动端购物车界面
2020/06/28 Javascript
分析JavaScript数组操作难点
2017/12/18 Javascript
layui多图上传实现删除功能的例子
2019/09/23 Javascript
element form 校验数组每一项实例代码
2019/10/10 Javascript
vue 动态组件用法示例小结
2020/03/06 Javascript
JavaScript this指向相关原理及实例解析
2020/07/10 Javascript
python多线程抓取天涯帖子内容示例
2014/04/03 Python
python实现动态数组的示例代码
2019/07/15 Python
Django框架模板用法入门教程
2019/11/04 Python
关于Pytorch MaxUnpool2d中size操作方式
2020/01/03 Python
Python Numpy,mask图像的生成详解
2020/02/19 Python
基于Python数据分析之pandas统计分析
2020/03/03 Python
实例代码讲解Python 线程池
2020/08/24 Python
全球知名巧克力品牌:Godiva
2016/07/22 全球购物
Famous Footwear加拿大:美国多品牌运动休闲鞋店
2018/12/05 全球购物
Timberland俄罗斯官方网上商店:全球领先的户外品牌
2020/03/15 全球购物
一套C++笔试题面试题
2012/06/06 面试题
一般党员对照检查材料
2014/09/24 职场文书
优秀教师单行材料
2014/12/16 职场文书
2015年学校党支部工作总结
2015/04/01 职场文书
童年读书笔记
2015/06/26 职场文书
《雪域豹影》读后感:父爱的伟大
2019/12/23 职场文书
Python实现查询剪贴板自动匹配信息的思路详解
2021/07/09 Python
Nginx配置使用详解
2022/07/07 Servers