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实现猜数字游戏(无重复数字)示例分享
Mar 29 Python
跟老齐学Python之??碌某?? target=
Sep 12 Python
开始着手第一个Django项目
Jul 15 Python
20招让你的Python飞起来!
Sep 27 Python
Python操作使用MySQL数据库的实例代码
May 25 Python
Django在win10下的安装并创建工程
Nov 20 Python
CentOS7.3编译安装Python3.6.2的方法
Jan 22 Python
Scrapy基于selenium结合爬取淘宝的实例讲解
Jun 13 Python
pygame游戏之旅 添加碰撞效果的方法
Nov 20 Python
python 2.7 检测一个网页是否能正常访问的方法
Dec 26 Python
Python制作简易版小工具之计算天数的实现思路
Feb 13 Python
利用python对mysql表做全局模糊搜索并分页实例
Jul 12 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
Symfony生成二维码的方法
2016/02/04 PHP
phalcon框架使用指南
2016/02/23 PHP
关于PHP转换超过2038年日期出错的问题解决
2017/06/28 PHP
JS面向对象编程 for Cookie
2010/09/19 Javascript
jquery获取特定name所有选中的checkbox,支持IE9标准模式
2013/03/18 Javascript
深入剖析JavaScript中的枚举功能
2014/03/06 Javascript
jQuery实现根据类型自动显示和隐藏表单
2015/03/18 Javascript
Node.js connect ECONNREFUSED错误解决办法
2016/09/15 Javascript
jQuery中animate的几种用法与注意事项
2016/12/12 Javascript
jquery单击文字或图片内容放大并居中显示
2017/06/23 jQuery
vue.js组件vue-waterfall-easy实现瀑布流效果
2017/08/22 Javascript
react中使用swiper的具体方法
2018/05/15 Javascript
在vue2.0中引用element-ui组件库的方法
2018/06/21 Javascript
微信小程序日历组件使用方法详解
2018/12/29 Javascript
微信小程序使用setData修改数组中单个对象的方法分析
2018/12/30 Javascript
layui 实现table翻页滚动条位置保持不变的例子
2019/09/05 Javascript
JS实现横向轮播图(初级版)
2020/06/24 Javascript
python的多重继承的理解
2017/08/06 Python
Python OOP类中的几种函数或方法总结
2019/02/22 Python
python3.5安装python3-tk详解
2019/04/26 Python
python3.6+selenium实现操作Frame中的页面元素
2019/07/16 Python
python实现大量图片重命名
2020/03/23 Python
Python ArgumentParse的subparser用法说明
2020/04/20 Python
基于python实现简单C/S模式代码实例
2020/09/14 Python
详解java调用python的几种用法(看这篇就够了)
2020/12/10 Python
python实现双人五子棋(终端版)
2020/12/30 Python
基于Pytorch版yolov5的滑块验证码破解思路详解
2021/02/25 Python
JD Sports丹麦:英国领先的运动时尚零售商
2020/11/24 全球购物
个人求职简历的自我评价
2013/10/19 职场文书
个人简历的自荐信
2013/10/23 职场文书
新年抽奖获奖感言
2014/03/02 职场文书
小学教师自我鉴定范文
2014/03/20 职场文书
争做文明公民倡议书
2014/08/29 职场文书
学习朴航瑛老师爱岗敬业先进事迹思想汇报
2014/09/17 职场文书
地陪导游欢迎词
2015/01/26 职场文书
本溪水洞导游词
2015/02/11 职场文书