为什么 instance.attribute = value 可以工作,而 __set__ 方法没有在 属性 中实现?

why instance.attribute = value can work while __set__ method is not implemented in property?

我正在通过 属性 学习描述符协议,我正在编写自己的 属性 如下:

class my_property(object):
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
           raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel)

class test_my_property(object):
    def __init__(self, value):
        self._val = value

    @my_property
    def val(self):
        return self._val

     @val.setter
     def val(self, value):
         self._val = value

def main():
     c = test_my_property(5)
     print c.val
     c.val = 10
     print c.val
     print type(c).__dict__['val'].__set__

我得到:

5
10
AttributeError: 'my_property' object has no attribute '__set__'

我的问题是,既然“__set__”没有定义,那么"c.val = 10"怎么行呢? 如果“__set__”是由my_property继承自object,那么,为什么会报AttributeError?

__set__ 不是 从对象继承的。获取和设置 val 属性是有效的,因为当访问对象的属性时,首先会检查实例, 然后 class。由于您设置了实例属性 val,它会使用该属性。如果您正在查看一个没有描述符的简单示例,我认为这一点特别清楚,

>>> class Foo(object):
...     val = 5
... 
>>> f = Foo()
>>> f.val  # f doesn't have val so fallback on Foo
5
>>> f.val = 10
>>> f.val  # f now has val so use it
10
>>> del f.val  # oops what now
>>> f.val  # class again
5

上面的例子和你的唯一区别是你的 class val 是(当你完成时)一个 属性.

综上所述,您通常不希望将 属性 命名为与保存其内容的实例属性相同的名称。通常的公式是这样的,

class Foo(object):
    def __init__(self, value):
        self._val = value

    @property
    def val(self):
        return self._val

@Jared 的回答是正确的。 __set__ 不是从对象继承的。我会尝试用另一种方式解释,这可能更清楚。

首先,如您所知,如果您的描述符 do 有一个 __set__ 方法,它会在 运行 c.val=10 时被调用。这意味着解释器会寻找 __set__ 方法,如果找到它,它会通过调用它来将其视为描述符。

现在,由于 my_property 没有 __set__ 方法,因此在 运行 c.val=10 时它不会得到描述符处理。解释器回退到 "standard" 处理,大致相当于 c.__dict__['val']=10.

您可以轻松验证使用:

print c.__dict__  # no 'val'
c.val = 10
print c.__dict__  # 'val' was added

现在,c.__dict__ 中的 'val'(在对象级别)覆盖了您的 属性(在 class 级别定义),并且将在访问时使用c.val.

如果您想禁止分配给您的属性,您需要明确地进行。您需要定义一个 __set__ 方法并在其中引发错误。