为什么 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__
方法并在其中引发错误。
我正在通过 属性 学习描述符协议,我正在编写自己的 属性 如下:
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__
方法并在其中引发错误。