Blog icon indicating copy to clipboard operation
Blog copied to clipboard

描述器

Open codcodog opened this issue 8 years ago • 0 comments

描述器

描述器

描述器协议

  • __get__(self, instance, owner)
  • __set__(self, instance, value)
  • __delete__(self, instance)

这是所有的「描述器」的方法.
一个对象具有其中 任一 方法就会成为「描述器」.

如果一个对象同时定义了 __get__()__set__() 方法,则叫做「资料描述器」(data descriptor)

只定义了 __get__() 方法的「描述器」叫做「非资料描述器」(non-data descriptor)

应用

「描述器」正是属性,实例方法,静态方法,类方法和 super 的背后的实现机制.
「描述器」通常是那些使用到装饰器或元类的大型框架中的一个组件.

示例

class Integer:
    ''' 描述器:设置值的时候,必须是 int 类型
    '''
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        else:
            return instance.__dict__[slef.name]

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise TypeError('Expected an int')
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        del instance.__dict__[self.name]

class Point:
    x = Integer('x')
    y = Integer('y')

    def __init__(self, x, y):
        self.x = x
        self.y = y
In [5]: p = Point(2, 3)

In [6]: p.x
Out[6]: 2

In [7]: p.y = 5

In [8]: p.x = 2.3
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-8-0a3d61583921> in <module>()
----> 1 p.x = 2.3

<ipython-input-4-b8f8e8c000be> in __set__(self, instance, value)
     13     def __set__(self, instance, value):
     14         if not isinstance(value, int):
---> 15             raise TypeError('Expected an int')
     16         instance.__dict__[self.name] = value
     17 

TypeError: Expected an int

属性查找

对象属性查找

假设,d = Demo(),则 d.attr 的时候,会调用 __getattribute__()
如果类定义了 __getattr__(),那么在 __getattribute__() 抛出 AttributeError 的时候,就会调用 __getattr__()

对象属性的查找顺序:

  1. 查找 Demo 及其 基类__dict__ 是否存在属性 attr,如果存在并且该属性 attr 是「资料描述器」,则返回 Demo.__dict__['attr'].__get__(d, Demo).

    如果是在 Demo 的基类 Base 中查找到 attr 属性并且该属性是「资料描述器」,则返回 Base.__dict__['attr'].__get__(d, Demo).

  2. 查找 d.__dict__ 是否存在属性 attr,如果存在,则直接返回 d.__dict__['attr']

  3. 查找 Demo 及其 基类__dict__ 是否存在属性 attr,如果存在,是否是一个「非资料描述器」

    • 是,则返回 Demo.__dict__['attr'].__get__(d, Demo)
    • 否,则返回 Demo.__dict__['attr']

    如果是在 Demo 的基类查找到的,则返回相应的基类属性

  4. Demo__getattr__(),则调用 __getattr__()

  5. 抛出 AttributeError

示例
class D1:
    def __get__(self, ins, owner):
        print('D1资料描述器get, ins: %s, owner: %s' % (ins, owner))

    def __set__(self, ins, value):
        print('D1资料描述器set')

class D2:
    def __get__(self, ins, owner):
        print('非资料描述器get, ins: %s, owner: %s' % (ins, owner))

class Demo:
    x = D1()
    y = D2()
    z = 1
    n = 2

    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
In [21]: d = Demo(1, 2, 3) # 因为 x 是「资料描述器」,self.x = x 触发 D1.__set__()
D1资料描述器set

In [22]: d.x # Demo.x 是「资料描述器」,调用的是 Demo.__dict__['x'].__get__(d, Demo)
D1资料描述器get, ins: <__main__.Demo object at 0x7fa3d448a978>, owner: <class '__main__.Demo'>

In [23]: d.y # Demo.y 是「非资料描述器」,调用的是 d.__dict__['y']
Out[23]: 2

In [25]: d.z # Demo.z 不是描述器,调用的是 d.__dict__['z']
Out[25]: 3

In [28]: d.n
Out[28]: 2

类属性查找

类同样也是一种对象,类是「元类」(metaclass)的实例,因此类属性的查找顺序基本跟对象属性相同.

详细了解「元类」,参考:深刻理解Python中的元类

假设,BaseDemo 的一个「元类」

class Demo(object, metaclass=Base):
    pass

Demo.attr的时候,类属性的查找顺序:

  1. 查找 Base 及其 基类__dict__ 是否存在属性 attr,存在并且是「资料描述器」,则返回 Base.__dict__['attr'].__get__(Demo, Base)

  2. 查找 Demo.__dict__ 及其 基类__dict__ 是否存在 attr,若存在,是否是一个描述器(不管是「资料描述器」还是「非资料描述器」):

    • 是,则返回 Demo.__dict__['attr'].__get__(None, Demo)
    • 否,则返回 Demo.__dict__['attr']

    注意这里的 基类 是指 Demo 继承的类,同「元类」是完全不同的.

  3. 查找 Base 及其 基类__dict__ 是否存在 attr,如果存在,是否是一个「非资料描述器」

    • 是,则返回 Base.__dict__['attr'].__get__(Demo, Base)
    • 否,则返回 Base.__dict__['attr']
  4. Base__getattr__(),则 Base.__getattr__('attr')

  5. 抛出 AttributeError

示例
class D1:
    def __get__(self, ins, owner):
        print('D1资料描述器get, ins: %s, owner: %s' % (ins, owner))

    def __set__(self, ins, value):
        print('D1资料描述器set')

class D2:
    def __get__(self, ins, owner):
        print('非资料描述器get, ins: %s, owner: %s' % (ins, owner))

class D3:
    def __get__(self, ins, owner):
        print('D3资料描述器get, ins: %s, owner: %s' % (ins, owner))

    def __set__(self, ins, value):
        print('D3资料描述器set')

class Base(type):
    x = D1()
    y = D2()
    z = 0
    m = 1

    def __getattr__(self, *args, **kwargs):
        print('元类的 getattr')

class C:
    x = D3()
    y = 1
    z = 1

class Demo(C, metaclass=Base):
    z = 3
In [12]: Demo.x # 在元类 Base 查找到 x 并且是一个「资料描述器」,而不是在父类 C
D1资料描述器get, ins: <class '__main__.Demo'>, owner: <class '__main__.Base'>

In [13]: Demo.y # 在父类 C 找到 y,而元类 Base 的 y 只是一个「非资料描述器」
Out[13]: 1

In [14]: Demo.z # 在 Demo 类 找到 z,覆盖了父类 C 的 z,而元类 Base 的 z 不是描述器
Out[14]: 3

In [17]: Demo.m # 在元类 Base 找到 m
Out[17]: 1

In [19]: Demo.i # 找不到i,在元类 Base 的 __getattr__ 处理
元类的 getattr

codcodog avatar Jan 28 '18 10:01 codcodog