pythonpropertiesgetter-setter

pythonic way of using @property


I am trying to wrap my head around the different ways of using getters and setters through @property. I have read docs, blogs and posts here, but can't find a convention. There might not be one, but I assume some approaches are more suited for some applications and some are maybe more pythonic?

Any best practices?

Example 1 (I assume it is redundant to do the check for a<0 in setter and init?):

class ExponentialDecay:

    def __init__(self, a: float) -> None:
        if a < 0:
            raise ValueError("Decay constant cannot be negative.")
        self._a = a

    @property
    def decay(self) -> float:
        return self._a
    
    @decay.setter
    def decay(self, value: float) -> None:
        if value < 0:
            raise ValueError("Decay constant cannot be negative.")
        self._a = value

Example 2 (use setter in init):

class ExponentialDecay:

    def __init__(self, a: float) -> None:
        self.decay = a

    @property
    def decay(self) -> float:
        return self._a
    
    @decay.setter
    def decay(self, value: float) -> None:
        if value < 0:
            raise ValueError("Decay constant cannot be negative.")
        self._a = value

Example 3: Same code, but using name mangling in init or in the setter (self.__decay).

Trying to learn more, not stuck on a problem per se.


Solution

  • To some extent, _a is an implementation detail of the property, not ExponentialDecay, and so ExponentialDecay should not use it directly, only "agree" to store it for the property to use. So Example 2 is "correct": ExponentialDecay.__init__ should let decay check and store the value of a itself.

    (The name _a and its existence, though, are considered part of the public interface of the property. Name-mangling would prevent subclasses of ExpoentialDecay from overwriting it, but not from ExponentialDecay itself from doing so. So we can thankfully dismiss Example 3 as a necessary option.)


    It's worth noting that neither the getter or setter are methods of ExponentialDecay, but simply functions stored by the property object which the property calls using an ExponentialDecay instance as an explicit argument. For example,

    self.decay = a
    

    is implemented with

    type(self).decay.__set__(self, a)
    

    which itself is implemented by

    type(self).decay.fset(self, a)