编写非数据描述符 [英] Writing a Non-Data Descriptor

查看:87
本文介绍了编写非数据描述符的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在学习python中的描述符.我想编写一个非数据描述符,但是将描述符作为其类方法的类在调用类方法时不会调用__get__特殊方法.这是我的示例(没有__set__):

class D(object):

    "The Descriptor"

    def __init__(self, x = 1395):
        self.x = x

    def __get__(self, instance, owner):
        print "getting", self.x
        return self.x


class C(object):

    d = D()

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

这就是我的称呼:

>>> c = C(4)
>>> c.d
4

描述符类的__get__没有任何调用.但是当我还设置了__set__时,描述符似乎已激活:

class D(object):

"The Descriptor"

    def __init__(self, x = 1395):
        self.x = x

    def __get__(self, instance, owner):
        print "getting", self.x
        return self.x

    def __set__(self, instance, value):
        print "setting", self.x
        self.x = value

class C(object):

    d = D()

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

现在,我创建一个C实例:

>>> c=C(4)
setting 1395
>>> c.d
getting 4
4

__get__, __set__都存在.似乎我缺少关于描述符及其使用方法的一些基本概念.谁能解释__get__, __set__的这种行为?

解决方案

您已成功创建了正确的非数据描述符,但随后通过设置实例属性来屏蔽 d属性./p>

因为它是一个 non 数据描述符,所以实例属性在这种情况下会获胜.添加__set__方法时,会将描述符变成数据描述符,并且即使存在实例属性,也始终应用数据描述符. (*)

来自 描述符操作方法 :

属性访问的默认行为是从对象的字典中获取,设置或删除属性.例如,a.x具有一个查找链,查找链从a.__dict__['x']开始,然后是type(a).__dict__['x'],并一直到type(a)的基类(不包括元类)继续.如果查找到的值是定义描述符方法之一的对象,则Python可能会覆盖默认行为并改为调用描述符方法.优先级链中发生此错误的位置取决于定义了哪些描述符方法.

如果对象同时定义了__get__()__set__(),则将其视为数据描述符.仅定义__get__()的描述符称为非数据描述符(它们通常用于方法,但也可以用于其他用途).

数据和非数据描述符的不同之处在于,如何计算实例字典中条目的替代值.如果实例的字典中的条目与数据描述符的名称相同,则以数据描述符为准.如果实例的字典中具有与非数据描述符同名的条目,则该字典条目优先.

如果删除 d实例属性(从不设置实例属性或从实例中删除它),则描述符对象将被调用:

>>> class D(object):
...     def __init__(self, x = 1395):
...         self.x = x
...     def __get__(self, instance, owner):
...         print "getting", self.x
...         return self.x
...
>>> class C(object):
...     d = D()
...
>>> c = C()
>>> c.d
getting 1395
1395

再次添加实例属性,由于实例属性获胜,描述符将被忽略:

>>> c.d = 42  # setting an instance attribute
>>> c.d
42
>>> del c.d   # deleting it again
>>> c.d
getting 1395
1395

另请参见 调用描述符文档在Python 数据模型参考中.


(*)假设数据描述符实现了__get__钩子.通过instance.attribute_name访问此类描述符将返回描述符对象,除非instance.__dict__ 中存在'attribute_name'.

I am learning about descriptors in python. I want to write a non-data descriptor but the class having the descriptor as its classmethod doesn't call the __get__ special method when I call the classmethod. This is my example (without the __set__):

class D(object):

    "The Descriptor"

    def __init__(self, x = 1395):
        self.x = x

    def __get__(self, instance, owner):
        print "getting", self.x
        return self.x


class C(object):

    d = D()

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

And here is how I call it:

>>> c = C(4)
>>> c.d
4

The __get__ of the descriptor class gets no call. But when I also set a __set__ the descriptor seems to get activated:

class D(object):

"The Descriptor"

    def __init__(self, x = 1395):
        self.x = x

    def __get__(self, instance, owner):
        print "getting", self.x
        return self.x

    def __set__(self, instance, value):
        print "setting", self.x
        self.x = value

class C(object):

    d = D()

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

Now I create a C instance:

>>> c=C(4)
setting 1395
>>> c.d
getting 4
4

and both of __get__, __set__ are present. It seems that I am missing some basic concepts about descriptors and how they can be used. Can anyone explain this behaviour of __get__, __set__?

解决方案

You successfully created a proper non-data descriptor, but you then mask the d attribute by setting an instance attribute.

Because it is a non-data descriptor, the instance attribute wins in this case. When you add a __set__ method, you turn your descriptor into a data descriptor, and data descriptors are always applied even if there is an instance attribute. (*)

From the Descriptor Howto:

The default behavior for attribute access is to get, set, or delete the attribute from an object’s dictionary. For instance, a.x has a lookup chain starting with a.__dict__['x'], then type(a).__dict__['x'], and continuing through the base classes of type(a) excluding metaclasses. If the looked-up value is an object defining one of the descriptor methods, then Python may override the default behavior and invoke the descriptor method instead. Where this occurs in the precedence chain depends on which descriptor methods were defined.

and

If an object defines both __get__() and __set__(), it is considered a data descriptor. Descriptors that only define __get__() are called non-data descriptors (they are typically used for methods but other uses are possible).

Data and non-data descriptors differ in how overrides are calculated with respect to entries in an instance’s dictionary. If an instance’s dictionary has an entry with the same name as a data descriptor, the data descriptor takes precedence. If an instance’s dictionary has an entry with the same name as a non-data descriptor, the dictionary entry takes precedence.

If you remove the d instance attribute (never set it or delete it from the instance), the descriptor object gets invoked:

>>> class D(object):
...     def __init__(self, x = 1395):
...         self.x = x
...     def __get__(self, instance, owner):
...         print "getting", self.x
...         return self.x
...
>>> class C(object):
...     d = D()
...
>>> c = C()
>>> c.d
getting 1395
1395

Add an instance attribute again and the descriptor is ignored because the instance attribute wins:

>>> c.d = 42  # setting an instance attribute
>>> c.d
42
>>> del c.d   # deleting it again
>>> c.d
getting 1395
1395

Also see the Invoking Descriptors documentation in the Python Datamodel reference.


(*) Provided the data descriptor implements the __get__ hook. Accessing such a descriptor via instance.attribute_name will return the descriptor object unless 'attribute_name' exists in instance.__dict__.

这篇关于编写非数据描述符的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆