¿Filtro Django versus get para un solo objeto?


147

Estaba teniendo un debate sobre esto con algunos colegas. ¿Hay una forma preferida de recuperar un objeto en Django cuando solo espera uno?

Las dos formas 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 conductualmente más correcto, pero utiliza excepciones en el flujo de control que pueden introducir algo de sobrecarga. El segundo es más indirecto, pero nunca planteará una excepción.

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

Respuestas:


177

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

La opción 2 es casi exactamente cómo get()se implementa realmente el método en Django, por lo que no debería haber una diferencia de "rendimiento" (y el hecho de que esté pensando en ello indica que está violando una de las reglas cardinales de programación, es decir, tratar de optimice 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 optimizar antes es un camino de dolor).


Todo es correcto, pero ¿quizás se deba agregar más información para responder? 1. Python recomienda probar / excepto (ver EAFP ), por eso QS.get()es bueno. 2. Los detalles importan: ¿"esperar solo uno" significa siempre 0-1 objetos, o es posible tener 2+ objetos y ese caso también debería ser manejado (en este caso len(objs)es una idea terrible)? 3. No asuma nada sobre los gastos generales sin un punto de referencia (creo que en este caso try/exceptserá más rápido siempre que al menos la mitad de las llamadas devuelvan algo)
imponga

> a saber, tratar de optimizar el código antes de que incluso se haya escrito y perfilado. Esta es una observación interesante. Siempre pensé que debería pensar en la forma más opcional de implementar algo antes de implementarlo. ¿Es eso incorrecto? ¿Puedes dar más detalles sobre este punto? ¿Hay algún recurso que explique esto en detalle?
Parth Sharma

Me sorprende que nadie haya mencionado primero (). Otro consejo parece indicar que es la llamada hecha para este escenario. stackoverflow.com/questions/5123839/…
NeilG

29

Puede instalar un módulo llamado django-molesto 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

1
¿Por qué es molesto tener ese método? luce bien para mi !
Thomas

17

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

2 Esto es lo que Django está haciendo en el backend. getllama filtery 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 seguimiento de la pila. Esa prueba tenía una longitud de pila de 1 que es mucho más baja de lo que normalmente encontraría en una aplicación.
Rob Young

@Rob Young: ¿Qué quieres decir? ¿Dónde ve el manejo del seguimiento de la pila en el esquema típico de "pedir perdón en lugar de permiso"? El tiempo de procesamiento depende de la distancia que recorre la excepción, no de qué tan profundo sucede todo (cuando no estamos escribiendo en Java y llamando a e.printStackTrace ()). Y con mayor frecuencia (como en la búsqueda de diccionario): la excepción se produce justo debajo de try.
Tomasz Gandor

12

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

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


Devuelve el primer objeto que coincide con el conjunto de consultas, o Ninguno si no hay ningún objeto coincidente. Si el QuerySet no tiene un orden definido, entonces el conjunto de consultas 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

No garantiza que solo tenga un objeto en una consulta
py_dude

8

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

Además (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 filter () 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 cada pequeña ayuda.


No hay experiencia con Django, pero aún así es perfecto. Ser explícitos, concisos y seguros por defecto, son buenos principios sin importar el lenguaje o el marco.
nevelis

8

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

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

2
Para eso Model.objects.get_or_create()es
barcocoder

7

Algo más de información sobre excepciones. Si no son criados, no 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 usted paga el costo de verificar cada vez, sin importar qué. Por otro lado, cuestan un poco más que una expresión condicional cuando se generan, por lo que si espera no tener un resultado con cierta frecuencia (por ejemplo, el 30% del tiempo, si la memoria sirve), la verificación condicional resulta ser un poco más 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é, probablemente domine las características de rendimiento, por lo tanto, favorezca la legibilidad, en este caso, ya que espera exactamente un resultado, use get().


4

Jugué un poco con este problema y descubrí que la opción 2 ejecuta dos consultas SQL, que para una tarea tan simple es excesiva. Mira 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, pude reducir sustancialmente el número de consultas que ejecuta mi aplicación.


1

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


1

Sugiero un diseño diferente.

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

El resultado es bastante impresionante, podrías por ejemplo:

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

Aquí, filter devuelve un conjunto de consultas vacío o un conjunto de consultas con un solo elemento. Sus funciones de conjunto de consultas 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()

0

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

Según mi propia experiencia, puedo decirle 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, cuando obtiene el objeto por su clave principal).


0

Lamento agregar una versión más sobre este tema, pero estoy usando el paginador django, y en mi aplicación de administración de datos, el usuario puede elegir qué consultar. A veces, esa es la identificación de un documento, pero de lo contrario es una consulta general que devuelve más de un objeto, es decir, un conjunto de consultas.

Si el usuario consulta la identificación, puedo ejecutar:

Record.objects.get(pk=id)

que arroja un error en el paginador de django, porque es un registro y no un conjunto de consultas de registros.

Necesito correr:

Record.objects.filter(pk=id)

Que devuelve un conjunto de consultas con un elemento en él. Entonces el paginador funciona bien.


Para usar el paginador, o cualquier funcionalidad que espere un QuerySet, su consulta debe devolver un QuerySet. No cambie entre usar .filter () y .get (), quédese con .filter () y suministre el filtro "pk = id", como ya se dio cuenta. Ese es el patrón para este caso de uso.
Cornel Masson

0

.obtener()

Devuelve el objeto que coincide con los parámetros de búsqueda dados, que deben estar en el formato descrito en las búsquedas de campo.

get () plantea MultipleObjectsReturned si se encontró más de un objeto. La excepción MultipleObjectsReturned es un atributo de la clase de modelo.

get () genera una excepción DoesNotExist si no se encontró un objeto para los parámetros dados. Esta excepción también es un atributo de la clase de modelo.

.filtrar()

Devuelve un nuevo QuerySet que contiene objetos que coinciden con los parámetros de búsqueda dados.

Nota

use get () cuando desee obtener un único objeto único y filter () cuando desee obtener todos los objetos que coincidan con sus parámetros de búsqueda.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.