Как получить объект, если он существует, или None, если он не существует?


223

Когда я прошу менеджера модели получить объект, он возникает, DoesNotExistкогда нет подходящего объекта.

go = Content.objects.get(name="baby")

Вместо того DoesNotExist, как я могу goбыть Noneвместо этого?

Ответы:


332

Не существует встроенного способа сделать это. Django будет вызывать исключение DoesNotExist каждый раз. Идиоматический способ справиться с этим в python - это обернуть его в попытку:

try:
    go = SomeModel.objects.get(foo='bar')
except SomeModel.DoesNotExist:
    go = None

То, что я сделал, это подкласс моделей. Менеджер, создайте safe_getподобный код выше и используйте этот менеджер для моих моделей. Таким образом , вы можете написать: SomeModel.objects.safe_get(foo='bar').


7
Хорошее использование SomeModel.DoesNotExist вместо импорта исключения тоже.
супермитч

197
Это решение длиной в четыре строки. Для меня это слишком много. С django 1.6 вы можете использовать SomeModel.objects.filter(foo='bar').first()это возвращает первое совпадение или None. Это не подведет, если есть несколько примеров, какqueryset.get()
Геттли

9
Я думаю, что это плохой стиль - чрезмерно использовать исключения для обработки случаев по умолчанию. Да, «проще просить прощения, чем разрешения». Но исключение все еще должно использоваться, на мой взгляд, для исключений.
Константин Шуберт

8
Явное лучше, чем неявное. Если нет причин использовать производительность, filter().first()я думаю, что исключение - это путь.
Кристианбунди

6
Использование first () - хорошая идея, если вам все равно, когда есть кратные значения. В противном случае это решение является превосходным, поскольку оно все равно выдаст исключение, если вы неожиданно найдете несколько объектов, что обычно и происходит в этом случае.
Россдавид

182

Начиная с django 1.6, вы можете использовать метод first () следующим образом:

Content.objects.filter(name="baby").first()

26
В этом случае ошибка не возникает, если найдено более одного совпадения.
Константин Шуберт

7
«FeroxTL», вы должны указать @guettli за этот ответ, поскольку он прокомментировал это с принятым ответом за год до вашего поста.
colm.anseo

7
@colminator Я бы скорее сказал, что Геттли должен узнать, что новый ответ не относится к комментариям, если он хочет повысить свою репутацию stackoverflow :) FeroxTL должен получать баллы за то, что что-то скрытое в комментарии становится более понятным в качестве ответа. Я думаю, что ваш комментарий достаточно кредитный для guettli и не должен быть добавлен к ответу, если это было ваше предложение.
Иоаким

3
@Joakim У меня нет проблем с публикацией нового «ответа» - просто чтобы отдать должное, где это необходимо :-)
colm.anseo

3
Как насчет выполнения этого подхода по сравнению с принятым ответом?
MaxCore

36

Из Django документов

get()вызывает DoesNotExistисключение, если объект не найден для данных параметров. Это исключение также является атрибутом класса модели. В DoesNotExist исключение наследует отdjango.core.exceptions.ObjectDoesNotExist

Вы можете поймать исключение и назначить Noneидти.

from django.core.exceptions import ObjectDoesNotExist
try:
    go  = Content.objects.get(name="baby")
except ObjectDoesNotExist:
    go = None

33

Вы можете создать универсальную функцию для этого.

def get_or_none(classmodel, **kwargs):
    try:
        return classmodel.objects.get(**kwargs)
    except classmodel.DoesNotExist:
        return None

Используйте это как ниже:

go = get_or_none(Content,name="baby")

go будет None, если ни одна запись не соответствует, иначе вернет запись Content.

Примечание. Возникает исключение MultipleObjectsReturned, если для name = "baby" возвращено более одной записи.



15

Чтобы упростить задачу, вот фрагмент кода, который я написал, на основе входных данных из замечательных ответов здесь:

class MyManager(models.Manager):

    def get_or_none(self, **kwargs):
        try:
            return self.get(**kwargs)
        except ObjectDoesNotExist:
            return None

И тогда в вашей модели:

class MyModel(models.Model):
    objects = MyManager()

Вот и все. Теперь у вас есть MyModel.objects.get (), а также MyModel.objetcs.get_or_none ()


7
Кроме того, не забудьте импортировать: из django.core.exceptions import ObjectDoesNotExist
Моти Радомски

13

Вы можете использовать existsс фильтром:

Content.objects.filter(name="baby").exists()
#returns False or True depending on if there is anything in the QS

просто альтернатива, если вы только хотите знать, существует ли он


4
Это вызвало бы дополнительный вызов базы данных, когда существует. Не очень хорошая идея
Кристофер

@Christoffer не уверен, почему это будет дополнительный вызов БД. В соответствии с документами :Note: If you only want to determine if at least one result exists (and don’t need the actual objects), it’s more efficient to use exists().
Anupam

2
@Christoffer Я думаю, ты прав. Теперь я снова читаю вопрос, и ОП на самом деле хочет вернуть реальный объект. Поэтому exists()будет использоваться с ifпредложением перед извлечением объекта, что приведет к двойному попаданию в БД. Я все еще буду держать комментарий в случае, если он поможет кому-то еще.
Anupam

7

Обработка исключений в разных точках в ваших представлениях может быть очень трудоемкой. Как насчет определения собственного менеджера моделей в файле models.py, например

class ContentManager(model.Manager):
    def get_nicely(self, **kwargs):
        try:
            return self.get(kwargs)
        except(KeyError, Content.DoesNotExist):
            return None

а затем включить его в класс модели содержимого

class Content(model.Model):
    ...
    objects = ContentManager()

Таким образом, это может быть легко решено в представлениях, т.е.

post = Content.objects.get_nicely(pk = 1)
if post:
    # Do something
else:
    # This post doesn't exist

1
Мне очень нравится это решение, но я не смог заставить его работать как есть при использовании Python 3.6. Хотел оставить записку, которая изменяет возврат в ContentManager, чтобы заставить return self.get(**kwargs)его работать на меня. Не говорить, что с ответом что-то не так, просто совет для тех, кто пытается использовать его с более поздними версиями (или с тем, что мешало мне работать).
Скагзилла

7

Это одна из тех раздражающих функций, которые вы, возможно, не захотите повторно реализовывать:

from annoying.functions import get_object_or_None
#...
user = get_object_or_None(Content, name="baby")

Я проверил код, get_object_or_Noneно обнаружил, что он все еще вызывает, MultipleObjectsReturnedесли более одного объекта. Таким образом, пользователь может рассмотреть окружение с try-except(которое уже есть в самой функции try-except).
Джон Пан

4

Если вы хотите простое однострочное решение, которое не включает обработку исключений, условные операторы или требование Django 1.6+, сделайте это вместо этого:

x = next(iter(SomeModel.objects.filter(foo='bar')), None)

4

Я думаю, что это не плохая идея использовать get_object_or_404()

from django.shortcuts import get_object_or_404

def my_view(request):
    my_object = get_object_or_404(MyModel, pk=1)

Этот пример эквивалентен:

from django.http import Http404

def my_view(request):
    try:
        my_object = MyModel.objects.get(pk=1)
    except MyModel.DoesNotExist:
        raise Http404("No MyModel matches the given query.")

Вы можете прочитать больше о get_object_or_404 () в онлайн документации django.


2

С django 1.7 вы можете делать следующее:

class MyQuerySet(models.QuerySet):

    def get_or_none(self, **kwargs):
        try:
            return self.get(**kwargs)
        except self.model.DoesNotExist:
            return None


class MyBaseModel(models.Model):

    objects = MyQuerySet.as_manager()


class MyModel(MyBaseModel):
    ...

class AnotherMyModel(MyBaseModel):
    ...

Преимущество «MyQuerySet.as_manager ()» заключается в том, что оба следующих варианта будут работать:

MyModel.objects.filter(...).get_or_none()
MyModel.objects.get_or_none()

1

Вот вариант вспомогательной функции, которая позволяет вам при желании передать QuerySetэкземпляр, если вы хотите получить уникальный объект (если имеется) из набора запросов, отличного от набора запросов allобъектов модели (например, из подмножества дочерних элементов, принадлежащих родительский экземпляр):

def get_unique_or_none(model, queryset=None, **kwargs):
    """
        Performs the query on the specified `queryset`
        (defaulting to the `all` queryset of the `model`'s default manager)
        and returns the unique object matching the given
        keyword arguments.  Returns `None` if no match is found.
        Throws a `model.MultipleObjectsReturned` exception
        if more than one match is found.
    """
    if queryset is None:
        queryset = model.objects.all()
    try:
        return queryset.get(**kwargs)
    except model.DoesNotExist:
        return None

Это можно использовать двумя способами, например:

  1. obj = get_unique_or_none(Model, **kwargs) как и ранее обсуждалось
  2. obj = get_unique_or_none(Model, parent.children, **kwargs)

1

Без исключений:

if SomeModel.objects.filter(foo='bar').exists():
    x = SomeModel.objects.get(foo='bar')
else:
    x = None

Используя исключение:

try:
   x = SomeModel.objects.get(foo='bar')
except SomeModel.DoesNotExist:
   x = None

Существует немного спора о том, когда следует использовать исключение в Python. С одной стороны, «проще просить прощения, чем разрешения». Хотя я согласен с этим, я считаю, что исключение должно оставаться, ну, исключение, и «идеальный случай» должен выполняться, не затрагивая его.


1

Мы можем использовать встроенное исключение Django, которое прикреплено к моделям, названным как .DoesNotExist. Таким образом, мы не должны импортировать ObjectDoesNotExistисключения.

Вместо этого:

from django.core.exceptions import ObjectDoesNotExist

try:
    content = Content.objects.get(name="baby")
except ObjectDoesNotExist:
    content = None

Мы можем это сделать:

try:
    content = Content.objects.get(name="baby")
except Content.DoesNotExist:
    content = None

0

Это подражатель из get_object_or_404 Джанго за исключением того, что метод возвращает None. Это чрезвычайно полезно, когда нам нужно использовать only()запрос, чтобы получить только определенные поля. Этот метод может принимать модель или набор запросов.

from django.shortcuts import _get_queryset


def get_object_or_none(klass, *args, **kwargs):
    """
    Use get() to return an object, or return None if object
    does not exist.
    klass may be a Model, Manager, or QuerySet object. All other passed
    arguments and keyword arguments are used in the get() query.
    Like with QuerySet.get(), MultipleObjectsReturned is raised if more than
    one object is found.
    """
    queryset = _get_queryset(klass)
    if not hasattr(queryset, 'get'):
        klass__name = klass.__name__ if isinstance(klass, type) else klass.__class__.__name__
        raise ValueError(
            "First argument to get_object_or_none() must be a Model, Manager, "
            "or QuerySet, not '%s'." % klass__name
        )
    try:
        return queryset.get(*args, **kwargs)
    except queryset.model.DoesNotExist:
        return None

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.