Tek nesne için get karşı Django filtre?


147

Bazı meslektaşlarımla bu konuda bir tartışma yapıyordum. Yalnızca bir tane beklerken Django'da bir nesne almanın tercih edilen bir yolu var mı?

İki açık yol:

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

Ve:

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

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

İlk yöntem davranışsal olarak daha doğru görünüyor, ancak kontrol akışında bazı ek yük getirebilecek istisnalar kullanıyor. İkincisi daha dolambaçlı ama bir istisna oluşturmayacak.

Bunlardan hangisi tercih edilebilir? Hangisi daha verimli?

Yanıtlar:


177

get()sağlanan bu durum için özel olarak . Kullanın.

Seçenek 2 neredeyse tam olarak get()yöntemin Django'da gerçekte nasıl uygulandığıdır, bu nedenle "performans" farkı olmamalıdır (ve bunun hakkında düşündüğünüz gerçeği, programlama temel kurallarından birini ihlal ettiğiniz anlamına gelir. kodu yazılmadan ve profillenmeden önce optimize edin - kodu alana kadar ve çalıştırabilinceye kadar, nasıl çalışacağını bilmiyorsunuz ve o zamandan önce optimize etmeye çalışmak bir acı yoludur).


Her şey doğru ama cevaplamak için daha fazla bilgi eklenmeli? 1. Python denemeyi / hariç tutmayı teşvik eder (bkz. EAFP ), bu yüzden QS.get()iyidir. 2. Ayrıntılar önemli: "yalnızca birini beklemek" her zaman 0-1 nesne anlamına mı gelir, yoksa 2+ nesneye sahip olmak mümkündür ve bu durumda da ele alınmalıdır (bu durumda len(objs)korkunç bir fikirdir)? 3. Karşılaştırma olmadan genel gider hakkında hiçbir şey varsaymayın (Bu durumda try/exceptaramaların en az yarısı bir şey döndürdüğü sürece daha hızlı olacağını düşünüyorum )
1'de empoze

> yani kod bile yazılmadan ve profillenmeden önce optimize etmeye çalışmak Bu ilginç bir açıklama. Her zaman bir şeyi uygulamadan önce uygulamanın en isteğe bağlı yolunu düşünmem gerektiğini düşündüm. Yanlış mı? Bu noktayı açıklayabilir misiniz? Bunu ayrıntılı olarak açıklayan bir kaynak var mı?
Parth Sharma

Kimsenin önce bahsetmediğine şaşırdım (). Diğer tavsiyeler, bu senaryo için yapılan çağrı olduğunu gösteriyor. stackoverflow.com/questions/5123839/…
NeilG

29

Django sinir bozucu adlı bir modül yükleyebilir ve ardından bunu yapabilirsiniz:

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
neden böyle bir yönteme sahip olmak sinir bozucu? bana iyi geliyor!
Thomas

17

1 doğrudur. Python'da bir istisnanın getiriye eşit yükü vardır. Basitleştirilmiş ispatı için, bakabilirsiniz bu .

2 Django'nun arka uçta yaptığı budur. öğe bulunmazsa veya birden fazla nesne bulunursa bir istisna getçağırır filterve çağırır .


1
Bu test oldukça haksız. Bir istisna atma yükünün büyük bir kısmı, yığın izinin işlenmesidir. Bu testin yığın uzunluğu 1'dir ve bu genellikle bir uygulamada bulacağınızdan çok daha düşüktür.
Rob Young

@Rob Young: Ne demek istiyorsun? Tipik "izin yerine af dileme" programında yığın izlemeyi nasıl görüyorsunuz? İşlem süresi, istisnanın kat ettiği mesafeye bağlıdır, hepsinin ne kadar derin olduğuna değil (java yazmıyor ve e.printStackTrace () çağrıldığında). Ve çoğu zaman (sözlük aramasında olduğu gibi) - istisna, hemen altına atılır try.
Tomasz Gandor

12

Partiye biraz geç kaldım, ancak Django 1.6 first()ile querysets'te yöntem var.

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


Queryset ile eşleşen ilk nesneyi veya eşleşen bir nesne yoksa None değerini döndürür. Sorgu Kümesi'nin tanımlanmış bir sırası yoksa, sorgu kümesi birincil anahtar tarafından otomatik olarak sıralanır.

Misal:

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

Bir sorguda yalnızca bir nesneniz olduğunu garanti etmez
py_dude

8

Django'nun herhangi bir tecrübesi ile konuşamam ama seçenek # 1, sisteme 1 nesne istediğini açıkça belirtirken, ikinci seçenek söylemiyor. Bu, # 1 seçeneğinin, özellikle filtrelediğiniz özelliğin benzersiz olduğu garanti edilmediğinde önbellek veya veritabanı dizinlerinden daha kolay yararlanabileceği anlamına gelir.

Ayrıca (tekrar, spekülasyon yapmak), ikinci seçeneğin, bir çeşit sonuç toplama veya yineleyici nesnesi oluşturması gerekebilir, çünkü filter () çağrısı normalde birçok satır döndürebilir. Bunu get () ile atlarsınız.

Son olarak, ilk seçenek hem daha kısadır hem de ekstra geçici değişkeni atlar - sadece küçük bir fark ama her küçük yardımcı olur.


Django ile hiçbir deneyim ama yine de spot. Varsayılan olarak açık, kısa ve güvenli olmak, dil veya çerçeve ne olursa olsun iyi ilkelerdir.
nevelis

8

Tüm bunlar neden işe yarıyor? 4 satırı 1 yerleşik kısayolla değiştirin. (Bu kendi denemesini yapar / hariçtir.)

from django.shortcuts import get_object_or_404

obj = get_object_or_404(MyModel, id=1)

1
Bu, istenen davranış olduğunda harikadır, ancak bazen eksik nesneyi oluşturmak isteyebilirsiniz veya çekme isteğe bağlı bilgilerdi.
SingleNegationElimination

2
Bunun için ne Model.objects.get_or_create()var
boatcoder

7

İstisnalar hakkında daha fazla bilgi. Eğer yetiştirilmezlerse, neredeyse hiçbir maliyeti yoktur. Bu nedenle, muhtemelen bir sonuç alacağınızı biliyorsanız, istisna kullanın, çünkü koşullu bir ifade kullanarak, ne olursa olsun, her seferinde kontrol maliyetini ödersiniz. Öte yandan, yükseltildiklerinde koşullu bir ifadeden biraz daha pahalıya mal olurlar, bu nedenle bazı sıklıklarla sonuç vermemeyi bekliyorsanız (örneğin, bellek hizmet veriyorsa, zamanın% 30'u), koşullu kontrol ortaya çıkar biraz daha ucuz olmak.

Ancak bu Django'nun ORM'sidir ve muhtemelen veritabanına gidiş-dönüş, hatta önbelleğe alınmış bir sonuç, performans özelliklerine hakim olacaktır, bu nedenle, tam olarak bir sonuç beklediğinizden, bu durumda okunabilirliği tercih edin get().


4

Bu sorunla biraz oynadım ve seçenek 2'nin böyle bir görev için aşırı olan iki SQL sorgusu yürüttüğünü keşfettim. Ek açıklamama bakı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

Tek bir sorguyu yürüten eşdeğer bir sürüm:

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]

Bu yaklaşıma geçerek, uygulamamın yürüttüğü sorgu sayısını önemli ölçüde azaltabildim.


1

İlginç bir soru, ama benim için # 2 seçenek erken optimizasyon reeks. Hangisinin daha iyi performans gösterdiğinden emin değilim, ancak # 1 seçeneği kesinlikle bana daha pitonik görünüyor ve hissediyor.


1

Farklı bir tasarım öneririm.

Olası bir sonuç üzerinde bir işlev gerçekleştirmek istiyorsanız, QuerySet'ten türetebilirsiniz, şöyle: http://djangosnippets.org/snippets/734/

Sonuç oldukça harika, örneğin:

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

Burada filtre, boş bir sorgu kümesi veya tek bir öğe içeren bir sorgu kümesi döndürür. Özel sorgu kümesi işlevleriniz de zincirlenebilir ve yeniden kullanılabilir. Tüm girişleriniz için gerçekleştirmek istiyorsanız:MyModel.objects.all().yourFunction() .

Ayrıca, yönetici arayüzünde eylem olarak kullanılmak için de idealdir:

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

0

Seçenek 1 daha zarif, ancak deneyin .. hariç kullandığınızdan emin olun.

Kendi tecrübelerime dayanarak, bazen veritabanında birden fazla eşleşen nesne olamayacağınızdan emin olabilirsiniz ve yine de iki tane olacaktır ... (elbette nesneyi birincil anahtarıyla alırken hariç).


0

Bu soruna bir tane daha eklemek için üzgünüm, ancak django sayfa düzenleyicisini kullanıyorum ve veri yöneticisi uygulamamda kullanıcının ne sorgulayacağını seçmesine izin veriliyor. Bazen bu bir belgenin kimliğidir, ancak aksi takdirde birden fazla nesne, yani bir Queryset döndüren genel bir sorgudur.

Kullanıcı kimliği sorgularsa, çalıştırabilirim:

Record.objects.get(pk=id)

django'nun sayfalayıcısına bir hata atar, çünkü bu bir Queryset Kayıtları değil, bir Kayıttır.

Koşmam gerek:

Record.objects.filter(pk=id)

Hangi bir öğe ile bir Queryset döndürür. Sonra sayfa düzenleyici gayet iyi çalışıyor.


Sayfalayıcıyı veya bir Sorgu Kümesi bekleyen herhangi bir işlevi kullanmak için, sorgunuzun bir Sorgu Kümesi döndürmesi gerekir. .Filter () ve .get () kullanma arasında geçiş yapmayın, .filter () ile yapışmayın ve daha önce fark ettiğiniz gibi "pk = id" filtresini sağlayın. Bu kullanım durumu için kalıp budur.
Cornel Masson

0

.almak()

Alan aramalarında açıklanan biçimde olması gereken belirli arama parametreleriyle eşleşen nesneyi döndürür.

get (), birden fazla nesne bulunduğunda MultipleObjectsReturned öğesini yükseltir. MultipleObjectsReturned istisnası model sınıfının bir özelliğidir.

get (), verilen parametreler için bir nesne bulunmazsa bir DoesNotExist istisnası oluşturur. Bu istisna aynı zamanda model sınıfının bir özelliğidir.

.filter ()

Belirli arama parametreleriyle eşleşen nesneler içeren yeni bir QuerySet döndürür.

Not

tek bir benzersiz nesne almak istediğinizde get (), arama parametrelerinizle eşleşen tüm nesneleri almak istediğinizde filter () kullanın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.