18.python-描述器

pyhton 描述器

在 Python 中,描述器是一种特殊的对象,它允许您控制属性的访问方式。描述器通常与类属性一起使用,以便在访问属性时执行自定义的行为。这种类是当做工具使用的,不单独使用

类属性和描述器

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

描述器必须实现至少一个以下方法:

__get__(self, instance, owner): 这个方法定义了获取属性值的方式。当您访问属性时,Python 将自动调用 __get__() 方法,并将属性所属的对象和类作为参数传递给它。__get__() 方法应该返回属性的值。

__set__(self, instance, value): 这个方法定义了设置属性值的方式。当您设置属性时,Python 将自动调用 __set__() 方法,并将属性所属的对象、属性的新值和类作为参数传递给它。__set__() 方法不需要返回任何值。

__delete__(self, instance): 这个方法定义了删除属性的方式。当您使用 del 关键字删除属性时,Python 将自动调用 __delete__() 方法,并将属性所属的对象作为参数传递给它。__delete__() 方法不需要返回任何值。

  • 仅实现了__get__,就是 非数据描述器 non-data descriptor

  • 实现了__get____set__,就是 数据描述器 data descriptor

  • 如果一个类的类属性设置为描述器实例,那么它就称为owner属主

  • 当该类的类属性被查找、设置和删除时,就会调用描述器相应的方法

  • 属性查找顺序:数据描述器 优先于 实例的__dict__ 优先于 非数据描述器

Note:

必须把描述符定义成这个类的类属性,不能为定义到构造函数中 
要严格遵循该优先级, 优先级由高到底分别是

1. 类属性
2. 数据描述符
3. 实例属性
4. 非数据描述符
5. 找不到的属性触发__getattr__()

but 当我们使用实例来调用时,如果这时候同时存在与实例属性同名的类属性或函数,实例属性的优先级大于一切
class Descriptor:

    def __init__(self, value=20):
        self.value = value

    def __get__(self, obj, type=None):
        print('call __get__: obj: %s type: %s' % (obj, type))
        return self.value

    def __set__(self, obj, value):
        if value <= 0:
            raise ValueError("age must be greater than 0")
        print('call __set__: obj: %s value: %s' % (obj, value))
        self.value = value

class Person:

    age = Descriptor()

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

p1 = Person('zhangsan')
print(p1.age)
# call __get__: obj: <__main__.Person object at 0x1055509e8> type: <class '__main__.Person'>
# 20

print(Person.age)
# call __get__: obj: None type: <class '__main__.Person'>
# 20

p1.age = 25
# call __set__: obj: <__main__.Person object at 0x1055509e8> value: 25

print(p1.age)
# call __get__: obj: <__main__.Person object at 0x1055509e8> type: <class '__main__.Person'>
# 25

p1.age = -1
# ValueError: age must be greater than 0

  • 当调用 p1.age 时,__get__ 被调用,参数 objPerson 实例,typetype(Person)
  • 当调用 Person.age 时,__get__ 被调用,参数 objNonetypetype(Person)
  • 当调用 p1.age = 25时,__set__ 被调用,参数 objPerson 实例,value 是25
  • 当调用 p1.age = -1时,__set__ 没有通过校验,抛出 ValueError

描述器的实际应用

数据校验

class Validator:

    def __init__(self):
        self.data = {}

    def __get__(self, obj, objtype=None):
        return self.data[obj]

    def __set__(self, obj, value):
        # 校验通过后再赋值
        self.validate(value)
        self.data[obj] = value

    def validate(self, value):
        pass


class Number(Validator):

    def __init__(self, minvalue=None, maxvalue=None):
        super(Number, self).__init__()
        self.minvalue = minvalue
        self.maxvalue = maxvalue

    def validate(self, value):
        if not isinstance(value, (int, float)):
            raise TypeError(f'Expected {value!r} to be an int or float')
        if self.minvalue is not None and value < self.minvalue:
            raise ValueError(
                f'Expected {value!r} to be at least {self.minvalue!r}'
            )
        if self.maxvalue is not None and value > self.maxvalue:
            raise ValueError(
                f'Expected {value!r} to be no more than {self.maxvalue!r}'
            )


class String(Validator):

    def __init__(self, minsize=None, maxsize=None):
        super(String, self).__init__()
        self.minsize = minsize
        self.maxsize = maxsize

    def validate(self, value):
        if not isinstance(value, str):
            raise TypeError(f'Expected {value!r} to be an str')
        if self.minsize is not None and len(value) < self.minsize:
            raise ValueError(
                f'Expected {value!r} to be no smaller than {self.minsize!r}'
            )
        if self.maxsize is not None and len(value) > self.maxsize:
            raise ValueError(
                f'Expected {value!r} to be no bigger than {self.maxsize!r}'
            )


class Person:
    # 定义属性的校验规则 内部用描述符实现
    name = String(minsize=3, maxsize=10)
    age = Number(minvalue=1, maxvalue=120)

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


# 属性符合规则
p1 = Person('zhangsan', 20)
print(p1.name, p1.age)

# 属性不符合规则
p2 = Person('a', 20)
# ValueError: Expected 'a' to be no smaller than 3
p3 = Person('zhangsan', -1)
# ValueError: Expected -1 to be at least 1

实现类型检查


class Dtype:
    def __init__(self,key,expect_type):
        self.key = key
        self.expect_type = expect_type

    def __get__(self, instance, owner):
        return instance.__dict__[self.key]

    def __set__(self, instance, value):
        if not isinstance(value,self.expect_type):
            raise TypeError('%s传入的类型有误,应该为%s'%(self.key,self.expect_type))
        instance.__dict__[self.key] = value


class People:
    name = Dtype('name',str)
    age = Dtype('age',int)
    def __init__(self,name,age):
        self.name = name
        self.age = age
p0 = People('chris',18)
print(p0.__dict__)

p1 = People('chris','18')
# 执行结果
{'name': 'chris', 'age': 18}
TypeError: age传入的类型有误,应该为<class 'int'>


在上面的示例中,我们定义了一个 TypeCheck 描述器,它实现了类型检查。我们还定义了一个 MyClass 类,它具有一个名为 value 的属性,该属性使用 TypeCheck 描述器来控制属性的访问方式。

在 TypeCheck 描述器中,我们实现了 `__set__()` 方法来检查属性值是否为指定的数据类型。如果属性值不是指定的数据类型,则引发 TypeError 异常。

在 MyClass 类中,我们定义了一个名为 value 的属性,并使用 TypeCheck 描述器来控制属性的访问方式。我们使用 TypeCheck(int) 来指定属性的数据类型为整数。

在主程序中,我们创建一个 MyClass 对象,并演示如何使用类型检查。我们首先访问 value 属性,它将返回属性的值 10。接着,我们尝试设置 value 属性的值为字符串 "20",它将引发 TypeError 异常,因为 value 属性必须是整数类型。
# 类型检查的描述器

class Typed:
    # 变量名称,数据类型
    def __init__(self, name, expected_type):
        self.name = name
        self.expected_type = expected_type

    # 获取字典里的变量名称
    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.name]

    # 设置变量,如果不符合期望类型抛出异常
    def __set__(self, instance, value):
        if not isinstance(value, self.expected_type):
            raise TypeError('Expected ' + str(self.expected_type))
        instance.__dict__[self.name] = value

    # 删除变量
    def __delete__(self, instance):
        del instance.__dict__[self.name]


# Class decorator that applies it to selected attributes
# 类型检查的描述器
def typeassert(**kwargs):
    # {'name': <class 'str'>, 'shares': <class 'int'>, 'price': <class 'float'>}
    def decorate(cls):
        for name, expected_type in kwargs.items():
            # Attach a Typed descriptor to the class
            # 给这个类添加一个类型描述器
            setattr(cls, name, Typed(name, expected_type))
        return cls

    return decorate


# 描述器应用样例
@typeassert(name=str, age=int)
class People:
    def __init__(self, name, age):
        self.name = name
        self.age = age


people = People('xiaoxin', 18)
print(people.__dict__)
people = People('xiaoxin', 18.6)
print(people.__dict__)

# 执行结果
{'name': 'xiaoxin', 'age': 18}
    raise TypeError('Expected ' + str(self.expected_type))
TypeError: Expected <class 'int'>

参考资料

参考资料

https://blog.csdn.net/weixin_42142216/article/details/91546879

https://zhuanlan.zhihu.com/p/336926012

https://blog.51cto.com/u_8406447/5769239

posted @ 2023-07-06 22:03  贝壳里的星海  阅读(41)  评论(0编辑  收藏  举报