Descriptor
且任容枯 Lv4

描述器是什么

  • 描述器让对象自定义属性的查找、存储、删除。
  • A descriptor is what we call any object that defines get(), set(), or delete()
  • 描述器可以具有 set_name()方法。这只用于描述器需要知道创建它的类或它被分配到的类变量的名称的情况。
  • 描述器在属性查找期间由点运算器调用。如果使用 vars(some_class)[description_name]间接访问描述器,则返回描述器实例而不调用它。
  • 描述符只在用作类变量时有效。当放入实例时,它们没有任何效果。
  • 描述符的主要动机是提供一个钩子,允许存储在类变量中的对象控制在属性查找期间发生的事情
  • 描述符在整个语言中使用。函数就是这样转化为绑定方法的。类似 classmethod ()、 staticmethod ()、 property ()和 function tools.cached _ property ()这样的常用工具都是作为描述符实现的。

Example

1
2
3
4
5
6
7
8
9
10
11
12
class Ten:
def __get__(self, obj, objtype=None):
return 10


class A:
x = 5
y = Ten()


print(A().x)
print(A().y)

Ten 就是一个描述器,使用时要将其作为一个类变量存储在另一个类里
注意,值10既不存储在类字典中,也不存储在实例字典中。相反,值10是根据需要计算的。

howtouse

动态查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import os

class DirectorySize:

def __get__(self, obj, objtype=None):
return len(os.listdir(obj.dirname))

class Directory:

size = DirectorySize()

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

s = Directory('songs')
g = Directory('games')

print(s.size) # 3
print(g.size) # 3

os.remove('games/chess')
print(g.size) # 2

管理属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import logging

logging.basicConfig(level=logging.INFO)

class LoggedAgeAccess:

def __get__(self, obj, objtype=None):
value = obj._age # 加_防止递归调用
logging.info('Accessing %r giving %r', 'age', value)
return value

def __set__(self, obj, value):
logging.info('Updating %r to %r', 'age', value)
obj._age = value

class Person:

age = LoggedAgeAccess() # Descriptor instance

def __init__(self, name, age):
self.name = name # Regular instance attribute
self.age = age # Calls __set__()

def birthday(self):
self.age += 1 # Calls both __get__() and __set__()


mary = Person('Mary', 30)

david = Person('David', 40)

加_防止递归调用

自定义names

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import logging

logging.basicConfig(level=logging.INFO)

class LoggedAccess:

def __set_name__(self, owner, name):
self.public_name = name
self.private_name = '_' + name

def __get__(self, obj, objtype=None):
value = getattr(obj, self.private_name)
logging.info('Accessing %r giving %r', self.public_name, value)
return value

def __set__(self, obj, value):
logging.info('Updating %r to %r', self.public_name, value)
setattr(obj, self.private_name, value)

class Person:

name = LoggedAccess() # First descriptor instance
age = LoggedAccess() # Second descriptor instance

def __init__(self, name, age):
self.name = name # Calls the first descriptor
self.age = age # Calls the second descriptor

def birthday(self):
self.age += 1

完整的例子

验证器

验证器是托管属性访问的描述符。在存储任何数据之前,它验证新值是否满足各种类型和范围限制。如果这些限制没有得到满足,它将引发一个异常,以防止数据源中的数据损坏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from abc import ABC, abstractmethod

class Validator(ABC):

def __set_name__(self, owner, name):
self.private_name = '_' + name

def __get__(self, obj, objtype=None):
return getattr(obj, self.private_name)

def __set__(self, obj, value):
self.validate(value)
setattr(obj, self.private_name, value)

@abstractmethod
def validate(self, value):
pass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class OneOf(Validator):

def __init__(self, *options):
self.options = set(options)

def validate(self, value):
if value not in self.options:
raise ValueError(f'Expected {value!r} to be one of {self.options!r}')

class Number(Validator):

def __init__(self, minvalue=None, maxvalue=None):
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, predicate=None):
self.minsize = minsize
self.maxsize = maxsize
self.predicate = predicate

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}'
)
if self.predicate is not None and not self.predicate(value):
raise ValueError(
f'Expected {self.predicate} to be true for {value!r}'
)
1
2
3
4
5
6
7
8
9
10
class Component:

name = String(minsize=3, maxsize=10, predicate=str.isupper)
kind = OneOf('wood', 'metal', 'plastic')
quantity = Number(minvalue=0)

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