2010-11-26 13 views
5

En el siguiente ejemplo, cached_attr se usa para obtener o establecer un atributo en una instancia modelo cuando se llama a una propiedad de base de datos costosa (related_spam en el ejemplo). En el ejemplo, uso cached_spam para guardar consultas. Puse declaraciones de impresión al configurar y cuando recibo valores para poder probarlo. Lo probé en una vista pasando una instancia de Egg en la vista y en la vista usando {{ egg.cached_spam }}, así como en otros métodos del modelo Egg que hacen llamadas a cached_spam. Cuando terminé y lo probé, la salida de shell en el servidor de desarrollo de Django mostró que la memoria caché de atributos se había omitido varias veces, y que se había obtenido varias veces con éxito. Parece ser inconsistente. Con los mismos datos, cuando hice pequeños cambios (tan poco como cambiar la secuencia de la instrucción de impresión) y los actualicé (con todos los mismos datos), ocurrieron diferentes cantidades de errores/éxitos. ¿Cómo y por qué está sucediendo esto? ¿Es este código incorrecto o altamente problemático?Python: almacenamiento en caché de una propiedad para evitar cálculos futuros

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 
+1

Me encontré con esta molestia y me di cuenta de que otra forma de resolver este problema es usar la etiqueta de la plantilla 'with' para crear un alias para un resultado calculado. – nedned

+0

@humble - thx, eso es realmente una nota bastante útil. – orokusaki

Respuesta

9

Nada malo con su código, hasta donde llega. El problema probablemente no está allí, sino en cómo usas ese código.

Lo principal es que las instancias modelo no tienen identidad. Eso significa que si crea una instancia de un objeto Egg en alguna parte, y una diferente en otra parte, incluso si se refieren a la misma fila subyacente de la base de datos, no compartirán el estado interno. Entonces, llamar al cached_attr en uno no hará que el caché se rellene en el otro.

Por ejemplo, suponiendo que tiene una clase relatedObject con un ForeignKey de huevo:

my_first_egg = Egg.objects.get(pk=1) 
my_related_object = RelatedObject.objects.get(egg__pk=1) 
my_second_egg = my_related_object.egg 

Aquí my_first_egg y my_second_egg ambos se refieren a la fila de base de datos con pk 1, pero son no el mismo objeto:

>>> my_first_egg.pk == my_second_egg.pk 
True 
>>> my_first_egg is my_second_egg 
False 

Así, llenando la memoria caché en my_first_egg no lo llena en my_second_egg.

Y, por supuesto, los objetos no persistirán en las solicitudes (a menos que sean específicamente globales, lo cual es horrible), por lo que la memoria caché tampoco persistirá.

+0

gracias. El ejemplo proporcionado es, creo, exactamente lo que está pasando. Simplemente no puedo entender en qué parte de mi código tengo tantas instancias. Un objeto que debería tener solo 2 instancias separadas basadas en mi revisión del código (una de 'my_related_object.egg') tiene 4 instancias. Creo que es hora de una revisión completa de mi modelo; todas las 200 líneas 8 ( – orokusaki

+0

) He encontrado el culpable. Tenía un método deshonesto que obtenía todos los objetos de nuevo (la búsqueda de mi editor simplemente no lo encontró porque la sintaxis era un poco diferente debido al filtrado). Con eso, combinados con mi método de almacenamiento en caché (simple memorización) y una selección inteligente relacionada obtuve mis 31 consultas hasta 3! Gracias de nuevo por el puntero. – orokusaki

1

Http servidores que escala son compartidos, nada; no puedes confiar en que nada sea singleton. Para compartir estado, necesita conectarse a un servicio especial.

Django's caching support es apropiado para su caso de uso. No es necesariamente un singleton global tampoco; si usa locmem://, será de proceso local, que podría ser la opción más eficiente.

+0

esto es solo para caché por solicitud. Nunca trataría de implementar mi propio marco de almacenamiento en caché. Estoy harto de sacar objetos de la base de datos una y otra vez durante la misma solicitud. – orokusaki

Cuestiones relacionadas