Filtro Django versus get para um único objeto?

147

Eu estava discutindo isso com alguns colegas. Existe uma maneira preferida de recuperar um objeto no Django quando você espera apenas um?

As duas maneiras óbvias são:

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

E:

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

if len(objs) == 1:
    obj = objs[0]
else:
    # We have no object! Do something...
    pass

O primeiro método parece comportamentalmente mais correto, mas usa exceções no fluxo de controle que podem introduzir alguma sobrecarga. A segunda é mais indireta, mas nunca gera uma exceção.

Quaisquer pensamentos sobre qual deles é preferível? Qual é mais eficiente?

Cory
fonte

Respostas:

177

get()é fornecido especificamente para este caso . Use-o.

A opção 2 é quase exatamente como o get()método é realmente implementado no Django, portanto não deve haver diferença de "desempenho" (e o fato de você estar pensando nisso indica que está violando uma das principais regras da programação, ou seja, tentar otimizar o código antes mesmo de ser gravado e criar um perfil - até que você tenha o código e possa executá-lo, você não sabe como ele será executado e tentar otimizar antes disso é um caminho complicado).

James Bennett
fonte
Tudo está correto, mas talvez seja necessário adicionar mais informações para responder? 1. O Python incentiva o try / except (consulte EAFP ), é por isso que QS.get()é bom. 2. Detalhes importam: "esperar apenas um" significa sempre 0-1 objetos, ou é possível ter mais de 2 objetos e esse caso também deve ser tratado (neste caso, len(objs)é uma péssima idéia)? 3. Não assuma nada sobre sobrecarga sem uma referência (acho que, nesse caso try/except, será mais rápido enquanto pelo menos metade das chamadas retornar algo))
imposeren
> ou seja, tentar otimizar o código antes mesmo de ser escrito e elaborado um perfil. Essa é uma observação interessante. Eu sempre pensei que deveria pensar na maneira mais opcional de implementar algo antes de implementá-lo. Isso está errado? Você pode elaborar sobre esse ponto? Existe algum recurso que explica isso em detalhes?
Parth Sharma #
Estou surpreso que ninguém tenha mencionado primeiro (). Outros conselhos parecem indicar que é a ligação feita para esse cenário. stackoverflow.com/questions/5123839/…
NeilG
29

Você pode instalar um módulo chamado django-annoying e faça o seguinte:

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
padre
fonte
1
por que é chato ter esse método? parece bom para mim!
Thomas
17

1 está correto. No Python, uma exceção tem sobrecarga igual a um retorno. Para uma prova simplificada, você pode ver isso .

2 É isso que o Django está fazendo no back-end. getchama filtere gera uma exceção se nenhum item for encontrado ou se mais de um objeto for encontrado.

Umair Mohammad
fonte
1
Esse teste é bastante injusto. Uma grande parte da sobrecarga ao lançar uma exceção é a manipulação do rastreamento de pilha. Esse teste teve um comprimento de pilha de 1, que é muito menor do que você normalmente encontraria em um aplicativo.
Rob Young
@Rob Young: Como assim? Onde você vê o manuseio do rastreamento de pilha no esquema típico "pedir perdão em vez de permissão"? O tempo de processamento depende da distância que a exceção percorre, não da profundidade de tudo (quando não estamos escrevendo em java e chamando e.printStackTrace ()). E na maioria das vezes (como na pesquisa de dicionário) - a exceção é lançada logo abaixo do try.
Tomasz Gandor
12

Estou um pouco atrasado para a festa, mas com o Django 1.6 existe o first()método nos conjuntos de consultas.

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


Retorna o primeiro objeto correspondido pelo conjunto de consultas ou Nenhum se não houver nenhum objeto correspondente. Se o QuerySet não tiver uma ordem definida, o conjunto de consultas será automaticamente ordenado pela chave primária.

Exemplo:

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
fonte
Ele não garante que você tenha apenas um objeto em uma consulta
py_dude
8

Não posso falar com nenhuma experiência do Django, mas a opção nº 1 diz claramente ao sistema que você está solicitando um objeto, enquanto a segunda opção não. Isso significa que a opção 1 pode aproveitar mais facilmente os índices de cache ou banco de dados, especialmente onde o atributo no qual você está filtrando não é garantido como único.

Além disso (novamente, especulando), a segunda opção pode ter que criar algum tipo de coleção de resultados ou objeto de iterador, pois a chamada filter () normalmente poderia retornar muitas linhas. Você ignoraria isso com get ().

Por fim, a primeira opção é mais curta e omite a variável temporária extra - apenas uma pequena diferença, mas cada pequena ajuda.

Kylotan
fonte
Nenhuma experiência com o Django, mas ainda assim. Ser explícito, conciso e seguro por padrão, são bons princípios, independentemente da linguagem ou estrutura.
Nevelis
8

Por que todo esse trabalho? Substitua 4 linhas por 1 atalho embutido. (Isso faz sua própria tentativa / exceto).

from django.shortcuts import get_object_or_404

obj = get_object_or_404(MyModel, id=1)
krubo
fonte
1
Isso é ótimo quando é o comportamento desejado, mas, às vezes, você pode querer criar o objeto que está faltando, ou o pull é uma informação opcional.
SingleNegationElimination
2
Isso é o que Model.objects.get_or_create()é para
boatcoder
7

Mais algumas informações sobre exceções. Se eles não são criados, eles custam quase nada. Portanto, se você sabe que provavelmente terá um resultado, use a exceção, pois ao usar uma expressão condicional, você paga o custo de verificar todas as vezes, não importa o quê. Por outro lado, eles custam um pouco mais do que uma expressão condicional quando são aumentados; portanto, se você espera não ter um resultado com alguma frequência (digamos, 30% do tempo, se a memória servir), a verificação condicional acaba ser um pouco mais barato.

Mas este é o ORM do Django, e provavelmente a ida e volta ao banco de dados, ou mesmo um resultado em cache, provavelmente domina as características de desempenho, portanto favorece a legibilidade, neste caso, já que você espera exatamente um resultado, use get().

SingleNegationElimination
fonte
4

Eu brinquei um pouco com esse problema e descobri que a opção 2 executa duas consultas SQL, o que para uma tarefa tão simples é excessivo. Veja minha anotação:

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

Uma versão equivalente que executa uma única consulta é:

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]

Ao mudar para essa abordagem, consegui reduzir substancialmente o número de consultas que meu aplicativo executa.

Jan Wrobel
fonte
1

Pergunta interessante, mas para mim a opção 2 cheira a otimização prematura. Não sei ao certo qual é o melhor desempenho, mas a opção 1 certamente parece e parece mais pitônica para mim.

John McCollum
fonte
1

Eu sugiro um design diferente.

Se você deseja executar uma função em um resultado possível, pode derivar do QuerySet, desta forma: http://djangosnippets.org/snippets/734/

O resultado é impressionante, você pode, por exemplo:

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

Aqui, o filtro retorna um conjunto de consultas vazio ou um conjunto de consultas com um único item. Suas funções personalizadas do conjunto de consultas também são encadeadas e reutilizáveis. Se você quiser que o faça por todas as suas entradas: MyModel.objects.all().yourFunction().

Eles também são ideais para serem usados ​​como ações na interface administrativa:

def yourAction(self, request, queryset):
    queryset.yourFunction()
joctee
fonte
0

A opção 1 é mais elegante, mas use try..except.

Pela minha própria experiência, posso lhe dizer que, às vezes, você tem certeza de que não pode haver mais de um objeto correspondente no banco de dados, e ainda haverá dois ... (exceto, é claro, ao obter o objeto por sua chave primária).

zooglash
fonte
0

Lamento adicionar mais uma opinião sobre esse problema, mas estou usando o paginador django e, no meu aplicativo de administração de dados, o usuário pode escolher o que consultar. Às vezes, esse é o ID de um documento, mas, caso contrário, é uma consulta geral retornando mais de um objeto, ou seja, um conjunto de consultas.

Se o usuário consultar o ID, eu posso executar:

Record.objects.get(pk=id)

que gera um erro no paginador do django, porque é um registro e não um conjunto de registros de consultas.

Eu preciso executar:

Record.objects.filter(pk=id)

O que retorna um Queryset com um item. Então o paginador funciona muito bem.

excyberlabber
fonte
Para usar o paginador - ou qualquer funcionalidade que espera um QuerySet - sua consulta deve retornar um QuerySet. Não alterne entre usar .filter () e .get (), use .filter () e forneça o filtro "pk = id", como você já percebeu. Esse é o padrão para este caso de uso.
Cornel Masson
0

.obter()

Retorna o objeto que corresponde aos parâmetros de pesquisa fornecidos, que devem estar no formato descrito em Pesquisas de campo.

get () gera MultipleObjectsReturned se mais de um objeto for encontrado. A exceção MultipleObjectsReturned é um atributo da classe de modelo.

get () gera uma exceção DoesNotExist se um objeto não foi encontrado para os parâmetros fornecidos. Essa exceção também é um atributo da classe de modelo.

.filtro()

Retorna um novo QuerySet contendo objetos que correspondem aos parâmetros de pesquisa fornecidos.

Nota

use get () quando quiser obter um único objeto único e filter () quando quiser obter todos os objetos que correspondem aos seus parâmetros de pesquisa.

Razia Khan
fonte