一、描述符定义

描述符是一种类,我们把实现了__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描述符内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!