2010-06-21 22 views
15

Nó được nói ở một vài nơi (herehere) rằng sự nhấn mạnh của Python về "dễ dàng hơn để yêu cầu sự tha thứ hơn sự cho phép" (EAFP) nên được ủ với ý tưởng rằng ngoại lệ chỉ nên được gọi trong trường hợp thật sự đặc biệt. Hãy xem xét những điều sau đây, trong đó chúng ta đang popping và đẩy vào một hàng đợi ưu tiên cho đến khi chỉ có một yếu tố còn lại:Ngoại lệ của Python: EAFP và Điều gì thực sự vượt trội?

import heapq 
... 
pq = a_list[:] 
heapq.heapify(pq) 
while True: 
    min1 = heapq.heappop(pq) 
    try: 
     min2 = heapq.heappop(pq) 
    except IndexError: 
     break 
    else 
     heapq.heappush(pq, min1 + min2) 
# do something with min1 

Ngoại lệ chỉ được nêu ra một lần trong len(a_list) lặp của vòng lặp, nhưng nó không thực sự đặc biệt, bởi vì chúng ta biết nó sẽ xảy ra cuối cùng. Thiết lập này giúp chúng tôi kiểm tra xem liệu a_list có trống không nhiều lần, nhưng (có thể) nó ít có thể đọc được hơn là sử dụng các điều kiện rõ ràng.

Sự đồng thuận về việc sử dụng ngoại lệ cho loại logic chương trình không đặc biệt này là gì?

+0

Mã của bạn trông lạ với tôi. 'heapify' biến đổi một danh sách * tại chỗ * thành một đống. Kết quả của 'pq = heapq.heapify (a_list)', 'pq' sẽ là' None'. – stephan

+1

Bạn nói đúng! Lỗi ngớ ngẩn mà tôi đã thực hiện khi cố gắng chắt lọc mã thực sự của tôi vào một thứ phù hợp cho một ví dụ. Bây giờ nó đã được sửa. –

Trả lời

28

exceptions should only be called in truly exceptional cases

Không bằng Python: ví dụ, mỗifor vòng lặp (trừ khi nó sớm break s hoặc return s) chấm dứt bởi một ngoại lệ (StopIteration) bị ném và bắt. Vì vậy, một ngoại lệ xảy ra một lần trên mỗi vòng lặp là không lạ với Python - nó có thường xuyên hơn không!

Nguyên tắc được đề cập có thể rất quan trọng trong các ngôn ngữ khác, nhưng chắc chắn không có lý do gì để áp dụng nguyên tắc đó cho Python, nơi nó trái ngược với đặc tính của ngôn ngữ.

Trong trường hợp này tôi thích viết lại Jon (nên đơn giản hơn bằng cách loại bỏ nhánh khác) vì nó làm cho mã nhỏ gọn hơn - một lý do thực dụng, nhất định không phải là "ủ" kiểu Python với nguyên tắc ngoại lai .

+0

Điểm tốt về việc đơn giản hóa Alex - đã hoàn tất! –

+0

Với EAFP, làm thế nào để bạn tránh được các lỗi thầm lặng gây ra bằng cách bắt một ngoại lệ khác với bạn mong đợi? Ví dụ: 'try: r = f (x.a) ngoại trừ AttributeError: r = 0'. Bạn nghĩ bạn chỉ gán 'r = 0' khi' x' không có thuộc tính 'a'. Nhưng nó chỉ xảy ra rằng một điều kiện bên trong trong 'f' đã ném cùng một ngoại lệ, và bạn bắt được nó, vô tình. Tôi có thực sự phải phân chia mọi biểu thức thành các phần nguyên tử, trước khi cho một trong những phân đoạn này vào 'try' /' trừ'? – max

+0

Max, tôi nghĩ cách tiếp cận tốt nhất trong trường hợp đó là thêm một khối 'try/accept' vào hàm' f' để bắt nội bộ 'AttributeError' trước khi nó đạt đến trình xử lý bên ngoài. –

6

Nhìn vào the docs Tôi nghĩ bạn có thể an toàn viết lại các chức năng như sau:

import heapq 
... 
pq = heapq.heapify(a_list) 
while pq: 
    min1 = heapq.heappop(pq) 
    if pq: 
     min2 = heapq.heappop(pq) 
     heapq.heappush(pq, min1 + min2) 
# do something with min1 

..và từ đó tránh được những thử-trừ.

Chuyển đến cuối danh sách mà bạn biết sẽ xảy ra ở đây không phải là ngoại lệ - nó thật đáng sợ! Vì vậy, thực hành tốt hơn sẽ là để xử lý nó trước. Nếu bạn có cái gì đó khác trong một chủ đề khác đã được tiêu thụ từ cùng một đống sau đó sử dụng try-except sẽ có ý nghĩa hơn nhiều (tức là xử lý một trường hợp đặc biệt/không thể đoán trước).

Nói chung, tôi sẽ tránh thử-excepts ở bất cứ đâu tôi có thể kiểm tra và tránh thất bại trước. Điều này buộc bạn phải nói "Tôi biết tình huống xấu này có thể xảy ra vì vậy đây là cách tôi giải quyết nó". Theo tôi, bạn sẽ có xu hướng viết mã dễ đọc hơn.

[Chỉnh sửa] Cập nhật ví dụ theo đề nghị của Alex

3

tôi đã tìm thấy việc thực hành của việc sử dụng ngoại lệ như các công cụ kiểm soát dòng chảy "bình thường" trở thành một khá chấp nhận rộng rãi trong Python. Nó được sử dụng phổ biến nhất trong các tình huống như mô tả của bạn, khi bạn kết thúc một chuỗi thứ tự.

Theo ý kiến ​​của tôi, đó là việc sử dụng ngoại lệ hoàn toàn hợp lệ. Bạn cần phải cẩn thận về việc sử dụng xử lý ngoại lệ mặc dù vậy. Tăng một ngoại lệ là một hoạt động hợp lý tốn kém, và do đó tốt nhất là đảm bảo bạn chỉ dựa vào một ngoại lệ ở cuối chuỗi, không phải trong mỗi lần lặp lại.

8

Trường hợp ngoại lệ ném rất tốn kém ở hầu hết các ngôn ngữ cấp thấp như C++. Điều đó ảnh hưởng rất nhiều đến "sự khôn ngoan thông thường" về ngoại lệ và không áp dụng quá nhiều cho các ngôn ngữ chạy trong máy ảo, như Python. Không có một chi phí lớn như vậy trong Python vì sử dụng một ngoại lệ thay vì một điều kiện.

(Đây là trường hợp "trí tuệ thông thường" trở thành vấn đề của thói quen. Mọi người đến từ kinh nghiệm trong một loại môi trường - ngôn ngữ cấp thấp - và sau đó áp dụng nó nó có ý nghĩa.)

Ngoại lệ, nói chung là ngoại lệ. Điều đó không có nghĩa là chúng không xảy ra thường xuyên; nó có nghĩa là chúng là ngoại lệ. Chúng là những thứ sẽ có xu hướng phá vỡ từ dòng mã thông thường, và phần lớn thời gian bạn không muốn phải xử lý từng cái một - đó là điểm xử lý ngoại lệ. Phần này giống với Python như trong C++ và tất cả các ngôn ngữ khác có ngoại lệ.

Tuy nhiên, điều đó có xu hướng xác định khi ngoại lệ là ném. Bạn đang nói về trường hợp ngoại lệ phải là bị bắt. Rất đơn giản, đừng lo lắng về nó: trường hợp ngoại lệ không đắt tiền, do đó, không đi đến độ dài để cố gắng ngăn chặn chúng bị ném. Rất nhiều mã Python được thiết kế xung quanh điều này.

Tôi không đồng ý với đề xuất của Jon để thử nghiệm và tránh các ngoại lệ trước. Đó là tốt nếu nó dẫn đến mã rõ ràng hơn, như trong ví dụ của mình. Tuy nhiên, trong nhiều trường hợp nó chỉ làm phức tạp mọi thứ - nó có hiệu quả có thể dẫn đến kiểm tra trùng lặp và giới thiệu lỗi. Ví dụ:

import os, errno, stat 

def read_file(fn): 
    """ 
    Read a file and return its contents. If the file doesn't exist or 
    can't be read, return "". 
    """ 
    try: 
     return open(fn).read() 
    except IOError, e: 
     return "" 

def read_file_2(fn): 
    """ 
    Read a file and return its contents. If the file doesn't exist or 
    can't be read, return "". 
    """ 
    if not os.access(fn, os.R_OK): 
     return "" 
    st = os.stat(fn) 
    if stat.S_ISDIR(st.st_mode): 
     return "" 
    return open(fn).read() 

print read_file("x") 

Chắc chắn, chúng tôi có thể kiểm tra và tránh thất bại - nhưng chúng tôi đã gặp những vấn đề phức tạp. Chúng tôi đang cố gắng đoán tất cả các cách truy cập tập tin có thể thất bại (và điều này không bắt được tất cả), chúng tôi có thể đã giới thiệu điều kiện chủng tộc, và chúng tôi đang làm rất nhiều công việc I/O. Điều này là tất cả được thực hiện cho chúng tôi - chỉ cần bắt ngoại lệ.

4

Chỉ cần cho các hồ sơ, tôi muốn viết là như thế này:

import heapq 
a_list = range(20) 
pq = a_list[:] 
heapq.heapify(pq) 
try: 
    while True: 
     min1 = heapq.heappop(pq) 
     min2 = heapq.heappop(pq) 
     heapq.heappush(pq, min1 + min2) 
except IndexError: 
    pass # we ran out of numbers in pq 

Exceptions có thể để lại một vòng lặp (thậm chí chức năng) và bạn có thể sử dụng chúng cho điều đó. Kể từ khi Python ném chúng ở khắp mọi nơi, tôi nghĩ rằng mô hình này là khá hữu ích (thậm chí pythonic).

Các vấn đề liên quan