Sử dụng thử vs nếu trong python


145

Có một lý do để quyết định nên sử dụng một trong những tryhoặc ifcấu trúc nào, khi kiểm tra biến có giá trị không?

Ví dụ: có một hàm trả về danh sách hoặc không trả về giá trị. Tôi muốn kiểm tra kết quả trước khi xử lý nó. Điều nào sau đây sẽ được ưa thích hơn và tại sao?

result = function();
if (result):
    for r in result:
        #process items

hoặc là

result = function();
try:
    for r in result:
        #process items
except TypeError:
    pass;

Thảo luận liên quan:

Kiểm tra sự tồn tại của thành viên trong Python


Hãy nhớ rằng nếu các mục # process của bạn có khả năng ném TypeError, bạn cần phải bọc lại một lần thử khác: ngoại trừ: chặn. Đối với ví dụ cụ thể này, tôi chỉ bằng cách sử dụng if:
richo

Câu trả lời:


237

Bạn thường nghe rằng Python khuyến khích phong cách EAFP ("dễ xin tha thứ hơn là cho phép") so với kiểu LBYL ("nhìn trước khi bạn nhảy"). Đối với tôi, đó là vấn đề hiệu quả và dễ đọc.

Trong ví dụ của bạn (giả sử thay vì trả về danh sách hoặc chuỗi rỗng, hàm sẽ trả về danh sách hoặc None), nếu bạn cho rằng 99% thời gian resultsẽ thực sự chứa thứ gì đó có thể lặp lại, tôi sẽ sử dụng try/exceptphương pháp này. Nó sẽ nhanh hơn nếu các ngoại lệ thực sự là đặc biệt. Nếu resultNonehơn 50% thời gian, thì sử dụng ifcó lẽ tốt hơn.

Để hỗ trợ điều này với một vài phép đo:

>>> import timeit
>>> timeit.timeit(setup="a=1;b=1", stmt="a/b") # no error checking
0.06379691968322732
>>> timeit.timeit(setup="a=1;b=1", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.0829463709378615
>>> timeit.timeit(setup="a=1;b=0", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.5070195056614466
>>> timeit.timeit(setup="a=1;b=1", stmt="if b!=0:\n a/b")
0.11940114974277094
>>> timeit.timeit(setup="a=1;b=0", stmt="if b!=0:\n a/b")
0.051202772912802175

Vì vậy, trong khi một iftuyên bố luôn khiến bạn phải trả giá, thì việc thiết lập một try/exceptkhối gần như miễn phí . Nhưng khi một Exceptionthực tế xảy ra, chi phí cao hơn nhiều.

Đạo đức:

  • Hoàn toàn ổn (và "pythonic") để sử dụng try/exceptcho kiểm soát dòng chảy,
  • nhưng nó có ý nghĩa nhất khi Exceptions thực sự đặc biệt.

Từ các tài liệu Python:

EAFP

Dễ dàng yêu cầu sự tha thứ hơn sự cho phép. Kiểu mã hóa Python phổ biến này giả định sự tồn tại của các khóa hoặc thuộc tính hợp lệ và bắt ngoại lệ nếu giả định chứng minh là sai. Phong cách sạch sẽ và nhanh chóng này được đặc trưng bởi sự hiện diện của nhiều tryexcepttuyên bố. Kỹ thuật này tương phản với phong cách LBYL phổ biến đối với nhiều ngôn ngữ khác như C.


1
Cảm ơn bạn. Tôi thấy rằng trong trường hợp này, lý do có thể là kỳ vọng của kết quả.
artdanil

6
.... và đó là một trong nhiều lý do tại sao thật khó để thực hiện một JIT tối ưu hóa thực sự cho Python. Như gần đây trong phiên bản beta LuaJIT 2 đã chứng minh, các ngôn ngữ động thể thực sự rất nhanh; nhưng nó phụ thuộc rất nhiều vào thiết kế ngôn ngữ ban đầu và phong cách mà nó khuyến khích. (trên một lưu ý liên quan, thiết kế ngôn ngữ là lý do tại sao ngay cả các JIT JavaScirpt tốt nhất cũng không thể so sánh với LuaJIT 1, ít hơn 2)
Javier

1
@ 2rs2ts: Bản thân tôi cũng đã làm tương tự. Trong Python 3, try/exceptnhanh hơn 25% so if key in d:với các trường hợp khóa nằm trong từ điển. Nó chậm hơn nhiều khi khóa không có trong từ điển, như mong đợi và phù hợp với câu trả lời này.
Tim Pietzcker

5
Tôi biết đây là một câu trả lời cũ, tuy nhiên: sử dụng các câu lệnh như 1/1với timeit không phải là một lựa chọn tuyệt vời, bởi vì chúng sẽ được tối ưu hóa (xem dis.dis('1/1')và lưu ý rằng không có sự phân chia nào xảy ra).
Andrea Corbellini

1
Nó cũng phụ thuộc vào hoạt động bạn sẵn sàng thực hiện. Một bộ phận duy nhất là nhanh chóng, bắt một lỗi là hơi tốn kém. Nhưng ngay cả trong trường hợp chi phí xử lý ngoại lệ có thể chấp nhận được, hãy suy nghĩ cẩn thận về những gì bạn yêu cầu máy tính làm. Nếu bản thân hoạt động rất tốn kém bất kể kết quả của nó là gì, và có một cách rẻ tiền để biết liệu thành công có khả thi hay không: Sử dụng nó. Ví dụ: Không sao chép một nửa tệp chỉ để nhận ra không có đủ dung lượng.
Bachsau

14

Hàm của bạn không được trả về các kiểu hỗn hợp (ví dụ: danh sách hoặc chuỗi rỗng). Nó sẽ trả về một danh sách các giá trị hoặc chỉ là một danh sách trống. Sau đó, bạn sẽ không cần phải kiểm tra bất cứ điều gì, tức là mã của bạn bị sập xuống:

for r in function():
    # process items

2
Tôi hoàn toàn đồng ý với bạn về điểm đó; tuy nhiên, chức năng này không phải của tôi và tôi chỉ sử dụng nó.
artdanil

2
@artdanil: Vì vậy, bạn có thể bọc chức năng đó trong một cái mà bạn viết hoạt động giống như chức năng mà Brandon Corfman đang nghĩ đến.
quamrana

24
Điều này đặt ra câu hỏi: trình bao bọc nên sử dụng if hoặc try để xử lý trường hợp không lặp lại được?
jcdyer

@quamrana đó chính xác là những gì tôi đang cố gắng làm. Vì vậy, như @jcd đã chỉ ra, Câu hỏi là về cách tôi nên xử lý tình huống trong chức năng trình bao bọc.
artdanil

12

Hãy bỏ qua giải pháp của tôi nếu mã tôi cung cấp không rõ ràng ngay từ cái nhìn đầu tiên và bạn phải đọc phần giải thích sau mẫu mã.

Tôi có thể cho rằng "không có giá trị trả về" có nghĩa là giá trị trả về là Không? Nếu có, hoặc nếu "không có giá trị" là sai boolean-khôn ngoan, bạn có thể làm như sau, vì về cơ bản mã của bạn coi "không có giá trị" là "không lặp lại":

for r in function() or ():
    # process items

Nếu function()trả về một cái gì đó không đúng, bạn lặp lại bộ dữ liệu trống, tức là bạn không chạy bất kỳ lần lặp nào. Đây thực chất là LBYL.


4

Ví dụ thứ hai của bạn bị hỏng - mã sẽ không bao giờ ném ngoại lệ TypeError vì bạn có thể lặp qua cả chuỗi và danh sách. Lặp lại thông qua một chuỗi hoặc danh sách trống cũng hợp lệ - nó sẽ thực thi phần thân của vòng lặp 0 lần.


4

Điều nào sau đây sẽ được ưa thích hơn và tại sao?

Nhìn Before You Leap là thích hợp hơn trong trường hợp này. Với cách tiếp cận ngoại lệ, TypeError có thể xảy ra ở bất cứ đâu trong thân vòng lặp của bạn và nó sẽ bị bắt và vứt đi, đó không phải là điều bạn muốn và sẽ khiến việc gỡ lỗi trở nên khó khăn.

(Tôi đồng ý với Brandon Corfman mặc dù: trả về Không cho 'không có mục' thay vì danh sách trống bị hỏng. Đó là thói quen khó chịu của các lập trình viên Java không nên thấy trong Python. Hoặc Java.)


4

Nói chung, ấn tượng tôi nhận được là các ngoại lệ nên được dành riêng cho các trường hợp đặc biệt. Nếu resultdự kiến ​​sẽ không bao giờ trống (nhưng có thể, ví dụ, nếu một đĩa bị hỏng, v.v.), cách tiếp cận thứ hai có ý nghĩa. Mặt khác, nếu một sản phẩm nào đó resulthoàn toàn hợp lý trong điều kiện bình thường, thì việc kiểm tra nó bằng một iftuyên bố có ý nghĩa hơn.

Tôi đã nghĩ đến kịch bản (phổ biến hơn):

# keep access counts for different files
file_counts={}
...
# got a filename somehow
if filename not in file_counts:
    file_counts[filename]=0
file_counts[filename]+=1

thay vì tương đương:

...
try:
    file_counts[filename]+=1
except KeyError:
    file_counts[filename]=1

Đây là một ví dụ về sự khác biệt giữa các cách tiếp cận mà Tim Pietzcker đề cập: Đầu tiên là LBYL, thứ hai là EAFP
Manageu

Chỉ cần một lưu ý nhanh mà ++không hoạt động trong python, += 1thay vào đó hãy sử dụng .
tgray

3

Bobince chỉ ra một cách khôn ngoan rằng bọc trường hợp thứ hai cũng có thể bắt TypeErrors trong vòng lặp, đó không phải là điều bạn muốn. Nếu bạn thực sự muốn sử dụng thử, bạn có thể kiểm tra xem nó có thể lặp lại trước vòng lặp không

result = function();
try:
    it = iter(result)
except TypeError:
    pass
else:
    for r in it:
        #process items

Như bạn có thể thấy, nó khá xấu xí. Tôi không đề xuất nó, nhưng nó nên được đề cập cho đầy đủ.


Trong mắt tôi, chạy vào lỗi loại cố ý luôn là phong cách mã hóa xấu. Một nhà phát triển nên biết những gì mong đợi và sẵn sàng xử lý các giá trị đó mà không có ngoại lệ. Bất cứ khi nào một hoạt động đã làm những gì nó phải làm (theo quan điểm của lập trình viên) và kết quả mong đợi là một danh sách hoặc None, luôn luôn kiểm tra kết quả với is Nonehoặc is not None. Mặt khác, đó cũng là phong cách xấu để nâng cao Ngoại lệ cho kết quả hợp pháp. Ngoại lệ là cho những điều bất ngờ. Ví dụ: str.find()trả về -1 nếu không tìm thấy gì, vì bản thân tìm kiếm đã hoàn thành không có lỗi.
Bachsau

1

Đối với hiệu suất có liên quan, sử dụng khối thử cho mã thường không đưa ra ngoại lệ nhanh hơn sử dụng câu lệnh if mọi lúc. Vì vậy, quyết định phụ thuộc vào xác suất của các trường hợp ngoại lệ.


-5

Theo nguyên tắc chung, bạn không bao giờ nên sử dụng thử / bắt hoặc bất kỳ công cụ xử lý ngoại lệ nào để kiểm soát luồng. Mặc dù việc lặp lại phía sau hậu trường được kiểm soát thông qua việc đưa ra các StopIterationngoại lệ, bạn vẫn nên thích đoạn mã đầu tiên của mình hơn đoạn mã thứ hai.


7
Điều này đúng trong Java. Python ôm EAFP khá nặng nề.
fengb

3
Đó vẫn là một thực tế kỳ lạ. Bộ não của hầu hết các lập trình viên đều có dây để bỏ qua EAFP theo kinh nghiệm của tôi. Họ cuối cùng tự hỏi tại sao một con đường nhất định đã được chọn. Điều này đang được nói, có một thời gian và địa điểm để phá vỡ mọi quy tắc.
ilowe
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.