Getters and setters as methods, vs getters and setters as properties in python3

Viewed 49

If my understanding is correct, properties are used for getters and setters. Decorating a getter or setter with @property allows us to modify it, use it, etc, as if it were an attribute, so rather than doing something like MyClass.foo(), you would do MyClass.foo. I've read that this is done in order to keep your code more clean and improve readability. Is there any other reason to use decorate getters and setters with @property? I've read somewhere that it is better to use properties to also not break you code when wanting to make a change, which I don't understand why you would need properties for that when you have getter and setter methods that are not properties.

2 Answers

As the article you linked to in the comment says:

The main advantage of Python properties is that they allow you to expose your attributes as part of your public API. If you ever need to change the underlying implementation, then you can turn the attribute into a property at any time without much pain.

The keywords here are to "expose your attributes".

Say you have a class Object with an instance attribute weight, which you initially expose as part of the API and allow end users to freely modify:

class Object:
    def __init__(self):
        self.weight = 0

But you later discover that some end users are setting weight with negative values, which should be considered invalid:

o = Object()
o.weight = -1

So you modify your class by adding a getter and a setter method and renaming the original attribute with a _ prefix in order to hide it:

class Object:
    def __init__(self):
        self._weight = 0

    def get_weight(self):
        return self._weight

    def set_weight(self, value):
        assert value >= 0 # validate that weight is not negative
        self._weight = value

But then you need make changes to your exposed API and tell the end users to make corresponding changes on their end:

o = Object()
o.set_weight(-1) # now this will be properly disallowed and raise an exception

which is troublesome for both you (having to change your exposed API documentation) and your end users (having to change the way they use your API).

Now, by converting the weight attribute to a property instead:

class Object:
    def __init__(self):
        self._weight = 0

    @property
    def weight(self):
        return self._weight

    @weight.setter
    def weight(self, value):
        assert value >= 0 # validate that weight is not negative
        self._weight = value

The end user can continue to use their existing codes:

o = Object()
o.weight = -1 # raises an exception

and they now get the benefit of having a proper validation to weight, without ever needing to know that the underlying implementation has changed from an attribute to a setter method.

You're right, @property is similar to getter/setter, but it behaves a little different when calling.

When using normal getter/setter, for example:

class Foo:
    def getFoo():
        # ...
    def setFoo(foo):
        # ...

my_foo = Foo()
# print foo value
print(my_foo.getFoo())

# set foo to 100
my_foo.setFoo(100)

As you can see, that's kinda ugly. So Python has the @property decorator to solve this:

class Foo:
    def __init__(self):
        self.inner_foo = 1

    @property
    def foo(self):
        return self.inner_foo

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

foo = Foo()
print(foo.foo)
foo.foo = 100
print(foo.foo)

Now the foo property acts like a normal property when usage and makes you code much more cleaner.

Related