'Can't set attribute' with new-style properties in Python
Asked Answered
C

4

85

I'm trying to use new-style properties declaration:

class C(object):
    def __init__(self):
        self._x = 0

    @property
    def x(self):
        print 'getting'
        return self._x

    @x.setter
    def set_x(self, value):
        print 'setting'
        self._x = value

if __name__ == '__main__':
    c = C()
    print c.x
    c.x = 10
    print c.x

and see the following in console:

pydev debugger: starting
getting
0
File "\test.py", line 55, in <module>
c.x = 10
AttributeError: can't set attribute

what am I doing wrong? P.S.: Old-style declaration works fine.

Crinum answered 15/11, 2010 at 10:27 Comment(2)
I've rolled back the edit which turns 'setting' (a docstring) into print 'setting' (a simple debugging statement). While it's certainly plausible that the print statement was intended, there's no harm or error in the docstring, and it doesn't affect the question at all.Twenty
I know this is an old question but just in case it's useful for anybody: if you're using Jupyter Notebooks to test your code there's a chance you might need to restart your kernel. It worked for me at least.Delila
E
123

The documentation says the following about using decorator form of property:

Be sure to give the additional functions the same name as the original property (x in this case.)

I have no idea why this is since if you use property as function to return an attribute the methods can be called whatever you like.

So you need to change your code to the following:

@x.setter
def x(self, value):
    'setting'
    self._x = value
Euniceeunuch answered 15/11, 2010 at 10:38 Comment(5)
Ah great, I saw this in another place, but assumed they might be ignorant about python's lack of method overloading. This actually looks cleaner than a 'set_x' name! :)Disruptive
What is the 'setting' string for?Frostbite
I had the same problem where I changed the name of the property and forgot to change the decorator -- amazing the things I can miss sometimes. Thanks for the answer :)Statistician
Does someone understand what's the reason for this constraint?Wiegand
@flonk: It's fairly technical, but Python is doing a bit of magic to use the method name (on the def line) as the attribute name. So in OP's case, where he has defined a method called set_x and decorated it as a setter, he has actually created a settable attribute called set_x! Thus, instead of c.x = 10, what he could do is c.set_x = 10. Note that the name in the decorator doesn't matter. If he had two properties, x and y, he could just as easily use @x.setter or @y.setter to turn set_x into a setter attribute (that assigns a value to self._x).Twenty
C
15

The setter method has to have the same name as the getter. Don't worry, the decorator knows how to tell them apart.

@x.setter
def x(self, value):
 ...
Comeback answered 15/11, 2010 at 10:31 Comment(0)
C
6

When you call @x.setter, @x.getter, or @x.deleter, you're creating a new property object and giving it the name of the function you're decorating. So really, all that matters is that the last time you use a @x.*er decorator in the class definition, it has the name you actually want to use. But since this would leave your class namespace polluted with incomplete versions of the property you wish to use, it's best to use the same name to clean them up.

Caylor answered 1/11, 2011 at 22:24 Comment(0)
S
2

If you don't want the extra _x name slot, here's a complex little trick you can do:
(tested with Py34)

>>> class C(object):
    __slots__ = ['x'] # create a member_descriptor
    def __init__( self ):
        self.x = 0
        # or use this if you don't want to call x_setter:
        #set_x( self, 0 )


>>> get_x = C.x.__get__ # member_descriptor getter
>>> set_x = C.x.__set__ # member_descriptor setter
>>> # define custom wrappers:
>>> def x_getter( self ):
    print('getting')
    return get_x( self )

>>> def x_setter( self, value ):
    print('setting')
    set_x( self, value )


>>> C.x = property( x_getter, x_setter ) # replace the member_descriptor
>>> c = C()
setting
>>> print(c.x)
getting
0
>>> c.x = 10
setting
>>> 
Sacrament answered 30/4, 2018 at 19:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.