Làm thế nào để xử lý các ngoại lệ trong một danh sách hiểu?


120

Tôi có một số cách hiểu danh sách bằng Python trong đó mỗi lần lặp có thể đưa ra một ngoại lệ.

Ví dụ , nếu tôi có:

eggs = (1,3,0,3,2)

[1/egg for egg in eggs]

Tôi sẽ nhận được một ZeroDivisionErrorngoại lệ trong phần tử thứ 3.

Làm cách nào tôi có thể xử lý ngoại lệ này và tiếp tục thực hiện việc hiểu danh sách?

Cách duy nhất tôi có thể nghĩ đến là sử dụng một hàm trợ giúp:

def spam(egg):
    try:
        return 1/egg
    except ZeroDivisionError:
        # handle division by zero error
        # leave empty for now
        pass

Nhưng điều này trông hơi rườm rà với tôi.

Có cách nào tốt hơn để làm điều này bằng Python không?

Lưu ý: Đây là một ví dụ đơn giản (xem " ví dụ " ở trên) mà tôi đã lấy vì ví dụ thực của tôi yêu cầu một số ngữ cảnh. Tôi không quan tâm đến việc tránh lỗi chia cho 0 nhưng trong việc xử lý các trường hợp ngoại lệ trong việc hiểu danh sách.


4
Có một PEP 463 để thêm một biểu thức để xử lý các ngoại lệ. Trong ví dụ của bạn, nó sẽ là [1/egg except ZeroDivisionError: None for egg in (1,3,0,3,2)]. Nhưng nó vẫn ở chế độ nháp. Cảm giác gan ruột của tôi là nó sẽ không được chấp nhận. Biểu thức Imho có thể trở nên quá lộn xộn (kiểm tra nhiều ngoại lệ, có nhiều kết hợp phức tạp hơn (nhiều toán tử logic, hiểu phức tạp, v.v.)
cfi

1
Lưu ý rằng đối với ví dụ cụ thể này , bạn có thể sử dụng một numpy ndarrayvới các cài đặt thích hợp trong np.seterr. Điều đó sẽ dẫn đến 1/0 = nan. Nhưng tôi nhận ra rằng điều đó không khái quát cho các tình huống khác khi nhu cầu này phát sinh.
gerrit

Câu trả lời:


96

Không có biểu thức tích hợp nào trong Python cho phép bạn bỏ qua một ngoại lệ (hoặc trả về các giá trị thay thế & c trong trường hợp ngoại lệ), vì vậy, nói theo nghĩa đen là không thể "xử lý các ngoại lệ trong một danh sách hiểu" vì một hiểu danh sách là một biểu thức chứa biểu thức khác, không có gì khác (tức là không có câu lệnh nào và chỉ có câu lệnh mới có thể bắt / bỏ qua / xử lý các ngoại lệ).

Lời gọi hàm là biểu thức và các thân hàm có thể bao gồm tất cả các câu lệnh bạn muốn, vì vậy, việc ủy ​​quyền đánh giá biểu thức phụ dễ xảy ra ngoại lệ cho một hàm, như bạn đã nhận thấy, là một cách giải quyết khả thi (những cách khác, khi khả thi, là kiểm tra các giá trị có thể gây ra ngoại lệ, như cũng được đề xuất trong các câu trả lời khác).

Các câu trả lời chính xác cho câu hỏi "làm thế nào để xử lý các trường hợp ngoại lệ trong việc hiểu danh sách" đều thể hiện một phần của tất cả sự thật này: 1) theo nghĩa đen, tức là về mặt từ vựng trong bản thân phần hiểu, bạn không thể; 2) thực tế, bạn ủy thác công việc cho một chức năng hoặc kiểm tra các giá trị dễ xảy ra lỗi khi điều đó khả thi. Việc bạn lặp đi lặp lại tuyên bố rằng đây không phải là câu trả lời là không có cơ sở.


14
Tôi hiểu rồi. Vì vậy, một câu trả lời đầy đủ là tôi nên: 1. sử dụng một hàm 2. không sử dụng khả năng hiểu danh sách 3. cố gắng ngăn chặn ngoại lệ hơn là xử lý nó.
Nathan Fellman

9
Tôi không thấy "không sử dụng cách hiểu danh sách" là một phần của câu trả lời cho "cách xử lý các trường hợp ngoại lệ trong cách hiểu danh sách", nhưng tôi đoán bạn có thể thấy đó là một hệ quả có thể có của "từ vựng TRONG LC, không thể xử lý các ngoại lệ ", đây thực sự là phần đầu tiên của câu trả lời theo nghĩa đen.
Alex Martelli

Bạn có thể gặp lỗi trong biểu thức trình tạo hoặc hiểu trình tạo không?

1
@AlexMartelli, điều khoản ngoại trừ có khó hoạt động trong các phiên bản python trong tương lai không? [x[1] for x in list except IndexError pass]. Trình thông dịch không thể tạo một hàm tạm thời để thử x[1]?
alancalvitti

@Nathan, 1,2,3 ở trên trở thành vấn đề đau đầu trong các luồng dữ liệu chức năng trong đó 1. người ta thường muốn nội tuyến các chức năng thông qua lambdas; 2. thay thế là sử dụng nhiều vòng lặp for lồng nhau vi phạm mô hình chức năng và dẫn đến mã dễ bị lỗi; 3. các lỗi thường là các tập dữ liệu phức tạp đặc biệt và tiềm ẩn, như từ latin có nghĩa là dữ liệu, do đó không thể dễ dàng ngăn chặn được.
alancalvitti

118

Tôi nhận thấy câu hỏi này khá cũ, nhưng bạn cũng có thể tạo một hàm chung để thực hiện loại việc này dễ dàng hơn:

def catch(func, handle=lambda e : e, *args, **kwargs):
    try:
        return func(*args, **kwargs)
    except Exception as e:
        return handle(e)

Sau đó, trong sự hiểu biết của bạn:

eggs = (1,3,0,3,2)
[catch(lambda : 1/egg) for egg in eggs]
[1, 0, ('integer division or modulo by zero'), 0, 0]

Tất nhiên, bạn có thể đặt chức năng xử lý mặc định bất cứ điều gì bạn muốn (giả sử bạn muốn trả về 'Không' theo mặc định).

Hy vọng điều này sẽ giúp bạn hoặc bất kỳ người xem tương lai nào của câu hỏi này!

Lưu ý: trong python 3, tôi sẽ chỉ tạo từ khóa đối số 'xử lý' và đặt nó ở cuối danh sách đối số. Điều này sẽ làm cho các đối số thực sự truyền và như vậy thông qua bắt tự nhiên hơn nhiều.


2
cực kỳ hữu ích, cảm ơn. Mặc dù tôi đồng ý với các nhận xét lý thuyết, nhưng điều này cho thấy một cách tiếp cận thực tế để giải quyết một vấn đề mà tôi đã nhiều lần gặp phải.
Paul

2
Tạo câu trả lời. Một mod mà tôi khuyên bạn nên chuyển qua argskwargsxử lý luôn. Bằng cách đó, bạn có thể trả về say eggthay vì mã cứng 0hoặc ngoại lệ như bạn đang làm.
Mad Physicist

3
Bạn cũng có thể muốn loại ngoại lệ làm đối số tùy chọn (loại ngoại lệ có thể được tham số không?), Để các ngoại lệ không mong muốn được đưa lên trên thay vì bỏ qua tất cả các ngoại lệ.
00prometheus

3
@Bryan, bạn có thể cung cấp mã cho "trong python 3, tôi chỉ tạo từ khóa đối số 'handle' và đặt nó ở cuối danh sách đối số không." đã thử đặt handlesau **kwargvà nhận được một SyntaxError. Bạn có ý muốn bỏ qua như kwargs.get('handle',e)?
alancalvitti

21

Bạn có thể dùng

[1/egg for egg in eggs if egg != 0]

điều này sẽ chỉ đơn giản là bỏ qua các phần tử bằng không.


28
Điều này không trả lời câu hỏi làm thế nào để xử lý các ngoại lệ trong việc hiểu danh sách.
Nathan Fellman

8
ừm, đúng vậy. nó không cần thiết phải xử lý các ngoại lệ. vâng, đó không phải là giải pháp phù hợp mọi lúc, nhưng đó là giải pháp phổ biến.
Peter

3
Tôi hiểu. Tôi rút lại nhận xét (mặc dù tôi sẽ không xóa nó vì 'cuộc thảo luận' ngắn đó cải thiện câu trả lời).
Nathan Fellman

11

Không có cách nào tốt hơn. Trong nhiều trường hợp, bạn có thể sử dụng biện pháp tránh né như Peter

Tùy chọn khác của bạn là không sử dụng phần hiểu

eggs = (1,3,0,3,2)

result=[]
for egg in eggs:
    try:
        result.append(egg/0)
    except ZeroDivisionError:
        # handle division by zero error
        # leave empty for now
        pass

Tùy bạn quyết định xem điều đó có cồng kềnh hơn hay không


1
Làm thế nào tôi sẽ sử dụng hiểu ở đây?
Nathan Fellman

@Nathan: bạn sẽ không. gnibbler nói: Không có không phải là một cách tốt hơn
SilentGhost

Xin lỗi ... Tôi đã bỏ lỡ 'không' trong câu trả lời của mình :-)
Nathan Fellman

4

Tôi nghĩ rằng một chức năng trợ giúp, như được đề xuất bởi người đặt câu hỏi ban đầu và Bryan Head, là tốt và không rườm rà chút nào. Một dòng mã ma thuật duy nhất thực hiện tất cả công việc không phải lúc nào cũng có thể thực hiện được vì vậy hàm trợ giúp là giải pháp hoàn hảo nếu người ta muốn tránh forcác vòng lặp. Tuy nhiên, tôi sẽ sửa đổi nó thành cái này:

# A modified version of the helper function by the Question starter 
def spam(egg):
    try:
        return 1/egg, None
    except ZeroDivisionError as err:
        # handle division by zero error        
        return None, err

Đầu ra sẽ là cái này [(1/1, None), (1/3, None), (None, ZeroDivisionError), (1/3, None), (1/2, None)]. Với câu trả lời này, bạn có toàn quyền kiểm soát để tiếp tục theo bất kỳ cách nào bạn muốn.


Đẹp. Điều này trông rất giống với Eitherkiểu trong một số ngôn ngữ lập trình chức năng (như Scala), trong đó một Eithercó thể chứa giá trị của kiểu này hoặc kiểu khác, nhưng không phải cả hai. Sự khác biệt duy nhất là trong những ngôn ngữ đó, việc đặt lỗi ở phía bên trái và giá trị ở phía bên phải là thành ngữ. Đây là một bài báo với nhiều thông tin hơn .
Alex Palmer

3

Tôi không thấy bất kỳ câu trả lời nào đề cập đến điều này. Nhưng ví dụ này sẽ là một cách để ngăn không cho một ngoại lệ được nêu ra đối với các trường hợp thất bại đã biết.

eggs = (1,3,0,3,2)
[1/egg if egg > 0 else None for egg in eggs]


Output: [1, 0, None, 0, 0]

Đó không phải là giống với câu trả lời này? stackoverflow.com/a/1528244/1084
Nathan Fellman

Có một sự khác biệt nhỏ. Bộ lọc được áp dụng trên đầu ra chứ không phải danh sách đầu vào. Như bạn có thể thấy trong ví dụ đã đăng, tôi đã ký hiệu "Không có" cho trường hợp có thể gây ra ngoại lệ.
Slakker
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.