Bộ lọc Django so với get cho đối tượng duy nhất?


147

Tôi đã có một cuộc tranh luận về điều này với một số đồng nghiệp. Có cách nào ưa thích để lấy một đối tượng trong Django khi bạn chỉ mong đợi một đối tượng không?

Hai cách rõ ràng là:

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

Và:

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

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

Phương pháp đầu tiên có vẻ đúng hơn về mặt hành vi, nhưng sử dụng các ngoại lệ trong luồng điều khiển có thể đưa ra một số chi phí. Thứ hai là vòng xoay nhiều hơn nhưng sẽ không bao giờ đưa ra một ngoại lệ.

Bất kỳ suy nghĩ về những điều này là thích hợp hơn? Cái nào hiệu quả hơn?

Câu trả lời:


177

get()được cung cấp cụ thể cho trường hợp này . Sử dụng nó.

Tùy chọn 2 gần như chính xác là cách get()phương thức được thực hiện trong Django, do đó, không nên có sự khác biệt về "hiệu suất" (và thực tế là bạn đang nghĩ về nó cho thấy bạn đang vi phạm một trong các quy tắc chính của lập trình, cụ thể là cố gắng tối ưu hóa mã trước khi nó được viết và định hình - cho đến khi bạn có mã và có thể chạy nó, bạn không biết nó sẽ hoạt động như thế nào và cố gắng tối ưu hóa trước đó là một con đường đau đớn).


Mọi thứ đều đúng nhưng có lẽ nên thêm thông tin để trả lời? 1. Python khuyến khích thử / ngoại trừ (xem EAFP ), đó là lý do tại sao QS.get()tốt. 2. Vấn đề chi tiết: "chỉ mong đợi một" có nghĩa là luôn có 0-1 đối tượng hay có thể có hơn 2 đối tượng và trường hợp đó cũng nên được xử lý (trong trường hợp len(objs)này là một ý tưởng tồi tệ)? 3. Đừng giả sử bất cứ điều gì về chi phí mà không có điểm chuẩn (Tôi nghĩ rằng trong trường hợp try/exceptnày sẽ nhanh hơn miễn là ít nhất một nửa số cuộc gọi trả lại một cái gì đó)
áp đặt

> cụ thể là cố gắng tối ưu hóa mã trước khi nó được viết và định hình Đây là một nhận xét thú vị. Tôi luôn nghĩ rằng tôi nên nghĩ ra cách tùy chọn nhất để thực hiện một cái gì đó trước khi thực hiện nó. Là sai đó? Bạn có thể giải thích về điểm này? Có một số tài nguyên giải thích điều này một cách chi tiết?
Parth Sharma

Tôi ngạc nhiên không ai nhắc đến đầu tiên (). Lời khuyên khác dường như cho thấy đó là cuộc gọi được thực hiện cho kịch bản này. stackoverflow.com/questions/5123839/
NeilG

29

Bạn có thể cài đặt một mô-đun gọi là django-phiền phức và sau đó làm điều này:

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
Tại sao nó khó chịu khi có một phương pháp như vậy? trông ổn với tôi !
Thomas

17

1 đúng. Trong Python, một ngoại lệ có chi phí tương đương với lợi nhuận. Đối với một bằng chứng đơn giản, bạn có thể nhìn vào điều này .

2 Đây là những gì Django đang làm trong phần phụ trợ. getgọi filtervà đưa ra một ngoại lệ nếu không tìm thấy mục nào hoặc nếu có nhiều hơn một đối tượng được tìm thấy.


1
Bài kiểm tra đó khá bất công. Một phần lớn chi phí trong việc ném một ngoại lệ là việc xử lý dấu vết ngăn xếp. Bài kiểm tra đó có độ dài ngăn xếp là 1 thấp hơn nhiều so với bạn thường thấy trong một ứng dụng.
Rob Young

@Rob Young: Ý bạn là gì? Bạn thấy xử lý dấu vết ngăn xếp ở đâu trong sơ đồ "yêu cầu tha thứ thay vì cho phép" điển hình? Thời gian xử lý phụ thuộc vào khoảng cách mà ngoại lệ di chuyển, chứ không phải tất cả xảy ra sâu như thế nào (khi chúng ta không viết bằng java và gọi e.printStackTrace ()). Và thường xuyên nhất (như trong tra cứu từ điển) - ngoại lệ được ném ngay bên dưới try.
Tomasz Gandor

12

Tôi đến bữa tiệc muộn một chút, nhưng với Django 1.6 có first()phương thức trên các truy vấn.

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


Trả về đối tượng đầu tiên được khớp bởi bộ truy vấn hoặc Không có nếu không có đối tượng phù hợp. Nếu Truy vấn không có thứ tự được xác định, thì bộ truy vấn sẽ tự động được sắp xếp theo khóa chính.

Thí dụ:

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

Điều đó không đảm bảo rằng bạn chỉ có một đối tượng trong một truy vấn
py_dude

8

Tôi không thể nói với bất kỳ kinh nghiệm nào về Django nhưng tùy chọn # 1 nói rõ cho hệ thống rằng bạn đang yêu cầu 1 đối tượng, trong khi tùy chọn thứ hai thì không. Điều này có nghĩa là tùy chọn # 1 có thể dễ dàng tận dụng các chỉ mục bộ đệm hoặc cơ sở dữ liệu hơn, đặc biệt là khi thuộc tính bạn đang lọc không được đảm bảo là duy nhất.

Ngoài ra (một lần nữa, suy đoán) tùy chọn thứ hai có thể phải tạo một số loại thu thập kết quả hoặc đối tượng lặp vì cuộc gọi bộ lọc () thường có thể trả về nhiều hàng. Bạn sẽ bỏ qua điều này với get ().

Cuối cùng, tùy chọn đầu tiên vừa ngắn hơn vừa bỏ qua biến tạm thời bổ sung - chỉ là một sự khác biệt nhỏ nhưng mỗi sự trợ giúp nhỏ.


Không có kinh nghiệm với Django nhưng vẫn phát hiện ra. Rõ ràng, ngắn gọn và an toàn theo mặc định, là những nguyên tắc tốt cho dù ngôn ngữ hay khuôn khổ.
nevelis

8

Tại sao tất cả những công việc đó? Thay thế 4 dòng bằng 1 phím tắt dựng sẵn. (Cái này không thử / ngoại trừ.)

from django.shortcuts import get_object_or_404

obj = get_object_or_404(MyModel, id=1)

1
Điều này thật tuyệt khi đó là hành vi mong muốn, nhưng đôi khi, bạn có thể muốn tạo đối tượng bị thiếu hoặc kéo là thông tin tùy chọn.
Độc thân Khuyến khích

2
Đó là những gì Model.objects.get_or_create()dành cho
thuyền viên

7

Một số thông tin thêm về các trường hợp ngoại lệ. Nếu chúng không được nuôi, chúng có giá gần như không có gì. Do đó, nếu bạn biết rằng bạn có thể sẽ có kết quả, hãy sử dụng ngoại lệ, vì sử dụng biểu thức điều kiện bạn phải trả chi phí kiểm tra mỗi lần, bất kể điều gì. Mặt khác, chúng có giá cao hơn một chút so với biểu thức có điều kiện khi chúng được nâng lên, vì vậy nếu bạn dự kiến ​​sẽ không có kết quả với một số tần số (giả sử, 30% thời gian, nếu bộ nhớ phục vụ), thì kiểm tra có điều kiện sẽ tắt rẻ hơn một chút

Nhưng đây là ORM của Django, và có lẽ là chuyến đi khứ hồi tới cơ sở dữ liệu, hoặc thậm chí là kết quả được lưu trong bộ nhớ cache, có khả năng chi phối các đặc tính hiệu suất, vì vậy, trong trường hợp này, bạn nên sử dụng chính xác một kết quả get().


4

Tôi đã giải quyết vấn đề này một chút và phát hiện ra rằng tùy chọn 2 thực thi hai truy vấn SQL, đối với một tác vụ đơn giản như vậy là quá mức. Xem chú thích của tôi:

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

Một phiên bản tương đương thực thi một truy vấn duy nhất là:

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]

Bằng cách chuyển sang cách tiếp cận này, tôi đã có thể giảm đáng kể số lượng truy vấn mà ứng dụng của tôi thực hiện.


1

Câu hỏi thú vị, nhưng đối với tôi, tùy chọn số 2 tối ưu hóa sớm. Tôi không chắc cái nào hiệu quả hơn, nhưng tùy chọn # 1 chắc chắn trông và cảm thấy nhiều pythonic hơn đối với tôi.


1

Tôi đề nghị một thiết kế khác nhau.

Nếu bạn muốn thực hiện một chức năng trên một kết quả có thể, bạn có thể xuất phát từ Truy vấn, như sau: http://djangosnippets.org/snippets/734/

Kết quả khá tuyệt vời, ví dụ bạn có thể:

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

Ở đây, bộ lọc trả về một bộ truy vấn trống hoặc bộ truy vấn với một mục duy nhất. Các chức năng truy vấn tùy chỉnh của bạn cũng có thể chuỗi và có thể tái sử dụng. Nếu bạn muốn thực hiện nó cho tất cả các mục của bạn : MyModel.objects.all().yourFunction().

Chúng cũng lý tưởng để được sử dụng làm hành động trong giao diện quản trị viên:

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

0

Tùy chọn 1 thanh lịch hơn, nhưng hãy chắc chắn sử dụng try..except.

Từ kinh nghiệm của riêng tôi, tôi có thể nói với bạn rằng đôi khi bạn chắc chắn không thể có nhiều hơn một đối tượng phù hợp trong cơ sở dữ liệu, nhưng sẽ có hai ... (tất nhiên trừ khi lấy đối tượng bằng khóa chính của nó).


0

Xin lỗi để thêm một lần nữa về vấn đề này, nhưng tôi đang sử dụng trình phân trang django và trong ứng dụng quản trị dữ liệu của tôi, người dùng được phép chọn những gì cần truy vấn. Đôi khi, đó là id của một tài liệu, nhưng nếu không, nó là một truy vấn chung trả về nhiều hơn một đối tượng, tức là Truy vấn.

Nếu người dùng truy vấn id, tôi có thể chạy:

Record.objects.get(pk=id)

trong đó đưa ra một lỗi trong trình phân trang của django, bởi vì đó là Bản ghi chứ không phải là Truy vấn Bản ghi.

Tôi cần phải chạy:

Record.objects.filter(pk=id)

Trả về một Queryset với một mục trong đó. Sau đó, paginator hoạt động tốt.


Để sử dụng trình phân trang - hoặc bất kỳ chức năng nào mong đợi Truy vấn - truy vấn của bạn phải trả về Truy vấn. Đừng chuyển đổi giữa việc sử dụng .filter () và .get (), gắn bó với .filter () và cung cấp bộ lọc "pk = id", như bạn đã nhận ra. Đó là mô hình cho trường hợp sử dụng này.
Cornel Masson

0

.được()

Trả về đối tượng khớp với các tham số tra cứu đã cho, phải ở định dạng được mô tả trong Tra cứu trường.

get () tăng MultipObjectsReturned nếu tìm thấy nhiều hơn một đối tượng. Ngoại lệ ManyObjectsReturned là một thuộc tính của lớp mô hình.

get () làm tăng ngoại lệ DoesNotExist nếu không tìm thấy đối tượng cho các tham số đã cho. Ngoại lệ này cũng là một thuộc tính của lớp mô hình.

.filter ()

Trả về một Truy vấn mới chứa các đối tượng khớp với các tham số tra cứu đã cho.

Ghi chú

sử dụng get () khi bạn muốn lấy một đối tượng duy nhất và lọc () khi bạn muốn nhận tất cả các đối tượng phù hợp với tham số tra cứu của mình.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.