Tốt hơn là 'thử' một cái gì đó và bắt ngoại lệ hoặc kiểm tra nếu có thể trước tiên để tránh ngoại lệ?


139

Tôi nên kiểm tra ifmột cái gì đó là hợp lệ hoặc chỉ tryđể làm điều đó và bắt ngoại lệ?

  • Có tài liệu vững chắc nào nói rằng một cách được ưa thích không?
  • Là một cách nhiều pythonic ?

Ví dụ: tôi nên:

if len(my_list) >= 4:
    x = my_list[3]
else:
    x = 'NO_ABC'

Hoặc là:

try:
    x = my_list[3]
except IndexError:
    x = 'NO_ABC'

Một số suy nghĩ ...
PEP 20 nói:

Lỗi không bao giờ nên âm thầm vượt qua.
Trừ khi im lặng rõ ràng.

Có nên sử dụng trythay vì một lỗi ifđược hiểu một cách lặng lẽ? Và nếu vậy, bạn có im lặng rõ ràng bằng cách sử dụng nó theo cách này, do đó làm cho nó ổn?


Tôi không đề cập đến các tình huống mà bạn chỉ có thể làm mọi thứ 1 cách; ví dụ:

try:
    import foo
except ImportError:
    import baz

Câu trả lời:


161

Bạn nên thích try/excepthơn if/elsenếu kết quả trong

  • tăng tốc (ví dụ bằng cách ngăn chặn tra cứu thêm)
  • mã sạch hơn (ít dòng hơn / dễ đọc hơn)

Thông thường, những người đi tay trong tay.


tăng tốc

Trong trường hợp cố gắng tìm một phần tử trong một danh sách dài bằng cách:

try:
    x = my_list[index]
except IndexError:
    x = 'NO_ABC'

thử, ngoại trừ là tùy chọn tốt nhất khi indexcó lẽ trong danh sách và IndexError thường không được nêu ra. Bằng cách này bạn tránh được sự cần thiết phải tra cứu thêm if index < len(my_list).

Python khuyến khích việc sử dụng các ngoại lệ mà bạn xử lý là một cụm từ từ Lặn vào Python . Ví dụ của bạn không chỉ xử lý ngoại lệ (một cách duyên dáng), thay vì để nó âm thầm vượt qua , còn ngoại lệ chỉ xảy ra trong trường hợp ngoại lệ của chỉ mục không được tìm thấy (do đó là ngoại lệ từ !).


mã sạch hơn

Tài liệu Python chính thức đề cập đến EAFP : Dễ dàng yêu cầu sự tha thứ hơn sự cho phépRob Knight lưu ý rằng bắt lỗi thay vì tránh chúng , có thể dẫn đến việc đọc mã dễ dàng hơn, dễ đọc hơn. Ví dụ của anh ấy nói như thế này:

Tệ hơn (LBYL 'nhìn trước khi bạn nhảy') :

#check whether int conversion will raise an error
if not isinstance(s, str) or not s.isdigit():
    return None
elif len(s) > 10:    #too many digits for int conversion
    return None
else:
    return int(s)

Tốt hơn (EAFP: Dễ dàng yêu cầu sự tha thứ hơn sự cho phép) :

try:
    return int(s)
except (TypeError, ValueError, OverflowError): #int conversion failed
    return None

if index in mylistkiểm tra chỉ số wether là một yếu tố của danh sách của tôi, không phải là một chỉ mục có thể. Bạn sẽ muốn if index < len(mylist)thay thế.
ychaouche

1
Điều này có ý nghĩa, nhưng tôi tìm tài liệu ở đâu để làm rõ những trường hợp ngoại lệ có thể được ném cho int (). docs.python.org/3/l Library / fiances.html # int Tôi không thể tìm thấy thông tin này ở đây.
BrutalSimplility

Ngược lại, bạn có thể thêm trường hợp này (từ off. Doc ) vào câu trả lời của bạn, khi bạn nên thích if/elsehơntry/catch
rappongy

20

Trong trường hợp cụ thể này, bạn nên sử dụng một cái gì đó hoàn toàn khác:

x = myDict.get("ABC", "NO_ABC")

Tuy nhiên, nói chung: Nếu bạn muốn kiểm tra thất bại thường xuyên, hãy sử dụng if. Nếu thử nghiệm đắt tiền liên quan đến việc chỉ thử hoạt động và bắt ngoại lệ nếu thất bại, hãy sử dụng try. Nếu không một trong những điều kiện này được áp dụng, hãy đi với bất cứ điều gì đọc dễ dàng hơn.


1
+1 cho lời giải thích dưới mẫu mã, được đặt tại chỗ.
ktdrv

4
Tôi nghĩ khá rõ ràng đây không phải là những gì anh ấy đã hỏi, và giờ anh ấy đã chỉnh sửa bài đăng để làm cho nó rõ ràng hơn nữa.
agf

9

Sử dụng tryexcepttrực tiếp thay vì bên trong một ifngười bảo vệ phải luôn luôn được thực hiện nếu có bất kỳ khả năng nào của tình trạng chủng tộc. Ví dụ: nếu bạn muốn đảm bảo rằng một thư mục tồn tại, đừng làm điều này:

import os, sys
if not os.path.isdir('foo'):
  try:
    os.mkdir('foo')
  except OSError, e
    print e
    sys.exit(1)

Nếu một luồng hoặc quá trình khác tạo thư mục giữa isdirmkdir, bạn sẽ thoát. Thay vào đó, hãy làm điều này:

import os, sys, errno
try:
  os.mkdir('foo')
except OSError, e
  if e.errno != errno.EEXIST:
    print e
    sys.exit(1)

Điều đó sẽ chỉ thoát nếu thư mục 'foo' không thể được tạo.


7

Nếu nó không quan trọng để kiểm tra xem một cái gì đó sẽ thất bại trước khi bạn làm điều đó, có lẽ bạn nên ủng hộ điều đó. Rốt cuộc, việc xây dựng các ngoại lệ (bao gồm cả các tracebacks liên quan của chúng) cần có thời gian.

Các ngoại lệ nên được sử dụng cho:

  1. những điều bất ngờ, hoặc ...
  2. những thứ mà bạn cần phải nhảy nhiều hơn một mức logic (ví dụ: nơi breakkhông giúp bạn đủ xa), hoặc ...
  3. những điều mà bạn không biết chính xác điều gì sẽ xử lý ngoại lệ trước thời hạn, hoặc ...
  4. những thứ mà việc kiểm tra trước sự thất bại là tốn kém (liên quan đến việc chỉ cố gắng vận hành)

Lưu ý rằng đôi khi, câu trả lời thực sự là "không" - ví dụ, trong ví dụ đầu tiên của bạn, điều bạn thực sự nên làm chỉ là sử dụng .get()để cung cấp mặc định:

x = myDict.get('ABC', 'NO_ABC')

ngoại trừ việc đó if 'ABC' in myDict: x = myDict['ABC']; else: x = 'NO_ABC'thực sự thường nhanh hơn sử dụng get, thật không may. Không nói đây là tiêu chí quan trọng nhất, nhưng đó là điều cần phải biết.
agf

@agf: Tốt hơn để viết mã rõ ràng và súc tích. Nếu một cái gì đó cần được tối ưu hóa sau này, thật dễ dàng để quay lại và viết lại nó, nhưng c2.com/cgi/wiki?PrematureOptimization
Amber

Tôi biết rằng; quan điểm của tôi là như vậy if / elsetry / exceptcó thể có vị trí của họ ngay cả khi có các lựa chọn thay thế theo trường hợp cụ thể vì chúng có các đặc tính hiệu suất khác nhau.
agf

@agf, bạn có biết phương thức get () sẽ được cải thiện trong các phiên bản trong tương lai để nhanh nhất (tìm kiếm) không? Nhân tiện, khi tìm kiếm hai lần (như trong 'ABC' trong d: d ['ABC']), hãy thử: d ['ABC'] ngoại trừ KeyError: ... không nhanh nhất?
Remi

2
@Remi Phần chậm của .get()là tra cứu thuộc tính và phí gọi hàm ở cấp độ Python; sử dụng các từ khóa trên các phần tử tích hợp về cơ bản sẽ đúng với C. Tôi không nghĩ rằng nó sẽ trở nên nhanh hơn bất cứ lúc nào. Theo như ifso với try, đọc phương thức dict.get () trả về một con trỏ có một số thông tin hiệu suất. Tỷ lệ số lần truy cập để bỏ lỡ vấn đề ( trycó thể nhanh hơn nếu khóa hầu như luôn tồn tại) cũng như kích thước của từ điển.
agf

5

Như các bài viết khác đề cập, nó phụ thuộc vào tình hình. Có một vài nguy hiểm khi sử dụng thử / ngoại trừ thay vì kiểm tra tính hợp lệ của dữ liệu của bạn trước, đặc biệt là khi sử dụng nó cho các dự án lớn hơn.

  • Mã trong khối thử có thể có cơ hội phá hủy tất cả các loại tàn phá trước khi ngoại lệ bị bắt - nếu bạn chủ động kiểm tra trước bằng một câu lệnh if, bạn có thể tránh điều này.
  • Nếu mã được gọi trong khối thử của bạn tăng một loại ngoại lệ phổ biến, như TypeError hoặc ValueError, thì bạn có thể không thực sự bắt được ngoại lệ tương tự mà bạn đang mong đợi - đó có thể là một thứ khác đưa ra cùng một lớp ngoại lệ trước hoặc sau khi nhận được dòng nơi ngoại lệ của bạn có thể được nêu ra.

ví dụ: giả sử bạn có:

try:
    x = my_list[index_list[3]]
except IndexError:
    x = 'NO_ABC'

IndexError không nói gì về việc nó xảy ra khi cố lấy phần tử của index_list hoặc my_list.


4

Có nên sử dụng thử thay vì nếu được hiểu là lỗi truyền qua âm thầm? Và nếu vậy, bạn có im lặng rõ ràng bằng cách sử dụng nó theo cách này, do đó làm cho nó ổn?

Sử dụng trylà thừa nhận rằng một lỗi có thể vượt qua, điều ngược lại với việc nó vượt qua một cách im lặng. Sử dụng exceptlà khiến nó không vượt qua chút nào.

Sử dụng try: except:được ưa thích trong trường hợp if: else:logic phức tạp hơn. Đơn giản là tốt hơn phức tạp; phức tạp là tốt hơn phức tạp; và nó dễ dàng hơn để yêu cầu sự tha thứ hơn là sự cho phép.

Điều "lỗi không bao giờ nên âm thầm" là cảnh báo, là trường hợp mã có thể đưa ra một ngoại lệ mà bạn biết và thiết kế của bạn thừa nhận khả năng đó, nhưng bạn đã thiết kế theo cách để xử lý ngoại lệ. Theo quan điểm của tôi, rõ ràng là làm im lặng một lỗipass trong một exceptkhối, điều này chỉ nên được thực hiện với một sự hiểu biết rằng "không làm gì" thực sự là xử lý lỗi chính xác trong tình huống cụ thể. (Đây là một trong số ít lần tôi cảm thấy như một bình luận trong mã được viết tốt có lẽ thực sự cần thiết.)

Tuy nhiên, trong ví dụ cụ thể của bạn, không thích hợp:

x = myDict.get('ABC', 'NO_ABC')

Lý do tất cả mọi người đều chỉ ra điều này - mặc dù bạn thừa nhận mong muốn hiểu biết nói chung và không thể đưa ra một ví dụ tốt hơn - là các bước bên tương đương thực sự tồn tại trong khá nhiều trường hợp và tìm kiếm chúng là bước đầu tiên trong việc giải quyết vấn đề.


3

Bất cứ khi nào bạn sử dụng try/exceptcho dòng điều khiển, hãy tự hỏi:

  1. Có dễ dàng để xem khi trykhối thành công và khi nó thất bại?
  2. Bạn có biết tất cả các tác dụng phụ bên trong trykhối?
  3. Bạn có biết tất cả các trường hợp trong đó trykhối ném ngoại lệ?
  4. Nếu việc thực hiện trykhối thay đổi, liệu luồng điều khiển của bạn có hoạt động như mong đợi không?

Nếu câu trả lời cho một hoặc nhiều câu hỏi trong số này là 'không', có thể có rất nhiều sự tha thứ để yêu cầu; rất có thể từ bản thân tương lai của bạn.


Một ví dụ. Gần đây tôi đã thấy mã trong một dự án lớn hơn trông như thế này:

try:
    y = foo(x)
except ProgrammingError:
    y = bar(x)

Nói chuyện với lập trình viên, nó cho thấy luồng điều khiển dự định là:

Nếu x là số nguyên, hãy làm y = foo (x).

Nếu x là danh sách các số nguyên, hãy làm y = bar (x).

Điều này hoạt động vì foođã thực hiện một truy vấn cơ sở dữ liệu và truy vấn sẽ thành công nếu xlà số nguyên và ném ProgrammingErrornếu xlà danh sách.

Sử dụng try/exceptlà một lựa chọn tồi ở đây:

  1. Tên của ngoại lệ, ProgrammingErrorkhông đưa ra vấn đề thực tế (đóx không phải là số nguyên), điều này gây khó khăn cho việc xem những gì đang xảy ra.
  2. Việc ProgrammingErrornày được nêu ra trong một cuộc gọi cơ sở dữ liệu, gây lãng phí thời gian. Mọi thứ sẽ trở nên thực sự khủng khiếp nếu hóa ra việc fooghi một cái gì đó vào cơ sở dữ liệu trước khi nó đưa ra một ngoại lệ, hoặc thay đổi trạng thái của một số hệ thống khác.
  3. Không rõ nếu ProgrammingErrorchỉ được nêu ra khi xmột danh sách các số nguyên. Ví dụ, giả sử có một lỗi đánh máy trong footruy vấn cơ sở dữ liệu. Điều này cũng có thể nâng cao a ProgrammingError. Hậu quả là bar(x)bây giờ cũng được gọi là khi xlà một số nguyên. Điều này có thể làm tăng các ngoại lệ khó hiểu hoặc tạo ra kết quả không lường trước được.
  4. Các try/exceptkhối thêm một yêu cầu cho tất cả các triển khai trong tương lai foo. Bất cứ khi nào chúng tôi thay đổi foo, bây giờ chúng tôi phải suy nghĩ về cách nó xử lý danh sách và đảm bảo rằng nó ném ProgrammingErrorvà không, nói, có AttributeErrorhoặc không có lỗi gì cả.

0

Đối với một ý nghĩa chung, bạn có thể xem xét việc đọc Thành ngữ và Chống thành ngữ trong Python: Ngoại lệ .

Trong trường hợp cụ thể của bạn, như những người khác đã nêu, bạn nên sử dụng dict.get():

lấy (khóa [, mặc định])

Trả về giá trị cho khóa nếu khóa nằm trong từ điển, mặc định khác. Nếu mặc định không được cung cấp, nó mặc định là Không có, do đó phương thức này không bao giờ tăng KeyError.


Tôi không nghĩ rằng liên kết bao gồm tình huống này. Các ví dụ của nó là về việc xử lý những thứ đại diện cho lỗi thực sự , chứ không phải về việc có nên sử dụng xử lý ngoại lệ cho các tình huống dự kiến ​​hay không.
agf

Liên kết đầu tiên đã lỗi thời.
Timofei Bondarev
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.