2009-06-19 17 views
108

Estaba teniendo un debate sobre esto con algunos colegas. ¿Hay una forma preferida de recuperar un objeto en Django cuando esperas solo uno?Django filter versus get for single object?

Los dos maneras obvias son:

try: 
     obj = MyModel.objects.get(id=1) 
    except MyModel.DoesNotExist: 
     # we have no object! do something 
     pass 

y

objs = MyModel.objects.filter(id=1) 
    if len(objs) == 1: 
     obj = objs[0] 
    else: 
     # we have no object! do something 
     pass 

El primer método parece comportamiento más correcto, pero utiliza excepciones en flujo de control que pueden introducir algo de sobrecarga. El segundo es más indirecto pero nunca levantará una excepción.

¿Alguna idea de cuál de estos es preferible? ¿Cuál es más eficiente?

Respuesta

137

get() se proporciona específicamente para este caso. Úselo.

Opción 2 es casi exactamente cómo el método get() se implementa realmente en Django, por lo que no debería haber diferencia de "rendimiento" (y el hecho de que esté pensando lo indica que está violando una de las reglas cardinales de programación, es decir, tratando de optimizar el código incluso antes de que se haya escrito y perfilado; hasta que tenga el código y pueda ejecutarlo, no sabe cómo funcionará, y tratar de optimizarlo antes es un camino de dolor).

1

Pregunta interesante, pero para mí la opción # 2 huele a optimización prematura. No estoy seguro de cuál es más eficiente, pero la opción n. ° 1 ciertamente se ve y se siente más pitónica para mí.

6

No puedo hablar con ninguna experiencia de Django pero la opción n. ° 1 le dice claramente al sistema que usted está pidiendo 1 objeto, mientras que la segunda opción no. Esto significa que la opción n. ° 1 podría aprovecharse más fácilmente de los índices de caché o de base de datos, especialmente donde no se garantiza que el atributo que está filtrando sea único.

También (de nuevo, especulando) la segunda opción puede tener que crear algún tipo de colección de resultados u objeto iterador ya que la llamada al filtro() normalmente podría devolver muchas filas. Evitarías esto con get().

Finalmente, la primera opción es más corta y omite la variable temporal adicional, solo una pequeña diferencia, pero todo ayuda.

+0

Sin experiencia con Django, pero todavía en el clavo. Ser explícito, concisa y segura por defecto, son buenos principios sin importar el idioma o el marco. – nevelis

13

1 es correcto. En Python, una excepción tiene una sobrecarga igual a un retorno. Para una prueba simplificada, puede mirar this.

2 Esto es lo que Django está haciendo en el back-end. get llama al filter y genera una excepción si no se encuentra ningún elemento o si se encuentra más de un objeto.

+1

Esa prueba es bastante injusta. Una gran parte de la sobrecarga al lanzar una excepción es el manejo del rastro de la pila. Esa prueba tenía una longitud de pila de 1, que es mucho más baja de lo que normalmente se encontraría en una aplicación. –

+0

@Rob Young: ¿Qué quieres decir? ¿Dónde ves el manejo de traza de pila en el típico esquema de "pedir perdón en lugar de permiso"? El tiempo de procesamiento depende de la distancia que recorra la excepción, no de cuán profundo sea todo (cuando no estamos escribiendo en java y llamando a e.printStackTrace()). Y con mayor frecuencia (como en la búsqueda del diccionario): la excepción se produce justo debajo del 'try'. –

5

¿Por qué funciona todo eso? Reemplace 4 líneas con 1 atajo incorporado. (Esto hace su propio try/except.)

from django.shortcuts import get_object_or_404 

obj = get_object_or_404(MyModel, id=1) 
+1

Esto es genial cuando es el comportamiento deseado, pero a veces, es posible que desee crear el objeto que falta, o la extracción fue información opcional. – SingleNegationElimination

+1

Eso es lo que 'Model.objects.get_or_create()' es para – boatcoder

6

Más información sobre excepciones. Si no se crían, cuestan casi nada. Por lo tanto, si sabe que probablemente obtendrá un resultado, use la excepción, ya que al usar una expresión condicional, paga el costo de verificar cada vez, pase lo que pase. Por otro lado, cuestan un poco más que una expresión condicional cuando se generan, por lo que si esperas no tener un resultado con cierta frecuencia (por ejemplo, 30% del tiempo, si la memoria sirve), se obtiene la verificación condicional ser un poco mas barato

Pero este es el ORM de Django, y probablemente el viaje de ida y vuelta a la base de datos, o incluso un resultado en caché, domine las características de rendimiento, por lo que es preferible leerlo, en este caso, ya que espera exactamente un resultado, use get().

27

Puede instalar un módulo llamado django-annoying y luego hacer esto:

from annoying.functions import get_object_or_None 

obj = get_object_or_None(MyModel, id=1) 

if not obj: 
    #omg the object was not found do some error stuff 
+0

¿por qué es molesto tener dicho método? luce bien para mi ! – Thomas

+2

@Thomas Creo que la idea es que es molesto NO tener ese método ... – user193130

0

La opción 1 es más elegante, pero asegúrese de usar try..except.

Según mi propia experiencia puedo decirles que a veces está seguro de que no puede haber más de un objeto coincidente en la base de datos, y sin embargo habrá dos ... (excepto, por supuesto, al obtener el objeto por su Clave primaria).

1

Sugiero un diseño diferente.

Si desea realizar una función sobre un posible resultado, se podría derivar de QuerySet, así: http://djangosnippets.org/snippets/734/

El resultado es bastante impresionante, usted podría, por ejemplo:

MyModel.objects.filter(id=1).yourFunction() 

Aquí, filter devuelve un conjunto de consulta vacío o un conjunto de consulta con un solo elemento. Sus funciones de conjunto de preguntas personalizadas también son encadenables y reutilizables. Si desea realizarlo para todas sus entradas: MyModel.objects.all().yourFunction().

También son ideales para ser utilizados como acciones en la interfaz de administración:

def yourAction(self, request, queryset): 
    queryset.yourFunction() 
3

He jugado con este problema un poco y descubrí que la opción 2 ejecuta dos consultas SQL, que para un simple tal la tarea es excesiva Véase mi anotación:

objs = MyModel.objects.filter(id=1) # This does not execute any SQL 
if len(objs) == 1: # This executes SELECT COUNT(*) FROM XXX WHERE filter 
    obj = objs[0] # This executes SELECT x, y, z, .. FROM XXX WHERE filter 
else: 
    # we have no object! do something 
    pass 

Una versión equivalente que ejecuta una sola consulta es:

items = [item for item in MyModel.objects.filter(id=1)] # executes SELECT x, y, z FROM XXX WHERE filter 
count = len(items) # Does not execute any query, items is a standard list. 
if count == 0: 
    return None 
return items[0] 

Al cambiar a este enfoque, yo era capaz de reducir sustancialmente el número de consultas de mi aplicación se ejecuta.

6

Llego un poco tarde a la fiesta, pero con Django 1.6 existe el método first() en querysets.

https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.first


Devuelve el primer objeto emparejado por el queryset o Ninguno si no hay objeto coincidente. Si QuerySet no tiene ningún orden definido, entonces el conjunto de consulta se ordena automáticamente por la clave primaria.

Ejemplo:

p = Article.objects.order_by('title', 'pub_date').first() 
Note that first() is a convenience method, the following code sample is equivalent to the above example: 

try: 
    p = Article.objects.order_by('title', 'pub_date')[0] 
except IndexError: 
    p = None 
Cuestiones relacionadas