¿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?

Cory
fuente

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).

James Bennett
fuente
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
sacerdote
fuente
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.

Umair Mohammad
fuente
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
BastiBen
fuente
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.

Kylotan
fuente
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)
krubo
fuente
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().

SingleNegationElimination
fuente
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.

Jan Wrobel
fuente
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.

John McCollum
fuente
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()
joctee
fuente
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).

zooglash
fuente
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.

excyberlabber
fuente
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.

Razia Khan
fuente