All posts

Should I use Python properties?

Python properties have been surprisingly divisive amongst developers I've worked with.

In simple use cases, they're great.

class User():
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    @property
    def full_name(self):
        return f'{self.first_name} {self.last_name}'


>>> User('Shirley', 'Jones').full_name
Shirley Jones

The problem is they can cause confusion.

class User():
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    @property
    def get_full_name(self):
        return f'{self.first_name} {self.last_name}'


# Feels weird:
>>> User('Shirley', 'Jones').get_full_name
Shirley Jones

Just by changing the method name, it feels unnatural for this to be a property. Calling my_user.get_full_name feels like it should have brackets after it, because it sounds like a function. So naming is definitely important when using properties.

Also, properties work great if you're confident you won't need to add any arguments in the future.

Imagine we wanted to modify get_full_name so it had an include_title argument.

If we implemented it as a property, we'll break everyone's code, because now it'll have to be called as a function to work properly:

class User():
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    def get_full_name(self, include_title=False):
        fullname = f'{self.first_name} {self.last_name}'
        if include_title:
            fullname = f'Madam {fullname}'

        return fullname


# We broke our existing code:
>>> User('Shirley', 'Jones').get_full_name
Error!

This might not matter much in small projects, but if you're a library author you don't want to introduce a breaking change just by adding an argument to a property.

In API design, properties can be overused too. If you're designing a fluent interface, you don't want to add a cognitive load to a programmer by making them consider 'is this a property or a method?'.

Take this example:

class Select(Query):

    def where(self, query) -> Select:
        # do stuff
        return self

    @property
    def first(self) -> Select:
        # do stuff
        return self

    def run(self):
        return 'some data'

To use this API:

select = Select().where(some_query).first.run()

Rather than having to remember that first is a property, it's cleaner to have them all as plain methods.

select = Select().where(some_query).first().run()

Sure, it takes a couple more key strokes, but sometimes consistency is king.

And lastly, perhaps the main way properties can be abused is if a really heavy piece of computation, or a long network request, is done to generate the response. A developer could unexpectedly cripple their app's performance by calling an innocent looking property too many times.

So in conclusion, properties can be great - but consider if you really need them, and if so keep them simple.

Posted on: 8 Aug 2019

Have any comments or feedback on this post? Chat with us on GitHub.