title: Python - caching a property to avoid future calculations
body: <p>In the following example, <code>cached_attr</code> is used to get or set an attribute on a model instance when a database-expensive property (<code>related_spam</code> in the example) is called. In the example, I use <code>cached_spam</code> to save queries. I put print statements when setting and when getting values so that I could test it out. I tested it in a view by passing an <code>Egg</code> instance into the view and in the view using <code>{{ egg.cached_spam }}</code>, as well as other methods on the <code>Egg</code> model that make calls to <code>cached_spam</code> themselves. When I finished and tested it out the shell output in Django's development server showed that the attribute cache was missed several times, as well as successfully gotten several times. It seems to be inconsistent. With the same data, when I made small changes (as little as changing the print statement's string) and refreshed (with all the same data), different amounts of misses / successes happened. How and why is this happening? Is this code incorrect or highly problematic?</p>
  
  <pre><code>class Egg(models.Model):
      ... fields
  
      @property
      def related_spam(self):
          # Each time this property is called the database is queried (expected).
          return Spam.objects.filter(egg=self).all()  # Spam has foreign key to Egg.
  
      @property
      def cached_spam(self):
          # This should call self.related_spam the first time, and then return
          # cached results every time after that.
          return self.cached_attr('related_spam')
  
      def cached_attr(self, attr):
          """This method (normally attached via an abstract base class, but put
          directly on the model for this example) attempts to return a cached
          version of a requested attribute, and calls the actual attribute when
          the cached version isn't available."""
          try:
              value = getattr(self, '_p_cache_{0}'.format(attr))
              print('GETTING - {0}'.format(value))
          except AttributeError:
              value = getattr(self, attr)
              print('SETTING - {0}'.format(value))
              setattr(self, '_p_cache_{0}'.format(attr), value)
          return value
  </code></pre>