python why is a setter preferred?

Viewed 269

The property decorator is a great way to "protect" attributes one wants to set once and never change again. I usually deal with this this way (btw., following a the advice here):

self._name = 'foo'

@property
def name(self):
    return self._name

so trying to set name directly yields an AttributeError.

However, I often see the following pattern:

@name.setter
def name(self, value):
    self._name = value

@property
def name(self):
    return self._name

which seems a little counter-intuitive, as it enables exactly what I want to avoid, and requires extra coding, i.e, theoretically

self.name = 'bar'

would suffice, although it is clear that this would be the worst way to deal with the problem.

The best explanation I can come up with is something like a message from the author saying "you should not change this attribute but if you really want to, there is a mechanism to do it without changing a 'protected' attribute". But then, python doesn't really protect attributes.

So, what's the point, which is more pythonic and why?

2 Answers

If the setter and getter are just directly writing and reading the protected variable, then they're pointless, and using it is not Pythonic; it's just wasting time on property overhead for each access. The attribute should just be made public and the property removed.

The advantage to properties is when you need to replace that simple public attribute with something more powerful, because you can make a property that continues to act like it should for code that was using the attribute, but performs additional work as well. Unless you have additional work though, stick with the attribute if you'd allow it to be written anyway.

Note: Technically, a getter and a setter isn't 100% equivalent to the attribute, since without a deleter, it's not behaviorally identical to the raw attribute. But enforcing non-deletability in your API is silly; developers who go around calling del obj.attr on random attributes of third-party class instances (or almost any instance really) deserve what's coming to them, and you shouldn't be defending against that nonsense at the expense of slowing down and complicating normal use patterns.

You're correct that there's no good reason to use a property if you're not doing anything special in the getter or setter. However, if you do want to do something special (like validate new values, or normalize them in some way), then it makes a lot of sense.

For example, this class's foo attribute will always be clamped between 0 and 1 (and non-numerical values will cause an error immediately):

class Foo:
    _foo = 1.0

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

    @foo.setter
    def foo(self, value):
        if value < 0:
            value = 0
        elif value > 1:
            value = 1
        self._foo = value

An example with a trivial setter, but a complicated getter might be something like this (deferring an expensive initialization that might not be needed):

class Foo:
    _foo = None

    def initialize_foo(self):
        self._foo = some_expensive_calculation()

    @property
    def foo(self):
        if self._foo is None:
            self.initialize_foo() # need the default value
        return self._foo

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