18

I have a class called Node that has an importance setter and getter, below:

class Node:

    @property
    def importance(self):
        return self._importance

    @importance.setter
    def importance(self, new_importance):
        if new_importance is not None:
            new_importance = check_type_and_clean(new_importance, int)
            assert new_importance >= 1 and new_importance <= 10
        self._importance = new_importance

Later on, I have a class Theorem that inherits from Node. The only difference between a Theorem and a Node, as far as importance is concerned, is that a Theorem must have an importance of at least 3.

How can a Theorem inherit the importance setter, but add on the additional constraint that importance >= 3?

I tried to do it this way:

class Theorem(Node):

    @importance.setter
    def importance(self, new_importance):
        self.importance = new_importance # hoping this would use the super() setter
        assert self.importance >= 3
Markus Meskanen
  • 19,939
  • 18
  • 80
  • 119
mareoraft
  • 3,474
  • 4
  • 26
  • 62
  • 4
    Note you could solve the problem completely differently by having a class attribute `MIN_IMPORTANCE` that is `1` in `Node` and `3` in `Theorem`. – jonrsharpe Aug 10 '15 at 06:36
  • 1
    Possible duplicate of [Overriding properties in python](http://stackoverflow.com/questions/7019643/overriding-properties-in-python) – Mr_and_Mrs_D Feb 13 '17 at 20:09

3 Answers3

28

You can refer to the existing property directly through the Node class, and use the property's setter method to create a new property from it:

class Theorem(Node):
    @Node.importance.setter
    def importance(self, new_importance):
        # You can change the order of these two lines:
        assert new_importance >= 3
        Node.importance.fset(self, new_importance)

This will create a new property into Theorem class that uses the getter method from Node.importance but replaces the setter method with a different one. That's how properties in general work: calling a property's setter returns a new property with a custom setter, which usually just replaces the old property.

You can learn more about how properties work by reading this answer (and the question too).

Community
  • 1
  • 1
Markus Meskanen
  • 19,939
  • 18
  • 80
  • 119
  • 1
    `TypeError: setter() takes exactly one argument (2 given)`. Did you mean `fset`? – jonrsharpe Aug 09 '15 at 21:58
  • @jonrsharpe Ups, try now :P – Markus Meskanen Aug 09 '15 at 21:59
  • 1
    Yep, much better, and neater than mine! – jonrsharpe Aug 09 '15 at 22:01
  • @MarkusMeskanen So writing `@Node.importance.setter` above the definition of `importance` is the same thing as writing `importance = Node.importance.setter(importance)` below the definition of importance? – mareoraft Aug 10 '15 at 01:46
  • 2
    @mareoraft indeed that's how *all* decorators work, the `@` is just syntactic sugar. – jonrsharpe Aug 10 '15 at 09:12
  • I often saw the convention that property getters have names such as `get_importance` and setters have names such as `set_important`, and I follow this convention. However, when I test your code, it **only** works if the setter method in the child class `Theorem(Node)` has the same name as the property. In other words, if I replace `def importance(...)` with `def set_importance(...)` in your code, then it stops working (it doesn't override the parent class's property setter). This seems a bit counterintuitive. Is it a bug or not? – Marses Apr 30 '21 at 12:24
  • @Marses It's definitely intended behavior, you can read more about properties here: https://docs.python.org/3/library/functions.html#property – Markus Meskanen Oct 26 '21 at 12:45
5

Here is a completely different solution to the broader problem, with a lot less faffing around:

class Node:

    MIN_IMPORTANCE = 1
    MAX_IMPORTANCE = 10

    @property
    def importance(self):
        return self._importance

    @importance.setter
    def importance(self, new_importance):
        if new_importance is not None:
            new_importance = check_type_and_clean(new_importance, int)
            assert (new_importance >= self.MIN_IMPORTANCE and 
                    new_importance <= self.MAX_IMPORTANCE)
        self._importance = new_importance


class Theorem(Node):

    MIN_IMPORTANCE = 3

    # and that's all it takes!

To my mind, this expresses:

The only difference between a Theorem and a Node, as far as importance is concerned, is that a Theorem must have an importance of at least 3.

a lot more clearly than overriding the property setter does.


Note that assert is generally used for testing and debugging, rather than as part of the general program flow, and certainly not for things that you expect could happen; see e.g. Best practice for Python Assert.

Community
  • 1
  • 1
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • 1
    This is the best solution for the *specific example problem* posed. The accepted answer above is the accepted answer because it addresses the question asked and is on-topic with the title. Nevertheless, I appreciate this well thought out answer. – mareoraft Aug 01 '19 at 17:25
4

One way to do this is by implementing a new property on Theorem using the Node getter, providing a new setter method and calling the Node setter explicitly within it:

class Theorem(Node):

    def _set_importance(self, new):
        Node.importance.fset(self, new)
        assert self.importance >= 3

    importance = property(Node.importance.fget, _set_importance)

As far as I'm aware, this cannot be done with super.


Per this bug report, you could do:

class Theorem(Node):

    def _set_importance(self, new):
        super(Theorem, Theorem).importance.fset(self, new)
        assert self.importance >= 3

    importance = property(Node.importance.fget, _set_importance)

However, this is clearly a bit awkward; the patch to allow super() instead appears to be scheduled for Python 3.5 (due in September 2015).

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • 1
    Don't pass `self.__class__` to `super()`. It will totally break when you subclass from `Theorem`. – Kevin Aug 09 '15 at 22:15
  • @Kevin will it? A quick test indicates it getting up to `Node.importance.fset` from a `Theorem` subclass instance. – jonrsharpe Aug 09 '15 at 22:17
  • ISTM that `super(TheoremSubclass, TheoremSubclass).importance` will return the object you created on the last line. That should produce infinite recursion once you call `fset()`. – Kevin Aug 09 '15 at 22:18
  • @Kevin you seem to be right, and I can't figure out why that didn't happen first time! – jonrsharpe Aug 09 '15 at 22:24