3
\$\begingroup\$

I have designed a Python TreeNodeBase class, so all tree nodes I implement can inherit from this. However, due to the laziness inherent in a lot of trees, I would like some fields to be properties (using the @property decorator) instead.

But doing so, the base class will raise AttributeError whenever it tries to set a field, when I call super().__init__(). The way I'm resolving this now is to ignore the AttributeError. Is this a good idea, and is there a standard approach to this problem?

from contextlib import suppress

class TreeNodeBase:
    def __init__(self, value, edge_value=None, children=None, parent=None, tree=None):
        with suppress(AttributeError):
            self.value = value
        with suppress(AttributeError):
            self.edge_value = edge_value
        with suppress(AttributeError):
            self.children = children
        with suppress(AttributeError):
            self.parent = parent
        self.tree = tree

    def get_child(self, edge_value):
        raise NotImplementedError
\$\endgroup\$
1
  • 2
    \$\begingroup\$ Could you provide an example that causes the error you mention (if contextlib.suppress is removed)? If the child class defines a @property for e.g. edge_value, the setter will get called by the base class __init__. \$\endgroup\$
    – jonrsharpe
    Aug 4, 2014 at 16:43

2 Answers 2

2
\$\begingroup\$

You solution does look a bit fragile, but I can't help that without seeing more of the hierarchy. The way I'd approach this is to add value=None to your argument function header, and then do this for each attribute:

if value is not None:
    self.value = value

That way the inheriting classes can signal that they don't want the attribute initialised by just leaving the attribute out of the __init__ args, or passing None (this won't work if you actually want None as a value. You'd have to find another placeholder). That way, the system will be a bit more flexible, and there will not be any bad consequences caused by trying to initialise an attribute which is not meant to be initialised.

\$\endgroup\$
1
  • \$\begingroup\$ Good idea, but I do indeed need None for parameter values. Using some other placeholder, would work but feels rather strange. Also tried has_value which also works but adds a lot of arguments. \$\endgroup\$
    – simonzack
    Nov 30, 2014 at 3:18
1
\$\begingroup\$

That approach (silencing exceptions) looks like trouble.

In general I'd say that @matsjoyce is right, why do you want to set unsettable, by virtue of being properties, attributes? Also, setting variables like this isn't very much trouble, you can just do that each derived class instead and be done with it.

Now certainly, if you create nodes with defaults and then choose a class which implements them as read-only properties instead, then it makes a bit of sense to just disregard that information at the calling site and just try to set everything.

However, let's at least look at four options.

First, you can make setters, which plain ignore any set value. That way the init function is clean, but you might run into bugs because this behaviour is silent. You could then set a flag in the init function, which will suppress throwing a regular AttributeError, which you will throw yourself in every setter normally. That way you have the best of both worlds.

The next would be to use **kwargs and not set any attributes not mentioned in that dictionary. Again, this way you'd have to be aware of what a class can actually do and then omit invalid attributes.

You could also remove invalid arguments in the init function of derived classes, or simply not pass them on.

Lastly, you could look up whether the attributes are available in the __dict__ of the object. For that you'd have to initialise them (to None or so) in the init function of the derived class.

\$\endgroup\$

This site is temporarily in read-only mode and not accepting new answers.

Not the answer you're looking for? Browse other questions tagged .