Skip to content

Properties

The @property is the pythonic way to use getters and setters in object-oriented programming. It can be used to make methods look like attributes.

The property decorator returns an object that proxies any request to set or access the attribute value through the methods we have specified.

class Foo:
    @property
    def foo(self):
        return 'bar'

We can specify a setter function for the new property

class Foo:
    @property
    def foo(self):
        return self._foo

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

We first decorate the foo method a as getter. Then we decorate a second method with exactly the same name by applying the setter attribute of the originally decorated foo method. The property function returns an object; this object always comes with its own setter attribute, which can then be applied as a decorator to other functions. Using the same name for the get and set methods is not required, but it does help group the multiple methods that access one property together.

We can also specify a deletion function with @foo.deleter. We cannot specify a docstring using property decorators, so we need to rely on the property copying the docstring from the initial getter method

class Silly:
    @property
    def silly(self):
        "This is a silly property"
        print("You are getting silly")
        return self._silly

    @silly.setter
    def silly(self, value):
        print("You are making silly {}".format(value))
        self._silly = value

    @silly.deleter
    def silly(self):
        print("Whoah, you kicked silly!")
        del self.silly
>>> s = Silly()
>>> s.silly = "funny"
You are making silly funny
>>> s.silly
You are getting silly
'funny'
>>> del s.silly
Whoah, you kicked silly!

When to use properties

The most common use of a property is when we have some data on a class that we later want to add behavior to.

The fact that methods are just callable attributes, and properties are just customizable attributes can help us make the decision. Methods should typically represent actions; things that can be done to, or performed by, the object. When you call a method, even with only one argument, it should do something. Method names a generally verbs.

Once confirming that an attribute is not an action, we need to decide between standard data attributes and properties. In general, always use a standard attribute until you need to control access to that property in some way. In either case, your attribute is usually a noun . The only difference between an attribute and a property is that we can invoke custom actions automatically when a property is retrieved, set, or deleted

Cache expensive data

A common need for custom behavior is caching a value that is difficult to calculate or expensive to look up.

We can do this with a custom getter on the property. The first time the value is retrieved, we perform the lookup or calculation. Then we could locally cache the value as a private attribute on our object, and the next time the value is requested, we return the stored data.

from urlib.request import urlopen

class Webpage:
    def __init__(self, url):
        self.url = url
        self._content = None

    @property
    def content(self):
        if not self._content:
            print("Retrieving New Page..")
            self._content = urlopen(self.url).read()
        return self._content
>>> import time
>>> webpage = Webpage("http://ccphillips.net/")
>>> now = time.time()
>>> content1 = webpage.content
Retrieving new Page...
>>> time.time() - now
22.43316
>>> now = time.time()
>>> content2 = webpage.content
>>> time.time() -now
1.926645
>>> content1 == content2
True

Attributes calculated on the fly

Custom getters are also useful for attributes that need to be calculated on the fly, based on other object attributes.

clsas AverageList(list):
    @property
    def average(self):
        return sum(self) / len(self)
>>> a = AverageList([1,2,3,4])
>>> a.average
2.5

Of course we could have made this a method instead, but then we should call it calculate_average(), since methods represent actions. But a property called average is more suitable, both easier to type, and easier to read.

Abstract properties

Sometimes you want to define properties in your abstract classes, to do that, use:

from abc import ABC, abstractmethod

class C(ABC):
    @property
    @abstractmethod
    def my_abstract_property(self):
        ...

If you want to use an abstract setter, you'll encounter the mypy Decorated property not supported error, you'll need to add a # type: ignore until this issue is solved.