Why Does @Foo.Setter in Python Not Work for Me

Why does @foo.setter in Python not work for me?

You seem to be using classic old-style classes in python 2. In order for properties to work correctly you need to use new-style classes instead (in python 2 you must inherit from object). Just declare your class as MyClass(object):

class testDec(object):

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

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

It works:

>>> k = testDec()
>>> k.x
called getter
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/devel/class_test.py", line 6, in x
return self._x
AttributeError: 'testDec' object has no attribute '_x'
>>> k.x = 5
called setter
>>> k.x
called getter
5
>>>

Another detail that might cause problems is that both methods need the same name for the property to work. If you define the setter with a different name like this it won't work:

@x.setter
def x_setter(self, value):
...

And one more thing that is not completely easy to spot at first, is the order: The getter must be defined first. If you define the setter first, you get name 'x' is not defined error.

@property setter not called in python 3

The setter is only invoked when you try to assign to the property. In the line

pozza = pizzo.getCiao('setter')

you first access the value of the property with pizzo.getCiao (which calls the getter and returns the string "getter", then attempts to call the string as a function with the argument 'setter'.

You have basially created a read-only property:

>>> pizzo = DumbClass('getter')
>>> pizzo.getCiao
'getter'
>>> pizzo.getCiao = 'foo'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

This is because you didn't use the same name when defining both the getter and setter, so your property doesn't have a setter. (Arguably, getCiao.setter should probably raise an error right away, since the name matters, but it doesn't. C'est la vie.)

So the right thing to do is use the same name for both. The usual convention is to use an private variable with the "same" name as the property to store the underlying data for simple properties like this.

class DumbClass:
def __init__(self, p):
self._ciao = p

@property
def ciao(self):
return self._ciao

@ciao.setter
def ciao(self, v):
self._ciao = v

Now you should be able to both get and set the value of the propery.

>>> d = DumbClass("hi")
>>> d.ciao
'hi'
>>> d.ciao = 'bye'
>>> d.ciao
'bye'

Python @property does not work as I want it to

If you'd use C properly (ie create an instance of it) you will get what you expect:

c = C()
c.x = 5
print(c.x)

outputs

setter
getter
5

Why is my python 2.7 getter acting like a setter?

Add object as parent class.

class Testclass(object):
def __init__(self):
self._testvar = None

@property
def testvar(self):
return self._testvar

@testvar.setter
def testvar(self, value):
self._testvar = value

testObj = Testclass()
print(testObj.testvar)
testObj.testvar = "test"
print(testObj.testvar)

>>> None
>>> test

Property setter gives an error in Python

A setter is used with the assignment operator =:

c.foo = 2

A further example how c.foo(2) works:

In [7]: c = C(lambda x: x*x)

In [8]: c.foo(2)
Out[8]: 4

c.foo(…) first gets the value in c.foo, then invokes the function.

Python's property decorator does not work as expected

The property decorator only works with new-style classes (see also). Make Silly extend from object explicitly to make it a new-style class. (In Python 3, all classes are new-style classes)

class Silly(object):
@property
def silly(self):
# ...
# ...

why the use of self.foo = self.foo in __init__ in Python class?

With a bit more code, its use may make some sense in two separate cases, although it's still not always the best way to achieve what it achieves:

class MyClass:
baz = 1

def __init__(self):
self.baz = self.baz
self._foo = None
self.bar = None
self.foo = self.foo

@property
def foo(self):
if self._foo is None:
self._foo = 1
return self._foo

@foo.setter
def foo(self, value):
self.bar = 0
self._foo = value

m1 = MyClass()
m2 = MyClass()
print(m1.foo, m1.bar, m1.baz)
m1.baz = 2
MyClass.baz = 3
print(m1.baz, m2.baz, MyClass.baz)

A bit contrived, but what self.foo = self.foo would achieve is that both the getter and setter for the .foo property will have been called.

People saying .foo cannot be accessed at that point and would raise an AttributeError are right in the limited case you're sharing, but of course defining a property makes it available in the constructor as well, as the example here shows. I kept the code a bit cleaner by defining ._foo first, but of course you could just first define it in the setter, that's not recommended though.

That setters and getters are run is mainly important if they have side effects, which is one reason to use properties (another common one being that you want to limit access, or restrict possible values).

Edit: The example in networkx is reassigning a class attribute to an instance attribute (I wasn't aware that worked until I tried). See the result of the final print() in my example:

m1.baz = 2
MyClass.baz = 3
print(m1.baz, m2.baz, MyClass.baz)

Result:

2 1 3

The reason networkx might be doing this is to allow you to supply a different Graph factory method, without affecting the class - only your instance would get a new one if you assign to its graph_attr_dict_factory, but it is initialised with the class factory method.

However, note that even if networkx didn't set it up like that in the constructor, a user could still assign a new factory to the instance later and see the same behaviour - the only real difference is that the instance wouldn't have an instance attribute up to that point and would have been accessing the class attribute instead. It's possible that other code relies on that distinction, so removing the assignment could have an impact elsewhere, but it does nothing for the immediate functionality of the class itself.

Creating property with abstract getter doesn't work as expected

The difference is that this code

BAR = property(_get_bar)

is executed only once when the body of your base class (Foo) is executed -
the _getbar in this expression refers to the method defined in this class (Foo), which is abstract and empty.

While the form

@property
def BAR(self) -> bool:
return self._get_bar()

encapsulates the reference to _get_bar inside a method (BAR), which will receive the instance as a parameter, and then retrieve the method from whatever subclass from Foo the instance (self) happens to be.

The way to fix that is to wrap the property argument in the function call form as a lambda, so that the method is retrieved from the appropriate class on call time:

BAR = property(lambda self: self._get_bar())

property.setter and inheritance

You can use @BaseClass.foo.getter to create a copy of the property with a different getter:

class Subclass(BaseClass):
@BaseClass.foo.getter
def foo(self):
print('Subclass')

See the property documentation for details.

How does the @property decorator work in Python?

The property() function returns a special descriptor object:

>>> property()
<property object at 0x10ff07940>

It is this object that has extra methods:

>>> property().getter
<built-in method getter of property object at 0x10ff07998>
>>> property().setter
<built-in method setter of property object at 0x10ff07940>
>>> property().deleter
<built-in method deleter of property object at 0x10ff07998>

These act as decorators too. They return a new property object:

>>> property().getter(None)
<property object at 0x10ff079f0>

that is a copy of the old object, but with one of the functions replaced.

Remember, that the @decorator syntax is just syntactic sugar; the syntax:

@property
def foo(self): return self._foo

really means the same thing as

def foo(self): return self._foo
foo = property(foo)

so foo the function is replaced by property(foo), which we saw above is a special object. Then when you use @foo.setter(), what you are doing is call that property().setter method I showed you above, which returns a new copy of the property, but this time with the setter function replaced with the decorated method.

The following sequence also creates a full-on property, by using those decorator methods.

First we create some functions and a property object with just a getter:

>>> def getter(self): print('Get!')
...
>>> def setter(self, value): print('Set to {!r}!'.format(value))
...
>>> def deleter(self): print('Delete!')
...
>>> prop = property(getter)
>>> prop.fget is getter
True
>>> prop.fset is None
True
>>> prop.fdel is None
True

Next we use the .setter() method to add a setter:

>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True

Last we add a deleter with the .deleter() method:

>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True

Last but not least, the property object acts as a descriptor object, so it has .__get__(), .__set__() and .__delete__() methods to hook into instance attribute getting, setting and deleting:

>>> class Foo: pass
...
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!

The Descriptor Howto includes a pure Python sample implementation of the property() type:

class Property:
"Emulate PyProperty_Type() in Objects/descrobject.c"

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 __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)

def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)

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

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

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


Related Topics



Leave a reply



Submit