Như bạn đã đoán chính xác, có hai mặt của nó: Bắt bất kỳ lỗi nào bằng cách chỉ định không có loại ngoại lệ nào sau đó except
và chỉ cần chuyển nó mà không thực hiện bất kỳ hành động nào.
Lời giải thích của tôi là một chút, một chút nữa, vì vậy, nó bị phá vỡ:
- Đừng bắt bất kỳ lỗi nào . Luôn chỉ định ngoại lệ nào bạn chuẩn bị khôi phục và chỉ bắt những ngoại lệ đó.
- Cố gắng tránh đi vào ngoại trừ các khối . Trừ khi mong muốn rõ ràng, đây thường không phải là một dấu hiệu tốt.
Nhưng hãy đi vào chi tiết:
Đừng bắt bất kỳ lỗi
Khi sử dụng một try
khối, bạn thường làm điều này bởi vì bạn biết rằng có khả năng có một ngoại lệ bị ném. Như vậy, bạn cũng đã có một ý tưởng gần đúng về những gì có thể phá vỡ và ngoại lệ nào có thể được ném ra. Trong những trường hợp như vậy, bạn bắt gặp một ngoại lệ vì bạn có thể phục hồi tích cực từ nó. Điều đó có nghĩa là bạn đã chuẩn bị cho ngoại lệ và có một số kế hoạch thay thế mà bạn sẽ tuân theo trong trường hợp ngoại lệ đó.
Ví dụ: khi bạn yêu cầu người dùng nhập số, bạn có thể chuyển đổi đầu vào bằng cách sử dụng int()
số có thể tăng a ValueError
. Bạn có thể dễ dàng phục hồi điều đó bằng cách đơn giản yêu cầu người dùng thử lại, do đó, bắt ValueError
và nhắc lại người dùng sẽ là một kế hoạch phù hợp. Một ví dụ khác là nếu bạn muốn đọc một số cấu hình từ một tệp và tệp đó xảy ra không tồn tại. Vì là tệp cấu hình, bạn có thể có một số cấu hình mặc định là dự phòng, vì vậy tệp không chính xác cần thiết. Vì vậy, bắt một FileNotFoundError
và chỉ cần áp dụng cấu hình mặc định sẽ là một kế hoạch tốt ở đây. Bây giờ trong cả hai trường hợp này, chúng tôi có một ngoại lệ rất cụ thể mà chúng tôi mong đợi và có một kế hoạch cụ thể như nhau để phục hồi từ nó. Như vậy, trong mỗi trường hợp, chúng tôi chỉ rõ ràng except
rằng ngoại lệ.
Tuy nhiên, nếu chúng ta bắt được tất cả mọi thứ , thì ngoài những trường hợp ngoại lệ đó, chúng ta đã sẵn sàng để phục hồi từ hồi còn có một cơ hội mà chúng ta không có ngoại lệ mà chúng ta không mong đợi và chúng ta thực sự không thể phục hồi; hoặc không nên phục hồi từ.
Hãy lấy ví dụ về tập tin cấu hình từ trên. Trong trường hợp tệp bị thiếu, chúng tôi chỉ áp dụng cấu hình mặc định của mình và có thể quyết định sau đó sẽ tự động lưu cấu hình (vì vậy lần sau, tệp tồn tại). Bây giờ hãy tưởng tượng chúng ta có một IsADirectoryError
, hoặc mộtPermissionError
thay thế. Trong những trường hợp như vậy, có lẽ chúng tôi không muốn tiếp tục; chúng tôi vẫn có thể áp dụng cấu hình mặc định của mình, nhưng sau đó chúng tôi sẽ không thể lưu tệp. Và có khả năng người dùng cũng có cấu hình tùy chỉnh, vì vậy sử dụng các giá trị mặc định có thể không mong muốn. Vì vậy, chúng tôi muốn nói với người dùng về nó ngay lập tức và có thể hủy bỏ việc thực hiện chương trình. Nhưng đó không phải là thứ chúng tôi muốn làm ở đâu đó sâu bên trong một phần mã nhỏ; đây là một cái gì đó có tầm quan trọng ở cấp ứng dụng, vì vậy nó nên được xử lý ở cấp cao nhất vì vậy hãy để ngoại lệ nổi lên.
Một ví dụ đơn giản khác cũng được đề cập trong tài liệu thành ngữ Python 2 . Ở đây, một lỗi đánh máy đơn giản tồn tại trong mã khiến nó bị hỏng. Bởi vì chúng tôi đang bắt mọi ngoại lệ, chúng tôi cũng bắt được NameError
s và SyntaxError
s . Cả hai đều là những sai lầm xảy ra với tất cả chúng ta trong khi lập trình; và cả hai đều là những lỗi chúng tôi hoàn toàn không muốn đưa vào khi vận chuyển mã. Nhưng bởi vì chúng tôi cũng đã bắt được chúng, chúng tôi thậm chí sẽ không biết rằng chúng xảy ra ở đó và mất bất kỳ trợ giúp nào để gỡ lỗi chính xác.
Nhưng cũng có những trường hợp ngoại lệ nguy hiểm hơn mà chúng ta khó có thể chuẩn bị cho. Ví dụ, SystemError thường là thứ hiếm khi xảy ra và chúng ta không thể thực sự lên kế hoạch cho; nó có nghĩa là có một cái gì đó phức tạp hơn đang diễn ra, một cái gì đó có khả năng ngăn chúng ta tiếp tục nhiệm vụ hiện tại.
Trong mọi trường hợp, rất khó có khả năng bạn chuẩn bị cho mọi thứ trong một phần quy mô nhỏ của mã, vì vậy đó thực sự là nơi bạn chỉ nên nắm bắt những ngoại lệ mà bạn đã chuẩn bị. Một số người đề nghị ít nhất bắt Exception
vì nó sẽ không bao gồm những thứ như SystemExit
và KeyboardInterrupt
đó bằng cách thiết kế là để chấm dứt các ứng dụng của bạn, nhưng tôi cho rằng điều này vẫn còn xa quá không đặc hiệu. Chỉ có một nơi mà cá nhân tôi chấp nhận bắt Exception
hoặc chỉ bất kỳngoại lệ, và đó là trong một trình xử lý ngoại lệ cấp ứng dụng toàn cầu duy nhất có mục đích duy nhất để ghi lại bất kỳ ngoại lệ nào mà chúng tôi chưa chuẩn bị. Bằng cách đó, chúng tôi vẫn có thể giữ lại nhiều thông tin về các trường hợp ngoại lệ không mong muốn, sau đó chúng tôi có thể sử dụng để mở rộng mã của mình để xử lý các mã đó một cách rõ ràng (nếu chúng tôi có thể khôi phục từ chúng) hoặc trong trường hợp có lỗi Lỗi để tạo các trường hợp kiểm tra để đảm bảo nó sẽ không xảy ra lần nữa Nhưng tất nhiên, điều đó chỉ hiệu quả nếu chúng ta chỉ bắt gặp những ngoại lệ mà chúng ta đã mong đợi, vì vậy những điều chúng ta không mong đợi sẽ tự nhiên nổi lên.
Cố gắng tránh đi vào ngoại trừ các khối
Khi rõ ràng nắm bắt một lựa chọn nhỏ của các ngoại lệ cụ thể, có nhiều tình huống chúng ta sẽ ổn bằng cách đơn giản không làm gì cả. Trong những trường hợp như vậy, chỉ cần có except SomeSpecificException: pass
là tốt. Hầu hết thời gian, đây không phải là trường hợp vì chúng ta có thể cần một số mã liên quan đến quá trình khôi phục (như đã đề cập ở trên). Đây có thể là ví dụ một cái gì đó thử lại hành động hoặc để thiết lập một giá trị mặc định thay thế.
Nếu đó không phải là trường hợp, ví dụ vì mã của chúng tôi đã được cấu trúc để lặp lại cho đến khi thành công, thì chỉ cần vượt qua là đủ tốt. Lấy ví dụ của chúng tôi từ trên, chúng tôi có thể muốn yêu cầu người dùng nhập số. Bởi vì chúng tôi biết rằng người dùng không muốn làm những gì chúng tôi yêu cầu, nên chúng tôi có thể đặt nó vào một vòng lặp ở vị trí đầu tiên, vì vậy nó có thể trông như thế này:
def askForNumber ():
while True:
try:
return int(input('Please enter a number: '))
except ValueError:
pass
Bởi vì chúng tôi tiếp tục cố gắng cho đến khi không có ngoại lệ nào bị ném, chúng tôi không cần phải làm gì đặc biệt trong khối ngoại trừ, vì vậy điều này là tốt. Nhưng tất nhiên, người ta có thể lập luận rằng ít nhất chúng tôi muốn hiển thị cho người dùng một số thông báo lỗi để cho anh ta biết lý do tại sao anh ta phải lặp lại đầu vào.
Tuy nhiên, trong nhiều trường hợp khác, chỉ cần vượt qua except
là một dấu hiệu cho thấy chúng ta không thực sự chuẩn bị cho ngoại lệ mà chúng ta đang nắm bắt. Trừ khi những ngoại lệ đó là đơn giản (như ValueError
hoặc TypeError
), và lý do tại sao chúng ta có thể vượt qua là rõ ràng, hãy cố gắng tránh chỉ vượt qua. Nếu thực sự không có gì để làm (và bạn hoàn toàn chắc chắn về điều đó), thì hãy xem xét thêm một nhận xét tại sao lại như vậy; mặt khác, mở rộng khối ngoại trừ để thực sự bao gồm một số mã khôi phục.
except: pass
Người phạm tội tồi tệ nhất là sự kết hợp của cả hai. Điều này có nghĩa là chúng tôi sẵn sàng bắt bất kỳ lỗi nào mặc dù chúng tôi hoàn toàn không chuẩn bị cho nó và chúng tôi cũng không làm gì với nó. Bạn ít nhất muốn đăng nhập lỗi và cũng có khả năng reraise nó vẫn chấm dứt việc áp dụng (đó là khó xảy ra, bạn có thể tiếp tục như bình thường sau một MemoryError). Chỉ cần vượt qua mặc dù sẽ không chỉ giữ cho ứng dụng tồn tại phần nào (tất nhiên tùy thuộc vào nơi bạn bắt được), mà còn vứt bỏ tất cả thông tin, khiến bạn không thể phát hiện ra lỗi lỗi, điều này đặc biệt đúng nếu bạn không phải là người phát hiện ra nó.
Vì vậy, điểm mấu chốt là: Chỉ nắm bắt các ngoại lệ bạn thực sự mong đợi và sẵn sàng phục hồi; tất cả những lỗi khác có thể là lỗi bạn nên sửa, hoặc điều gì đó bạn chưa chuẩn bị cho dù sao đi nữa. Vượt qua các ngoại lệ cụ thể là tốt nếu bạn thực sự không cần phải làm gì đó với chúng. Trong tất cả các trường hợp khác, đó chỉ là dấu hiệu của sự suy đoán và lười biếng. Và bạn chắc chắn muốn khắc phục điều đó.
logging
mô-đun ở cấp DEBUG để tránh chúng phát ra trong sản xuất, nhưng giữ cho chúng có sẵn trong quá trình phát triển.