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?

+3


source to share


2 answers


Answer to

@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.

0


source


__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

      

+1


source







All Articles