Luke Taylor
Luke Taylor

Reputation: 9561

Declaring class attributes in __init__ vs with @property

If I'm creating a class that needs to store properties, when is it appropriate to use an @property decorator and when should I simply define them in __init__?

The reasons I can think of:

Say I have a class like

class Apple:
    def __init__(self):
        self.foodType = "fruit"
        self.edible = True
        self.color = "red"

This works fine. In this case, it's pretty clear to me that I shouldn't write the class as:

class Apple:
    @property
    def foodType(self):
        return "fruit"
    @property
    def edible(self):
        return True
    @property
    def color(self):
        return "red"

But say I have a more complicated class, which has slower methods (say, fetching data over the internet).

I could implement this assigning attributes in __init__:

class Apple:
    def __init__(self):
        self.wikipedia_url = "https://en.wikipedia.org/wiki/Apple"
        self.wikipedia_article_content = requests.get(self.wikipedia_url).text

or I could implement this with @property:

class Apple:
    def __init__(self):
        self.wikipedia_url = "https://en.wikipedia.org/wiki/Apple"

    @property
    def wikipedia_article_content(self):
        return requests.get(self.wikipedia_url).text

In this case, the latter is about 50,000 times faster to instantiate. However, I could argue that if I were fetching wikipedia_article_content multiple times, the former is faster:

a = Apple()
a.wikipedia_article_content
a.wikipedia_article_content
a.wikipedia_article_content

In which case, the former is ~3 times faster because it has one third the number of requests.

My question

Is the only difference between assigning properties in these two ways the ones I've thought of? What else does @property allow me to do other than save time (in some cases)? For properties that take some time to assign, is there a "right way" to assign them?

Upvotes: 2

Views: 822

Answers (2)

MSeifert
MSeifert

Reputation: 152647

Yes, I would suggest using property for those arguments. If you want to make it lazy or cached you can subclass property.

This is just an implementation of a lazy property. It does some operations inside the property and returns the result. This result is saved in the class with another name and each subsequent call on the property just returns the saved result.

class LazyProperty(property):
    def __init__(self, *args, **kwargs):
        # Let property set everything up
        super(LazyProperty, self).__init__(*args, **kwargs)
        # We need a name to save the cached result. If the property is called
        # "test" save the result as "_test".
        self._key = '_{0}'.format(self.fget.__name__)


    def __get__(self, obj, owner=None):
        # Called on the class not the instance
        if obj is None:
            return self

        # Value is already fetched so just return the stored value
        elif self._key in obj.__dict__:
            return obj.__dict__[self._key]

        # Value is not fetched, so fetch, save and return it
        else:
            val = self.fget(obj)
            obj.__dict__[self._key] = val
            return val

This allows you to calculate the value once and then always return it:

class Test:
    def __init__(self):
        pass

    @LazyProperty
    def test(self):
        print('Doing some very slow stuff.')
        return 100

This is how it would work (obviously you need to adapt it for your case):

>>> a = Test()
>>> a._test   # The property hasn't been called so there is no result saved yet.
AttributeError: 'Test' object has no attribute '_test'
>>> a.test    # First property access will evaluate the code you have in your property
Doing some very slow stuff.
100
>>> a.test    # Accessing the property again will give you the saved result
100
>>> a._test   # Or access the saved result directly
100

Upvotes: 2

Ignacio Vazquez-Abrams
Ignacio Vazquez-Abrams

Reputation: 798666

Using a property allows for more complex behavior. Such as fetching the article content only when it has changed and only after a certain time period has passed.

Upvotes: 2

Related Questions