社区导航

 

搜索
查看: 252|回复: 0

[分享] 聊聊Python中的描述符

[复制链接]

2

TA的帖子

0

TA的资源

一粒金砂(初级)

Rank: 1

发表于 2020-1-4 19:43 | 显示全部楼层 |阅读模式

描述符是实现描述符协议方法的Python对象,当将其作为其他对象的属性进行访问时,该描述符使您能够创建具有特殊行为的对象。

通常,描述符是具有“绑定行为”的对象属性,其属性访问已被描述符协议中的方法所覆盖。这些方法是__get __(),__set __()和__delete __()。如果为对象定义了这些方法中的任何一种,则称其为描述符。属性访问的默认行为是从对象的字典中获取,设置或删除属性。例如,a.x具有一个查找链,查找链从a .__ dict __ ['x']开始,然后键入(a).__ dict __ ['x'],并继续遍历类型(a)的基类(不包括元类)。如果查找到的值是定义描述符方法之一的对象,则Python可能会覆盖默认行为并改为调用描述符方法。优先链在何处发生取决于定义了哪些描述符方法。描述符是功能强大的通用协议。它们是属性,方法,静态方法,类方法和super()背后的机制。在Python本身中使用它们来实现2.2版中引入的新样式类。

1

2

3

descr.__get__(self, obj, type=None-> value

descr.__set__(self, obj, value) -None

descr.__delete__(self, obj) -None

定义这些方法中的任何一个,对象被视为描述符,并且在被视为属性时可以覆盖默认行为。

如果对象定义了__set __()或__delete __(),则将其视为数据描述符。仅定义__get __()的描述符称为非数据描述符(它们通常用于方法,但也可以用于其他用途)。数据和非数据描述符在实例字典中替代计算方式方面有所不同。如果实例的字典中的属性名称与数据描述符的名称相同,则以数据描述符为准。如果实例的字典中具有与非数据描述符同名的属性,则该字典属性优先。我们来看一下例子:

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

class lazy(object):

    def __init__(self, func):

        self.func = func

 

    def __get__(self, instance, owner):

        val = self.func(instance)

        setattr(instance, self.func.__name__, val)

        return val

 

class Circle(object):

    def __init__(self, radius):

        self.radius = radius

         

    @lazy

    def area(self):

        print('evalute')

        return 3.14 * self.radius ** 2

 

    def __getattr__(self, item):

        return 1

 

 

= Circle(4)

print(c.area)

print(c.area)

输出结果是

1

2

3

evalute

50.24

50.24

我们定义了一个描述符的类 lazy,它只实现了__get__方法,是一个非数据的描述符,我们用它定义了类Circle中的area方法,所以area方法成为了一个描述符的对象,可以看到,在第一次调用c.area的时候,执行了area的方法,打印了"evalute",在第二次的时间就直接输出了结果,没有指向area的方法,这是为什么呢?

那么重点来了,可以看到在lazy定义的__get__方法中,执行了被描述对象的方法,也就是这里的area函数,获取到结果之后,给当前的instance设置了一个同名的属性,并且设值为结果,这样下次在调用的时间,因为这是一个非数据的描述符,看上面的黑体字,实例的字典中的属性名称与数据描述符的名称相同,则以数据描述符为准。所以会取你刚刚设置的属性的值,不会再去取描述符的值。我们再来看看数据描述符的一个例子:

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

class lazy(object):

    def __init__(self, func):

        self.func = func

 

    def __get__(self, instance, owner):

        val = self.func(instance)

        setattr(instance, self.func.__name__, val)

        return val

 

    def __set__(self, instance, value):

        pass

 

 

class Circle(object):

    def __init__(self, radius):

        self.radius = radius

 

    @lazy

    def area(self):

        print('evalute')

        return 3.14 * self.radius ** 2

 

    def __getattr__(self, item):

        return 1

 

 

= Circle(4)

print(c.area)

print(c.area)

 

我们看一下输出的结果:

1

2

3

4

evalute

50.24

evalute

50.24

  

 

同样的定义,只是在描述符中添加了__set__方法,就会执行调用描述符定义的属性,和非描述符的调用方式天壤之别。这就是这个高级特性的特别之处。我们可以使用非数据描述符做惰性加载,只计算一次,下次直接取值,我在工作中也是这样干的。

 

知其然,知其所以然,我们来看一下是为什么:

根据官方的解释,描述符可以通过其方法名称直接调用。例如,d .__ get __(obj)。另外,更常见的是在属性访问时自动调用描述符。例如,obj.d在obj的字典中查找d。如果d定义了方法__get __(),则根据下面列出的优先级规则调用d .__ get __(obj)。调用的细节取决于obj是对象还是类。

    对于对象,机制位于object .__ getattribute __()中,它将b.x转换为type(b).__ dict __ ['x'] .__ get __(b,type(b))。该实现通过优先级链进行工作,该优先级链赋予数据描述符优先于实例变量的优先级,实例变量优先于非数据描述符的优先级,并为__getattr __()分配最低优先级。完整的C实现可在Objects / object.c中的PyObject_GenericGetAttr()中找到。 

对于类,机制的类型为.__ getattribute __(),它将B.x转换为B .__ dict __ ['x'] .__ get __(无,B)。在纯Python中,它看起来像:

def __getattribute__(self, key):    "Emulate type_getattro() in Objects/typeobject.c"    v = object.__getattribute__(self, key)    if hasattr(v, '__get__'):        return v.__get__(None, self)    return v

要记住的重要点是:

  • 描述符由__getattribute __()方法调用

  • 重写__getattribute __()防止自动描述符调用

  • object .__ getattribute __()和type .__ getattribute __()对__get __()进行不同的调用。

  • 数据描述符始终会覆盖实例字典。非数据描述符可以被实例字典覆盖。

来源:站长资讯平台



回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

关闭

站长推荐上一条 /6 下一条

  • 论坛活动 E手掌握

    扫码关注
    EEWORLD 官方微信

  • EE福利  唾手可得

    扫码关注
    EE福利 唾手可得

Archiver|手机版|小黑屋|电子工程世界 ( 京ICP证 060456 )

GMT+8, 2020-1-30 05:22 , Processed in 0.093109 second(s), 18 queries , Gzip On, MemCache On.

快速回复 返回顶部 返回列表