13

What's the difference between a class property and a class method? As I understand it, property is calculated when an object is created. And method makes calculations when I call it.

Is there any other difference than that?

For example, I have a property in my class Product():

@property
    def total_ammount_in_store(self):
        consignments = self.product.package.consignments
        total_ammount = 0
        for consignment in consignments:
            total_ammount += consignment.package_ammount

When I render some page, I pass some products. For example: {'products':Product.objects.filter(expiration_data < datetime.now())

I don't need to calculate total_ammount_in_store every time I get an instance of Product. What if I just need to calculate it when I call it in a template: {{product.total_ammount_in_store}}? Is it possible?

Is method also calculated when the object is created?

jeffhale
  • 3,759
  • 7
  • 40
  • 56
Andrew
  • 423
  • 1
  • 6
  • 16

2 Answers2

15

The property is called each time you access product.total_ammount_in_store, not at the time when the product is created.

Therefore including {{ product.total_ammount_in_store }} in your template will do the right thing.

By using the property decorator, you can access product.total_ammount_in_store instead of product.total_ammount_in_store() if it was an instance method. In the Django template language, this difference is not so apparent, because Django will call the method automatically in the template.

Don't confuse an instance method with a class method, which is quite different. A class method belongs to your class Product, not an individual instance product. You don't have access to instance variables e.g. self.package when you call a class method.

Alasdair
  • 298,606
  • 55
  • 578
  • 516
  • When I try to debug my project, I noticed that when I pass in `render` my products, `total_ammount_in_store` property is executed too. – Andrew Apr 30 '15 at 13:27
  • I don't use my property in my template. But that property is executed. – Andrew Apr 30 '15 at 13:41
  • @Andrew This sounds like you actually are using it in the template or in the view. Can you show us the code? – geckon Apr 30 '15 at 13:48
  • Raise an exception inside the property. The traceback should show you the code that is accessing the property. – Alasdair Apr 30 '15 at 14:02
  • 1
    @Andrew, use Alasdair's idea with the exception. – geckon Apr 30 '15 at 14:08
  • @Alasdair yes, I found it. In template I use `is_available` property that is based on `total_amount_in_store`. Ty – Andrew Apr 30 '15 at 14:11
  • @Alasdair But what's the difference if `total_ammount_in_store` is a `property` or a class method? It will return result in both cases, when I access it `{{product.total_ammount_in_store}}` – Andrew Apr 30 '15 at 14:42
  • You are right, that there is no difference between accessing a property or instance method in the Django template. I've updated my answer to try to make it clearer. – Alasdair Apr 30 '15 at 14:49
  • 1
    @Andrew, please beware of the difference between class method and instance method. – geckon Apr 30 '15 at 14:49
3

The @property decorator can be used to implement a getter for your class' instance variable (in your case it would be self.total_ammount_in_store). Every time you call some_product.total_ammount_in_store, the decorated method is executed. It wouldn't make sense to execute it only when a new object is created - you want to get current amount in store, don't you? More reading on @property is in Python documentation (it's a Python's construct, not Django's):

https://docs.python.org/2/library/functions.html#property

As for class methods, they are something completely different. As the name suggests, they're tied to classes, not instances. Therefore, no instance is needed to call a class method, but also you can't use any instance variables in class methods (because they are tied to a particular instance).

To the Django related part of your question...

If you include {{ some_product.total_ammount_in_store }} in your template, then every time the page is displayed, the total amount in store is obtained from the some_product instance. Which means that the decorated total_ammount_in_store getter is called.

If for example the total amount in store isn't changed during the product's life, you can calculate the amount in __init__ method and then only return the value. If the total amount can change you can do it as well but you will need to ensure that the amount is re-calculated every time it should be changed - e.g. by calling a method. Like this:

class Product(object):
    def __init__(self):
        # ...
        # other initialization
        # ...
        self.recalculate_amount()

    def recalculate_amount(self):
        consignments = self.product.package.consignments
        self._total_amount = 0
        for consignment in consignments:
            self._total_amount += consignment.package_amount

    @property
    def total_amount(self):
        """Get the current total amount in store."""
        return self._total_amount

Then the getter is still called every time you call some_product.total_ammount_in_store (e.g. in your Django template), but it will not calculate the amount every time - it will use the stored amount instead.

geckon
  • 8,316
  • 4
  • 35
  • 59
  • A question about last part of your answer: for example, when I write in view next code: `Product.objects.filter(some_condition)`, it will recalculate total amount for every instance? – Andrew Apr 30 '15 at 13:53
  • @Andrew This actually depends on what `some_condition` is. If you use something like `Product.objects.filter(total_amount < 100)` then with my code example, it won't recalculate every time. It will call the `total_amount()` method for every `Product` instance, but the method will only return the stored number. The stored number will be recalculated only if you call the `recalculate_amount()` method. Which might be what you want and also might be something else than you actually want. – geckon Apr 30 '15 at 13:56
  • Ok, I try to put another question: `__init__ `method is called only when object is created, or even when we call `Product.objects.filter(total_amount < 100)` ? – Andrew Apr 30 '15 at 14:02
  • @Andrew Only when a new object is created. For more information read the method's documentation: https://docs.python.org/2/reference/datamodel.html#object.__init__ – geckon Apr 30 '15 at 14:06
  • so, when I access `product.total_amount` property as in your code, it can return old value of `total_amount`, because I didn't call `recalculate_amount` method before? – Andrew Apr 30 '15 at 14:14
  • @Andrew, yes. It will only be updated by the `recalculate_amount` method. Actually you can add an alternative way to update the amount (for example add another method `ship(self, how_many)` that will subtract `how_many` from `self._total_amount` etc.), but the total amount won't be recalculated every time you get its value. – geckon Apr 30 '15 at 14:20
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/76663/discussion-between-andrew-and-geckon). – Andrew Apr 30 '15 at 17:15