Jusene's Blog

python 描述器

字数统计: 1.3k阅读时长: 6 min
2018/06/17 Share

描述器

所谓描述器,即实现了描述符协议,即对一个类变量实现get, set, 和 delete方法的对象。

简单例子:

1
class Int:
2
    def __init__(self, name):
3
        self.name = name
4
5
    def __get__(self, instance, cls):
6
        print('get {}'.format(self.name))
7
        if instance is not None:
8
            return instance.__dict__[self.name]
9
        return self
10
11
    def __set__(self, instance, value):
12
        instance.__dict__[self.name] = value
13
14
class A:
15
    val = Int('val')
16
17
    def __init__(self):
18
        self.val = 3
19
20
a = A()
21
a.val          # 在获取类变量的时候是通过上述的定义的描述器Int获得的
22
get val
23
3
24
a.__dict__
25
{'val': 3}
26
27
class Int:
28
    def __init__(self, name):
29
        self.name = name
30
        self.data = {}
31
32
    def __get__(self, instance, cls):
33
        print('get {}'.format(self.name))
34
        if instance is not None:
35
            return self.data[instance]
36
        return self
37
38
    def __set__(self, instance, value):
39
        self.data[instance] = value
40
41
class A:
42
    val = Int('val')
43
44
    def __init__(self):
45
        self.val = 3
46
47
a = A()
48
a.val   ## 当一个类变量 实现了__get__和__set__方法之后,访问这个类变量会调用__get__方法,对这个变量赋值会调用__set__方法  这种类变量叫做描述器
49
get val
50
3
51
a.__dict__
52
{}

描述器是一个类,实现了__get__,__set__,__delete__中1个或多个方法
描述器事实上是一种代理机制
当一个类变量被定义为描述器,对这个类的操作,将由此描述器来代理

  • 访问: __get__
  • 赋值: __set__
  • 删除: __delete__

setdelete方法的描述器会提升优先级到dict之前

  • __get__(self, instance, cls) instance 表示当前实例 cls表示类本身 使用类访问的时候 instance为None

    1
    class Desc:
    2
        def __get__(self, instance, cls):
    3
            print(instance)
    4
            print(cls)
    5
    6
    class A:
    7
        x = Desc()
    8
    9
    A().x
    10
    <__main__.A object at 0x10561cb70>  # A类的实例
    11
    <class '__main__.A'>                # A类对象
    12
    13
    A.x
    14
    None
    15
    <class '__main__.A'>
  • __set__(self, instance, value) instance 表示当前实例 value为右值 只有实例才会调用set

    1
    class Desc:
    2
        def __get__(self, instance, cls):
    3
            print(instance)
    4
            print(cls)
    5
    6
        def __set__(self, instance, value):
    7
            print(instance)
    8
            print(value)
    9
    10
    class A:
    11
        x = Desc()
    12
    13
    A().x = 5
    14
    <__main__.A object at 0x10561cc50>  # A类实例
    15
    5
    16
    17
    A.x = 5
  • __delete__(self, instance) instance 表示当前实例

    1
    class Desc:
    2
        def __get__(self, instance, cls):
    3
            print(instance)
    4
            print(cls)
    5
    6
        def __set__(self, instance, value):
    7
            print(instance)
    8
            print(value)
    9
    10
        def __delete__(self, instance):
    11
            print(instance)
    12
    class A:
    13
        x = Desc()
    14
    15
    del A().x
    16
    <__main__.A object at 0x1055fed30>  # A类实例

描述器主要针对的是类变量,这个需要记得,可以针对类变量进行多重操作。

描述器的应用

  • classmethod的实现
1
from functools import partial
2
from functools import wraps
3
4
class Classmethod:
5
    def __init__(self, fn):
6
        self.fn = fn
7
    
8
    def __get__(self, instance, cls):
9
        return wraps(self.fn)(partial(self.fn, cls))  #classmethod最主要的即固定下第一个参数也类对象,所以需要固定下第一参数
10
11
class A:
12
    NAME = 'jusene'
13
    @Classmethod
14
    def cls_method(cls):  # 这里结合装饰器的理解
15
        print(cls)
16
        print(cls.NAME)
17
18
A.cls_method()            # A.class_method.__get__(None,A)
19
<class '__main__.A'>
20
jusene
21
A().cls_method()          # A.class_method.__get__(A.instance,A)
22
<class '__main__.A'>
23
jusene
  • staticmethod的实现
1
class Staticmethod:
2
    def __init__(self, fn):
3
        self.fn = fn
4
5
    def __get__(self, instance, cls): # staticmethod 忽略类对象和实例,所以这里只要返回自己的函数即可
6
        return self.fn
7
8
class A:
9
    @Staticmethod
10
    def static_method():
11
        print('haha')
12
13
A.static_method()
14
haha
15
A().static_method()
16
haha
  • property
1
class Property:
2
    def __init__(self, fget, fset=None, fdel=None):
3
        self.fget = fget
4
        self.fset = fset
5
        self.fdel = fdel
6
    def __get__(self, instance, cls):
7
        if instance is not None:
8
            return self.fget(instance)
9
        return self
10
    def __set__(self, instance, value):
11
        if callable(self.fset):
12
            self.fset(instance, value)
13
        else:
14
            raise AttributeError('{} can not assigneable'.format(self.fget.__name__))
15
    def __delete__(self, instance):
16
        if callable(self.fdel):
17
            self.fdel(instance)
18
        else:
19
            raise AttributeError('{} can not deleteable'.format(self.fget.__name__))
20
    def setter(self, fn):
21
        self.fset = fn
22
        return self
23
    def deletter(self, fn):
24
        self.fdel = fn
25
        return self
26
27
class A:
28
    def __init__(self):
29
        self.__x = 1
30
    @Property            # Property(x)
31
    def x(self):
32
        return self.__x
33
    @x.setter            # x=Property(x)
34
    def x(self, value):
35
        self.__x = value
36
    @x.deletter
37
    def x(self):
38
        print('can not delete')
39
40
a=A()
41
a.x
42
1
43
a.x = 3
44
a.x
45
3
46
del a.x
47
can not delete

描述器的使用场景

描述器的使用场景:用于接管对实例变量的操作

类型检测:

1
import inspect
2
3
class Person:
4
    def __init__(self, name: str, age: int):
5
        self.name = name
6
        self.age = age
7
8
p = Person(25,'jusene')   # 无法针对类型注解约束
9
10
sig = inspect.signature(Person)
11
param = list(sig.parameters.items())[0][1]
12
param.annotation
13
str
14
15
16
class Typed:
17
    def __init__(self, name, type):
18
        self.name = name
19
        self.type = type
20
21
    def __get__(self, instance, cls):
22
        if instance is not None:
23
            return instance.__dict__(self.name)
24
        return self
25
26
    def __set__(self, instance, value):
27
        if not isinstance(value, self.type):
28
            raise TypeError()
29
        instance.__dict__[self.name] = value
30
31
class Person:
32
    name = Typed('name', str)
33
    age = Typed('age', int)
34
35
    def __init__(self, name: str, age: int):
36
        self.name = name
37
        self.age = age
38
39
p = Person(25, 'instance')
40
TypeError  
41
p = Person('instance', 25)
42
43
44
def typeassert(cls):
45
    params = inspect.signature(cls).parameters
46
    for name, param in params.items():
47
        if param.annotation != inspect._empty:
48
            setattr(cls, name, Typed(name, param.annotation))
49
    return cls
50
51
@typeassert
52
class Person:
53
    def __init__(self, name: str, age: int, desc):
54
        self.name = name
55
        self.age = age
56
        self.desc = desc
1
import datetime
2
from functools import wraps
3
from types import MethodType
4
5
class Timeit:
6
    def __init__(self, fn):
7
        self.fn = fn
8
9
    def __get__(self, instance, cls):
10
        @wraps(self.fn)
11
        def wrap(*args, **kwargs):
12
            start = datetime.datetime.now()
13
            ret = self.fn(*args, **kwargs)
14
            cost = datetime.datetime.now() - start
15
            instance.send(cost)
16
            return ret
17
18
        if instance is not None:
19
            return MethodType(wrap, instance)
20
        return self
21
22
class Sender:
23
    def send(self, cost):
24
        print(cost)
25
26
    @Timeit
27
    def other(self):
28
        pass
29
30
s = Sender()
31
s.other()
CATALOG
  1. 1. 描述器
  2. 2. 描述器的应用
  3. 3. 描述器的使用场景