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__
被调用,参数obj
是Person
实例,type
是type(Person)
- 当调用
Person.age
时,__get__
被调用,参数obj
是None
,type
是type(Person)
- 当调用
p1.age = 25
时,__set__
被调用,参数obj
是Person
实例,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