Filter Django versus dapatkan untuk objek tunggal?

147

Saya sedang berdebat tentang hal ini dengan beberapa rekan. Apakah ada cara yang disukai untuk mengambil objek di Django ketika Anda hanya mengharapkan satu?

Dua cara yang jelas adalah:

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

Dan:

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

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

Metode pertama tampaknya lebih benar secara perilaku, tetapi menggunakan pengecualian dalam aliran kontrol yang mungkin menyebabkan beberapa overhead. Yang kedua lebih bundaran tetapi tidak akan pernah memunculkan pengecualian.

Adakah pemikiran yang lebih disukai? Mana yang lebih efisien?

Cory
sumber

Jawaban:

177

get()disediakan khusus untuk kasus ini . Gunakan.

Opsi 2 hampir persis bagaimana get()metode ini benar-benar diterapkan di Django, jadi seharusnya tidak ada perbedaan "kinerja" (dan fakta bahwa Anda memikirkannya menunjukkan Anda melanggar salah satu aturan utama pemrograman, yaitu mencoba untuk optimalkan kode sebelum ditulis dan diprofilkan - sampai Anda memiliki kode dan dapat menjalankannya, Anda tidak tahu bagaimana kinerjanya, dan mencoba mengoptimalkan sebelum itu adalah jalan yang menyakitkan).

James Bennett
sumber
Semuanya benar tetapi mungkin lebih banyak info harus ditambahkan untuk menjawab? 1. Python menganjurkan mencoba / kecuali (lihat EAFP ), itu sebabnya QS.get()bagus. 2. Detail penting: apakah "hanya mengharapkan satu" berarti selalu objek 0-1, atau dimungkinkan untuk memiliki 2+ objek dan kasing itu juga harus ditangani (dalam hal ini len(objs)adalah ide yang buruk)? 3. Jangan berasumsi apa-apa tentang overhead tanpa patokan (saya pikir dalam hal ini try/exceptakan lebih cepat selama setidaknya setengah dari panggilan mengembalikan sesuatu)
imposeren
> yaitu mencoba mengoptimalkan kode sebelum ditulis dan diprofilkan. Ini adalah pernyataan yang menarik. Saya selalu berpikir bahwa saya harus memikirkan cara yang paling opsional untuk mengimplementasikan sesuatu sebelum mengimplementasikannya. Apakah itu salah? Bisakah Anda menguraikan hal ini? Apakah ada sumber daya yang menjelaskan ini secara rinci?
Parth Sharma
Saya terkejut tidak ada yang disebutkan pertama kali (). Saran lain tampaknya menunjukkan itu panggilan yang dibuat untuk skenario ini. stackoverflow.com/questions/5123839/…
NeilG
29

Anda dapat menginstal modul yang disebut django-annoying dan kemudian lakukan ini:

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
pendeta
sumber
1
mengapa menjengkelkan memiliki metode seperti itu? terlihat baik untuk saya!
Thomas
17

1 benar. Dalam Python pengecualian memiliki overhead yang sama dengan return. Untuk bukti disederhanakan Anda dapat melihat ini .

2 Inilah yang dilakukan Django di backend. getpanggilan filterdan menimbulkan pengecualian jika tidak ada item ditemukan atau jika lebih dari satu objek ditemukan.

Umair Mohammad
sumber
1
Tes itu sangat tidak adil. Sebagian besar overhead dalam melempar pengecualian adalah penanganan jejak tumpukan. Tes itu memiliki panjang tumpukan 1 yang jauh lebih rendah daripada yang biasanya Anda temukan dalam aplikasi.
Rob Young
@Rob Young: Apa maksudmu? Di mana Anda melihat penanganan tumpukan jejak dalam skema "minta maaf daripada izin"? Waktu pemrosesan tergantung pada jarak perjalanan pengecualian, bukan seberapa dalam semua itu terjadi (ketika kita tidak menulis dalam java dan memanggil e.printStackTrace ()). Dan paling sering (seperti dalam pencarian kamus) - pengecualian dilemparkan tepat di bawah try.
Tomasz Gandor
12

Saya agak terlambat ke pesta, tetapi dengan Django 1.6 ada first()metode pada querysets.

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


Mengembalikan objek pertama yang cocok dengan queryset, atau None jika tidak ada objek yang cocok. Jika QuerySet tidak memiliki urutan yang ditentukan, maka queryset secara otomatis dipesan oleh kunci utama.

Contoh:

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
sumber
Itu tidak menjamin bahwa Anda hanya memiliki satu objek dalam kueri
py_dude
8

Saya tidak dapat berbicara dengan pengalaman Django tetapi opsi # 1 dengan jelas memberitahu sistem bahwa Anda meminta 1 objek, sedangkan opsi kedua tidak. Ini berarti bahwa opsi # 1 dapat lebih mudah memanfaatkan cache atau indeks basis data, terutama di mana atribut yang Anda filter tidak dijamin unik.

Juga (lagi, berspekulasi) opsi kedua mungkin harus membuat semacam koleksi hasil atau objek iterator karena panggilan filter () biasanya bisa mengembalikan banyak baris. Anda akan melewati ini dengan get ().

Akhirnya, opsi pertama lebih pendek dan menghilangkan variabel sementara ekstra - hanya perbedaan kecil tetapi setiap sedikit membantu.

Kylotan
sumber
Tidak ada pengalaman dengan Django tetapi masih tepat. Menjadi eksplisit, singkat & aman secara default, adalah prinsip yang baik apa pun bahasa atau kerangka kerjanya.
nevelis
8

Mengapa semua itu berhasil? Ganti 4 baris dengan 1 pintasan bawaan. (Ini mencoba / kecuali sendiri.)

from django.shortcuts import get_object_or_404

obj = get_object_or_404(MyModel, id=1)
Krubo
sumber
1
Ini bagus ketika itu perilaku yang diinginkan, tetapi kadang-kadang, Anda mungkin ingin membuat objek yang hilang, atau tarikannya adalah informasi opsional.
SingleNegationElimination
2
Itulah yang Model.objects.get_or_create()bagi
boatcoder
7

Beberapa info lebih lanjut tentang pengecualian. Jika mereka tidak dibesarkan, mereka hampir tidak memerlukan biaya apa pun. Jadi, jika Anda tahu Anda mungkin akan mendapatkan hasil, gunakan pengecualian, karena menggunakan ekspresi bersyarat Anda membayar biaya memeriksa setiap waktu, tidak peduli apa. Di sisi lain, harganya sedikit lebih mahal daripada ekspresi kondisional ketika mereka dinaikkan, jadi jika Anda berharap tidak memiliki hasil dengan frekuensi tertentu (katakanlah, 30% dari waktu, jika memori berfungsi), pemeriksaan bersyarat ternyata menjadi sedikit lebih murah.

Tetapi ini adalah ORANG Django, dan mungkin bolak-balik ke basis data, atau bahkan hasil yang di-cache, cenderung mendominasi karakteristik kinerja, jadi nikmatilah keterbacaan, dalam hal ini, karena Anda mengharapkan tepat satu hasil, gunakan get().

SingleNegationElimination
sumber
4

Saya telah bermain dengan masalah ini sedikit dan menemukan bahwa opsi 2 mengeksekusi dua query SQL, yang untuk tugas sederhana seperti itu berlebihan. Lihat anotasi saya:

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

Versi yang setara yang mengeksekusi satu query adalah:

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]

Dengan beralih ke pendekatan ini, saya dapat secara substansial mengurangi jumlah kueri yang dijalankan aplikasi saya.

Jan Wrobel
sumber
1

Pertanyaan yang menarik, tetapi bagi saya opsi # 2 berbau optimasi prematur. Saya tidak yakin mana yang lebih performan, tetapi opsi # 1 tentu terlihat dan terasa lebih pythonic bagi saya.

John McCollum
sumber
1

Saya menyarankan desain yang berbeda.

Jika Anda ingin melakukan fungsi pada hasil yang mungkin, Anda bisa berasal dari QuerySet, seperti ini: http://djangosnippets.org/snippets/734/

Hasilnya cukup mengagumkan, Anda bisa misalnya:

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

Di sini, filter mengembalikan queryset kosong atau queryset dengan satu item. Fungsi queryset khusus Anda juga dapat diputus-putus dan dapat digunakan kembali. Jika Anda ingin melakukannya untuk semua entri Anda:MyModel.objects.all().yourFunction() .

Mereka juga ideal untuk digunakan sebagai tindakan di antarmuka admin:

def yourAction(self, request, queryset):
    queryset.yourFunction()
Joctee
sumber
0

Opsi 1 lebih elegan, tetapi pastikan untuk menggunakan try..exception.

Dari pengalaman saya sendiri, saya dapat memberi tahu Anda bahwa kadang-kadang Anda yakin tidak mungkin ada lebih dari satu objek yang cocok dalam database, namun akan ada dua ... (kecuali tentu saja ketika mendapatkan objek dengan kunci utamanya).

zooglash
sumber
0

Maaf menambahkan satu lagi mengambil masalah ini, tapi saya menggunakan pagjinator Django, dan di aplikasi admin data saya, pengguna diizinkan untuk memilih apa yang ingin ditanyakan. Kadang-kadang itu adalah id dokumen, tetapi sebaliknya itu adalah permintaan umum yang mengembalikan lebih dari satu objek, yaitu Queryset.

Jika pengguna menanyakan id, saya bisa menjalankan:

Record.objects.get(pk=id)

yang melempar kesalahan dalam paginator Django, karena itu adalah Record dan bukan Queryset of Records.

Saya perlu menjalankan:

Record.objects.filter(pk=id)

Yang mengembalikan Queryset dengan satu item di dalamnya. Kemudian paginator berfungsi dengan baik.

excyberlabber
sumber
Untuk menggunakan paginator - atau fungsi apa pun yang mengharapkan QuerySet - kueri Anda harus mengembalikan QuerySet. Jangan beralih antara menggunakan .filter () dan .get (), tetap dengan .filter () dan berikan filter "pk = id", seperti yang telah Anda sadari. Itulah pola untuk kasus penggunaan ini.
Cornel Masson
0

.Dapatkan()

Mengembalikan objek yang cocok dengan parameter pencarian yang diberikan, yang harus dalam format yang dijelaskan dalam Pencarian bidang.

get () memunculkan MultipleObjectsReturned jika lebih dari satu objek ditemukan. Pengecualian MultipleObjectsReturned adalah atribut dari kelas model.

get () memunculkan exception DoesNotExist jika objek tidak ditemukan untuk parameter yang diberikan. Pengecualian ini juga merupakan atribut dari kelas model.

.Saring()

Mengembalikan QuerySet baru yang berisi objek yang cocok dengan parameter pencarian yang diberikan.

Catatan

gunakan get () saat Anda ingin mendapatkan satu objek unik, dan filter () ketika Anda ingin mendapatkan semua objek yang cocok dengan parameter pencarian Anda.

Razia Khan
sumber