class MyModel(object): def combined_name(self): return self.first_name + ' ' + self.last_nameAdding the @property decorator to that method allows you to access the computed value of combined_name like an attribute: MyModel().combined_name, instead of having to explicitly trigger the function like: MyModel().combined_name().
How does the @property decorator work?
There's actually a lot going on behind the scenes in Python to make this happen. First of all, the @property decorator is just a convenient way to call the property() function, which is built in to Python. This function returns a special descriptor object which allows direct access to the method's computed value. So instead of doing:
class FooBar(object): def foo(self): return "foo" x = property(foo)You can do:
class FooBar(object): @property def foo(self): return "foo"Both implementations allow you to access the computed value, the string "foo" in this case, by either doing FooBar().x or FooBar().foo. And again, this is possible because the attribute is now pointing to a descriptor object which is triggering the method foo(self) and returning the computed value.
Descriptor objects have one or more of the following methods defined: __get__, __set__, and/or __delete__. Using the property() function, you can set these methods by passing them in as arguments like so: property(fget, fset, fdel), with fget getting assigned to __get__, the fset to __set__, and fdel to __delete__. In our above examples, we're only passing in the fget positional argument. So, when you access the class attribute assigned to the descriptor object, the method we passed in is now assigned to the descriptor object's __get__ method and is triggered, with the computed value being returned.
These special methods also have access to the instance. Which is how something like the following could work:
class FooBar(object): def __init__(self, name='Dylan'): self.name = name @property def foo(self): return "My name is " + self.name + "!"Which is why calling FooBar(name='Mark').foo would evaluate to "My name is Mark!"
Confused still? That's fine. I am too at times. This isn't the easiest of stuff to wrap your head around. Take a look at the "Further Reading" below for more helpful resources about descriptor objects and the property function.
Why use it?
I only recently stumbled upon the @property decorator. Personally, I like to use it for model methods that return computed attributes. For example, in my project www.wikivinci.com, I have an Account model. Each Account instance has a 'points' IntegerField to, well, keep track of each Account's total points. I wrote a basic model method to return the ranking of an Account instance (ie: how many other Account objects are in the database with higher 'points'). This method simply computes an attribute, so I chose to use the @property decorator.
On the other hand, I also wrote the following model method:
def award_points(self, points=0): self.points += points self.save()It doesn't make sense to use the @property decorator here. Since this is a method that's called like a traditional function with arguments.
Django Limitations/Considerations
While using the @property decorator can make it a bit more convenient to access model methods as attributes, you can't filter querysets by them, use them for ordering, or any other operation that happens at the database level. I read somewhere that when using the the @property decorator, the computed value is cached, but I didn't find that to be true during my testing. If someone has more insight around this, and/or other limitations or best practices regarding using custom model method properties, please post them in the comments!
Further Reading
Property function
Descriptors
How does the property decorator work - Stack Overflow