Forum Archive

Properties that are more fun

mikael

Is it just me, or is the Python syntax for defining properties of classes a bit cumbersome? Like this:

class Example:

  @property
  def my_property(self):
    return self.real_value

  @my_property.setter
  def my_property(self, value):
    self.real_value = value

My main problem with this is that the name of the property, my_property above, has to be repeated 3 times. This does not seem very Pythonic, and is a pain when I want to rename the property, or want to create several properties - copy/paste or a snippet, then fix the name 3 times. People with editors that have better refactoring support might not mind so much, but in Pythonista it is painful.

So, I have started to routinely use a decorator like this:

def prop(func):
  return property(func, func)

With it, defining a new property goes like this:

class Example:

  @prop
  def my_property(self, *value):
    if value:
      self.real_value = value[0]
    else:
      return self.real_value

A bit ugly, same amount of lines, but easy to copy/paste or refactor, as the name of the property is there only once.

I do not know if anyone else cares, but opinions and alternate approaches are very welcome.

bennr01

I am not sure, but IIRC, you could also use my_property = property(getter, setter) instead of the @property syntax.

mikael

@bennr01, while that syntax is maybe cleaner in a way, would I not still need to maintain 3 names – property, getter and setter?

bennr01

@mikael You are right. This was only meant as a suggestion for reducing the number of times you have to write my_property (for a complete getter/setter/deleter setup it would be 3 instead of 5 times). Your decorator @prop is indeed neat, but it may still be useful to remember the my_property = property(...) syntax in case you (or anyone else who reads this) may find themself in a situation where you can not use it (due to code style guidelines or other reasons). I had not seen that you actually used this syntax in your example above and thought it may be a nice tip for you.

would I not still need to maintain 3 names – property, getter and setter?

I am not sure, but you may be able to use lambda in this case, which could still be usefull for some cases like read-only properties. Also, in some cases the getter and setter may only be calling some other aliases (like getting/setting text labels).

upwart

@mikael
Thanks for this great idea.
I have renamed your decorator to getter_setter, to make the purpose of it more clear.

Thus we define a decorator function:

def getter_setter(func):
    return property(func, func)

and then we can redo the example as:

class Example:

@getter_setter
def my_property(self, *value):
    if value:
        self._my_property = value[0]
    else:
        return self._my_property

or EAFP style:

class Example:

    @getter_setter
    def my_property(self, *value):
        try:
            self._my_property = value[0]
        except IndexError:
            return self._my_property

Optionally, we could define two more decorators for getter only and setter only properties:

def getter(func):
    def raise_attribute_error(*args):
        raise AttributeError("can't set attribute")
    return property(func, raise_attribute_error)


def setter(func):
    def raise_attribute_error(*args):
        raise AttributeError("can't get attribute")
    return property(raise_attribute_error, func)

Here is an example of a Circle class demonstrating the three decorators.
radius can be used to get the radius and set the radius (thus updating the _area attribute)
area can be used to get the area only. Setting will raise an error.
diameter can only be used to set the radius. You can’t get it.

Here is an implementation with getter_setter only:

```
import math

class Circle:
def init(self, radius):
self.radius = radius

@getter_setter
def radius(self, *value):
    if value:
        self._radius = value[0]
        self._area = math.pi * self._radius ** 2
    else:
        return self._radius

@getter_setter
def area(self, *value):
    if value:
        raise AttributeError("can't set attribute")
    else:
        return self._area

@getter_setter
def diameter(self, *value):
    if value:
        self.radius = value[0] / 2
    else:
        raise AttributeError("can't get attribute")

And if we use getter and setter as well:

import math

class Circle:
def init(self, radius):
self.radius = radius

@getter_setter
def radius(self, *value):
    if value:
        self._radius = value[0]
        self._area = math.pi * self._radius ** 2
    else:
        return self._radius

@getter
def area(self):
    return self._area

@setter
def diameter(self, value):
    self.radius = value / 2
JonB

Doesn't this have the problem if you try to set the property to None? I think you want to check len of value, rather than if value.

enceladus

Property is useful when you have needs like validation, caching etc. and it is not much useful for simple cases. Hence I feel it is better to use standard methodology even if it is a bit cumbersome. See this for some good real life examples. https://stackoverflow.com/questions/6304040/real-world-example-about-how-to-use-property-feature-in-python

enceladus

Using property to update variable in CustomView. (Position in GUI and equivalent value)
https://gist.github.com/33f06c4f1c439849be7736f897b0c6b0

upwart

@enceladus
This GUI Customview is where the getter_setter would certainly be useful!