Python: Làm thế nào tôi có thể biết ngoại lệ nào có thể được ném ra từ một cuộc gọi phương thức


87

Có cách nào để biết (tại thời điểm viết mã) ngoại lệ nào sẽ xảy ra khi thực thi mã python không? Tôi kết thúc việc bắt lớp Ngoại lệ cơ sở 90% thời gian vì tôi không biết loại ngoại lệ nào có thể được ném (và đừng bảo tôi đọc tài liệu. Nhiều lần một ngoại lệ có thể được lan truyền từ sâu và nhiều lần tài liệu không được cập nhật hoặc chính xác). Có một số loại công cụ để kiểm tra điều này? (như bằng cách đọc mã python và libs)?


2
Hãy nhớ rằng trong Python <2,6, bạn cũng có thể raisexâu chuỗi chứ không chỉ các BaseExceptionlớp con. Vì vậy, nếu bạn đang gọi vào mã thư viện nằm ngoài tầm kiểm soát của bạn, thậm chí except Exceptionlà không đủ, vì nó sẽ không bắt các ngoại lệ chuỗi. Như những người khác đã chỉ ra, bạn đang trồng sai cây ở đây.
Daniel Pryden

Tôi không biết điều đó. Tôi nghĩ ngoại trừ Ngoại lệ: .. nắm bắt được hầu hết mọi thứ.
GabiMe

2
except Exceptionhoạt động tốt để bắt các ngoại lệ chuỗi trong Python 2.6 trở lên.
Jeffrey Harris

Câu trả lời:


22

Tôi đoán một giải pháp chỉ có thể không chính xác vì thiếu các quy tắc nhập tĩnh.

Tôi không biết một số công cụ kiểm tra ngoại lệ, nhưng bạn có thể nghĩ ra công cụ của riêng mình phù hợp với nhu cầu của bạn (một cơ hội tốt để chơi một chút với phân tích tĩnh).

Lần thử đầu tiên, bạn có thể viết một hàm tạo AST, tìm tất cả Raisecác nút và sau đó cố gắng tìm ra các mẫu phổ biến của việc nâng cao các ngoại lệ (ví dụ: gọi trực tiếp một hàm tạo)

Hãy xlà chương trình sau:

x = '''\
if f(x):
    raise IOError(errno.ENOENT, 'not found')
else:
    e = g(x)
    raise e
'''

Xây dựng AST bằng cách sử dụng compilergói:

tree = compiler.parse(x)

Sau đó, xác định một Raiselớp khách truy cập:

class RaiseVisitor(object):
    def __init__(self):
        self.nodes = []
    def visitRaise(self, n):
        self.nodes.append(n)

Và đi bộ qua Raisecác nút thu thập AST :

v = RaiseVisitor()
compiler.walk(tree, v)

>>> print v.nodes
[
    Raise(
        CallFunc(
            Name('IOError'),
            [Getattr(Name('errno'), 'ENOENT'), Const('not found')],
            None, None),
        None, None),
    Raise(Name('e'), None, None),
]

Bạn có thể tiếp tục bằng cách giải quyết các ký hiệu bằng cách sử dụng bảng ký hiệu của trình biên dịch, phân tích sự phụ thuộc dữ liệu, v.v. Hoặc bạn có thể chỉ suy luận rằng CallFunc(Name('IOError'), ...)"chắc chắn có nghĩa là nâng cao IOError", điều này khá tốt cho kết quả thực tế nhanh chóng :)


Cảm ơn vì câu trả lời thú vị này. Tôi không hiểu tại sao tôi phải tìm kiếm thứ gì đó khác hơn là tất cả các nút tăng. Tại sao tôi nên "giải quyết các ký hiệu bằng cách sử dụng các bảng ký hiệu trình biên dịch, phân tích sự phụ thuộc dữ liệu"? Không phải cách duy nhất để tăng ngoại lệ là tăng ()?
GabiMe

1
Với v.nodesgiá trị trên, bạn thực sự không thể nói, vật là gì Name('IOError')hoặc Name('e'). Bạn không biết (các) giá trị đó IOErrorecó thể trỏ đến, vì chúng được gọi là các biến tự do. Ngay cả khi bối cảnh ràng buộc của chúng đã được biết (ở đây các bảng biểu tượng có tác dụng), bạn nên thực hiện một số loại phân tích phụ thuộc dữ liệu để suy ra giá trị chính xác của chúng (điều này sẽ khó trong Python).
Andrey Vlasovskikh 19/10/09

Khi bạn đang tìm kiếm một giải pháp bán tự động thực tế, một danh sách ['IOError(errno.ENOENT, "not found")', 'e']hiển thị cho người dùng là tốt. Nhưng bạn không thể suy luận lớp thực tế của các giá trị của các biến thể hiện bằng chuỗi :) (xin lỗi vì reposting)
Andrey Vlasovskikh

1
Đúng. Phương pháp này, mặc dù thông minh, nhưng không thực sự cung cấp cho bạn một phạm vi toàn diện. Do bản chất năng động của Python, hoàn toàn có thể xảy ra (mặc dù rõ ràng là một ý tưởng tồi) đối với mã bạn đang gọi để làm một cái gì đó giống như vậy exc_class = raw_input(); exec "raise " + exc_class. Vấn đề là loại phân tích tĩnh này không thực sự khả thi trong một ngôn ngữ động như Python.
Daniel Pryden

7
Bằng cách này, bạn có thể chỉ find /path/to/library -name '*.py' | grep 'raise 'để có được kết quả tương tự :)
Andrey Vlasovskikh

24

Bạn chỉ nên bắt các trường hợp ngoại lệ mà bạn sẽ xử lý.

Bắt tất cả các trường hợp ngoại lệ bởi các loại cụ thể của họ là vô nghĩa. Bạn nên nắm bắt các trường hợp ngoại lệ cụ thể mà bạn có thểsẽ xử lý. Đối với các trường hợp ngoại lệ khác, bạn có thể viết một lệnh bắt chung để bắt "ngoại lệ cơ sở", ghi nhật ký nó (sử dụng str()hàm) và kết thúc chương trình của bạn (hoặc làm điều gì đó khác thích hợp trong tình huống gặp sự cố).

Nếu bạn thực sự sẽ xử lý tất cả các ngoại lệ và chắc chắn rằng không có trường hợp nào trong số đó gây tử vong (ví dụ: nếu bạn đang chạy mã trong một số loại môi trường hộp cát), thì cách tiếp cận bắt BaseException chung phù hợp với mục tiêu của bạn.

Bạn cũng có thể quan tâm đến tham chiếu ngoại lệ ngôn ngữ , không phải tham chiếu cho thư viện bạn đang sử dụng.

Nếu tham chiếu thư viện thực sự kém và nó không ném lại các ngoại lệ của riêng mình khi bắt các hệ thống, thì cách tiếp cận hữu ích duy nhất là chạy thử nghiệm (có thể thêm nó vào bộ thử nghiệm, bởi vì nếu thứ gì đó không có tài liệu, nó có thể thay đổi!) . Xóa một tệp quan trọng đối với mã của bạn và kiểm tra xem ngoại lệ nào đang được đưa ra. Cung cấp quá nhiều dữ liệu và kiểm tra xem nó gây ra lỗi gì.

Dù sao thì bạn cũng sẽ phải chạy các bài kiểm tra, vì ngay cả khi phương pháp lấy các ngoại lệ bằng mã nguồn tồn tại, nó sẽ không cung cấp cho bạn bất kỳ ý tưởng nào về cách bạn nên xử lý bất kỳ ngoại lệ nào . Có thể bạn sẽ hiển thị thông báo lỗi "Không tìm thấy tệp needful.txt!" khi bạn bắt IndexError? Chỉ có thử nghiệm mới có thể biết được.


26
Chắc chắn rồi, Nhưng làm thế nào người ta có thể quyết định trường hợp ngoại lệ nào anh ta nên xử lý nếu anh ta không biết những gì có thể bị ném ??
GabiMe

@ bugspy.net, đã sửa câu trả lời của tôi để phản ánh vấn đề này
P Shved

Có lẽ đã đến lúc trình phân tích mã nguồn có thể tìm ra điều này? Không nên quá khó khăn để phát triển tôi nghĩ rằng
GabiMe

@ bugspy.net, tôi đã khuyến khích điều khoản tại sao có thể không phải là lúc dành cho nó.
P Shved

Chắc chắn bạn đúng. Tuy nhiên vẫn có thể thú vị - trong quá trình phát triển - để biết loại ngoại lệ nào có thể xảy ra.
hek2mgl

11

Công cụ chính xác để giải quyết vấn đề này là unittest. Nếu bạn gặp phải các trường hợp ngoại lệ được nâng lên bởi mã thực mà các đơn vị không tăng lên, thì bạn cần thêm các đơn vị khác.

Xem xét điều này

def f(duck):
    try:
        duck.quack()
    except ??? could be anything

vịt có thể là bất kỳ đối tượng nào

Rõ ràng là bạn có thể có một AttributeErrorcon vịt nếu không có lang băm, một TypeErrorcon vịt có lông nhưng nó không thể gọi được. Bạn không biết điều gì duck.quack()có thể tăng lên, thậm chí có thể là một DuckErrorhoặc một cái gì đó

Bây giờ giả sử bạn có mã như thế này

arr[i] = get_something_from_database()

Nếu nó tăng lên, IndexErrorbạn không biết liệu nó đến từ arr [i] hay từ sâu bên trong hàm cơ sở dữ liệu. thường thì ngoại lệ xảy ra ở đâu không quan trọng lắm, đúng hơn là đã xảy ra lỗi và điều bạn muốn xảy ra đã không xảy ra.

Một kỹ thuật hữu ích là nắm bắt và có thể sắp xếp lại ngoại lệ như thế này

except Exception as e
    #inspect e, decide what to do
    raise

Tại sao phải bắt nó ở tất cả nếu bạn định "reraise" nó?
Tarnay Kálmán

Bạn không cần phải đánh giá lại nó, đó là những gì nhận xét được cho là chỉ ra.
John La Rooy

2
Bạn cũng có thể chọn để đăng nhập một nơi nào đó ngoại lệ và sau đó reraise
John La Rooy

2
Tôi không nghĩ rằng bài kiểm tra viết đơn vị là câu trả lời. Câu hỏi là "làm thế nào tôi có thể tìm ra những ngoại lệ nào sẽ xảy ra" và việc viết các bài kiểm tra đơn vị sẽ không giúp bạn tìm ra điều này. Trên thực tế, để viết bài kiểm tra đơn vị, bạn phải biết những ngoại lệ nào có thể xảy ra, vì vậy để viết một bài kiểm tra đơn vị chính xác, bạn cũng phải trả lời câu hỏi ban đầu.
Bruno Ranschaert

6

Cho đến nay vẫn chưa có ai giải thích tại sao bạn không thể có một danh sách đầy đủ, chính xác 100% các trường hợp ngoại lệ, vì vậy tôi nghĩ điều đó đáng để bình luận. Một trong những lý do là một chức năng hạng nhất. Giả sử rằng bạn có một chức năng như thế này:

def apl(f,arg):
   return f(arg)

Bây giờ aplcó thể nêu ra bất kỳ ngoại lệ nào ftăng. Mặc dù không có nhiều chức năng như vậy trong thư viện lõi, nhưng bất kỳ thứ gì sử dụng khả năng hiểu danh sách với các bộ lọc tùy chỉnh, bản đồ, thu nhỏ, v.v. đều bị ảnh hưởng.

Tài liệu và bộ phân tích nguồn là nguồn thông tin "nghiêm túc" duy nhất ở đây. Chỉ cần ghi nhớ những gì họ không thể làm.


4

Tôi gặp phải điều này khi sử dụng socket, tôi muốn tìm hiểu tất cả các điều kiện lỗi mà tôi sẽ gặp phải (vì vậy thay vì cố gắng tạo lỗi và tìm ra socket nào, tôi chỉ muốn có một danh sách ngắn gọn). Cuối cùng, tôi đã kết thúc việc gửi email "/usr/lib64/python2.4/test/test_socket.py" cho "tăng":

$ grep raise test_socket.py
Any exceptions raised by the clients during their tests
        raise TypeError, "test_func must be a callable function"
    raise NotImplementedError, "clientSetUp must be implemented."
    def raise_error(*args, **kwargs):
        raise socket.error
    def raise_herror(*args, **kwargs):
        raise socket.herror
    def raise_gaierror(*args, **kwargs):
        raise socket.gaierror
    self.failUnlessRaises(socket.error, raise_error,
    self.failUnlessRaises(socket.error, raise_herror,
    self.failUnlessRaises(socket.error, raise_gaierror,
        raise socket.error
    # Check that setting it to an invalid value raises ValueError
    # Check that setting it to an invalid type raises TypeError
    def raise_timeout(*args, **kwargs):
    self.failUnlessRaises(socket.timeout, raise_timeout,
    def raise_timeout(*args, **kwargs):
    self.failUnlessRaises(socket.timeout, raise_timeout,

Đó là một danh sách các lỗi khá ngắn gọn. Tất nhiên bây giờ điều này chỉ hoạt động trên cơ sở từng trường hợp và phụ thuộc vào các thử nghiệm có chính xác (mà chúng thường là). Nếu không, bạn cần phải nắm bắt khá nhiều trường hợp ngoại lệ, ghi nhật ký và mổ xẻ chúng và tìm ra cách xử lý chúng (điều này với kiểm thử đơn vị sẽ không khó).


4
Điều này củng cố lập luận của tôi, rằng xử lý ngoại lệ trong Python rất có vấn đề, nếu chúng ta cần sử dụng trình phân tích mã nguồn hoặc grep để xử lý một thứ quá cơ bản (ví dụ như trong java đã tồn tại từ ngày đầu tiên. Đôi khi chi tiết là một điều tốt. Java là dài dòng nhưng ít nhất không có bất ngờ khó chịu)
GabiMe

@GabiMe, Nó không giống như khả năng này (hay nói chung là gõ tĩnh) là một viên đạn bạc để ngăn chặn tất cả các lỗi. Java đầy rẫy những bất ngờ khó chịu. Đó là lý do tại sao nhật thực thường xuyên xảy ra sự cố.
John La Rooy

2

Có hai cách mà tôi thấy có nhiều thông tin. Đầu tiên, chạy mã trong iPython, mã này sẽ hiển thị loại ngoại lệ.

n = 2
str = 'me '
str + 2
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Theo cách thứ hai, chúng tôi giải quyết việc đánh bắt quá nhiều và cải thiện nó theo thời gian. Bao gồm một trybiểu thức trong mã của bạn và bắt except Exception as err. In đủ dữ liệu để biết ngoại lệ nào đã được đưa ra. Khi các ngoại lệ được ném ra, hãy cải thiện mã của bạn bằng cách thêm một exceptmệnh đề chính xác hơn . Khi bạn cảm thấy rằng bạn đã nắm bắt được tất cả các trường hợp ngoại lệ có liên quan, hãy xóa trường hợp bao gồm tất cả. Một điều tốt nên làm vì nó nuốt chửng các lỗi lập trình.

try:
   so something
except Exception as err:
   print "Some message"
   print err.__class__
   print err
   exit(1)

1

thông thường, bạn chỉ cần bắt ngoại lệ xung quanh một vài dòng mã. Bạn sẽ không muốn đặt toàn bộ mainchức năng của mình vào try exceptmệnh đề. đối với mỗi vài dòng, bây giờ bạn luôn nên (hoặc có thể dễ dàng kiểm tra) loại ngoại lệ nào có thể được nêu ra.

tài liệu có một danh sách đầy đủ các ngoại lệ được tích hợp sẵn . đừng cố gắng ngoại trừ những ngoại lệ mà bạn không mong đợi, chúng có thể được xử lý / mong đợi trong mã gọi điện.

chỉnh sửa : những gì có thể được ném phụ thuộc rõ ràng vào những gì bạn đang làm! truy cập phần tử ngẫu nhiên của một chuỗi : IndexError, phần tử ngẫu nhiên của một dict:, KeyErrorv.v.

Chỉ cần cố gắng chạy vài dòng đó trong IDLE và gây ra một ngoại lệ. Nhưng tự nhiên, unittest sẽ là một giải pháp tốt hơn.


1
Điều này không trả lời câu hỏi đơn giản của tôi. Tôi không hỏi về cách thiết kế xử lý ngoại lệ của mình, hoặc khi nào hoặc cách bắt. Tôi hỏi làm thế nào để tìm hiểu những gì có thể được ném
GabiMe

1
@ bugspy.net: Không thể thực hiện những gì bạn yêu cầu và đây là một giải pháp hoàn toàn hợp lệ.
Daniel Pryden
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.