Why instance.attribute = value might work and the __set__ method is not implemented on the property?
I am looking at a property to learn about the handle protocol and I am writing my own property like this:
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__
And I get:
5
10
AttributeError: 'my_property' object has no attribute '__set__'
My question is, since "__set__" is not defined, how can "c.val = 10" work? if "__set__" inherits from my_property object, why is it reporting AttributeError?
source to share
@Jared is correct. __set__
does not inherit from the object. I will try to explain in a different way, which may be clearer.
First, as you already know, if your handle has a method __set__
, it is called at startup c.val=10
. This means that the interpreter looks for a method __set__
, and if it finds one, it treats it as a handle by calling it.
Now, since my_property
it has no method __set__
, it will not receive handle processing on startup c.val=10
. The translator reverts to "standard" treatments, which is roughly equivalent c.__dict__['val']=10
.
You can easily verify that using:
print c.__dict__ # no 'val'
c.val = 10
print c.__dict__ # 'val' was added
Now 'val' in c.__dict__
(at the object level) is greater than your property (which is defined at the class level) and will be used when accessing c.val
.
If you want to prohibit the appropriation of your property, you will need to do so explicitly. You will need to define a method __set__
and call an error in it.
source to share
__set__
does not inherit from the object. Getting and setting an attribute val
works because when you access an attribute of an object, the instance is checked first and then the class. Since you are setting an instance attribute val
, it uses that. I think this is especially clear if you look at a simple example of this without descriptors,
>>> 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
The only difference between the above example and yours is that your class val
is (when you're done) a property.
For all that, you generally don't want to name your property the same as the instance attribute that will contain its content. The usual wording looks like this:
class Foo(object):
def __init__(self, value):
self._val = value
@property
def val(self):
return self._val
source to share