2009-06-19 40 views
108

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 ở Django khi bạn đang mong đợi một đối tượng không?Bộ lọc Django so với nhận đối tượng đơn lẻ?

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

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

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 dường như tính hành chính xác hơn, nhưng sử dụng ngoại lệ trong dòng điều khiển trong đó có thể giới thiệu một số chi phí. Thứ hai là vòng xoay nhiều hơn nhưng sẽ không bao giờ nêu ra một ngoại lệ.

Bất kỳ suy nghĩ nào trong số này là thích hợp hơn? Đó là hiệu quả hơn?

Trả lời

137

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

Phương án 2 gần như chính xác cách thức get() thực sự được triển khai ở Django, vì vậy không có sự khác biệt "hiệu suất" (và thực tế là bạn đang nghĩ về điều đó cho biết bạn đang vi phạm một trong các quy tắc 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à lược tả - 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à con đường đau).

1

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

6

Tôi không thể nói chuyện với bất kỳ trải nghiệm nào của Django nhưng tùy chọn # 1 rõ ràng cho hệ thống biết 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 bộ nhớ cache hoặc chỉ mục cơ sở dữ liệu, đặ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 kết quả thu thập hoặc đối tượng iterator vì cuộc gọi filter() 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 là ngắn hơn và bỏ qua biến tạm thời bổ sung - chỉ khác biệt nhỏ nhưng mỗi chút đều giúp ích.

+0

Không kinh nghiệm với Django nhưng vẫn tại chỗ trên. Rõ ràng, ngắn gọn và an toàn theo mặc định, là các nguyên tắc tốt cho dù ngôn ngữ hoặc khuôn khổ là gì. – nevelis

13

1 là chính xác. Trong Python một ngoại lệ có chi phí bằng nhau để trả về. Để có bằng chứng đơn giản, bạn có thể xem this.

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

+1

Kiểm tra đó là 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. –

+0

@Rob Young: Ý bạn là gì? Nơi nào bạn thấy xử lý dấu vết ngăn xếp trong chương trình "yêu cầu tha thứ chứ không phải là sự 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 (khi chúng tôi không viết bằng java và gọi e.printStackTrace()). Và thường xuyên nhất (như tra cứu từ điển) - ngoại lệ được ném ngay bên dưới 'try'. –

5

Tại sao tất cả đều hoạt động? Thay thế 4 dòng bằng 1 phím tắt dựng sẵn. (Điều này tự 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 là rất tốt khi đó là hành vi mong muốn, nhưng đôi khi, bạn có thể muốn tạo đối tượng còn thiếu hoặc kéo là thông tin tùy chọn. – SingleNegationElimination

+1

Đó là những gì 'Model.objects.get_or_create()' là cho – boatcoder

6

Một số thông tin khác về ngoại lệ. Nếu chúng không được nâng lên, chúng hầu như không tốn kém gì cả. Vì vậy, nếu bạn biết 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 có điều kiện bạn phải trả chi phí kiểm tra mọi lúc, bất kể điều gì. Mặt khác, chúng tốn nhiều hơn một biểu thức có điều kiện khi chúng được nâng lên, vì vậy nếu bạn mong đợi không có kết quả với một số tần số (nói, 30% thời gian, nếu bộ nhớ phục vụ), kiểm tra điều kiện hóa ra rẻ hơn một chút.

Nhưng đây là ORM của Django, và có thể 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ếm ưu thế về đặc tính hiệu suất, vì vậy bạn có thể đọc được chính xác. get().

27

Bạn có thể cài đặt một module gọi là django-annoying 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 
+0

tại sao nó gây phiền nhiễu để có một phương pháp như vậy? trông ổn với tôi ! – Thomas

+2

@Thomas Tôi nghĩ rằng ý tưởng là nó gây phiền toái KHÔNG có phương pháp như vậy ... – user193130

0

Lựa chọn 1 là tao nhã 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 đối tượng ... (ngoại trừ khóa học khi nhận được đối tượng của nó). khóa chính).

1

Tôi đề xuất một thiết kế khác.

Nếu bạn muốn thực hiện một chức năng vào một kết quả tốt, bạn có thể bắt nguồn từ QuerySet, như thế này: http://djangosnippets.org/snippets/734/

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

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

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

Họ cũng rất lý tưởng để được sử dụng như hành động trong giao diện quản trị:

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

Tôi đã chơi với vấn đề này một chút và phát hiện ra rằng phương án 2 thực hiện hai truy vấn SQL, mà đối với một đơn giản như vậy nhiệm vụ là quá nhiều. 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 mà thực hiện 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 phương pháp này, tôi đã có thể làm giảm đáng kể số lượng các truy vấn ứng dụng của tôi thực hiện.

6

Tôi hơi muộn với bữa tiệc, nhưng với Django 1.6 có phương thức first() trên queryset.

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


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

Ví 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 
Các vấn đề liên quan