Làm thế nào để tôi bắt được một cảnh báo khó chịu như đó là một ngoại lệ (không chỉ để thử nghiệm)?


172

Tôi phải tạo một đa thức Lagrange trong Python cho một dự án tôi đang thực hiện. Tôi đang thực hiện kiểu một kiểu nhị phân để tránh sử dụng vòng lặp for rõ ràng trái ngược với kiểu khác biệt được chia của Newton. Vấn đề tôi gặp phải là tôi cần bắt một phép chia bằng 0, nhưng Python (hoặc có thể là numpy) chỉ làm cho nó trở thành một cảnh báo thay vì một ngoại lệ bình thường.

Vì vậy, những gì tôi cần biết làm thế nào là bắt được cảnh báo này như thể đó là một ngoại lệ. Các câu hỏi liên quan đến điều này tôi tìm thấy trên trang web này đã được trả lời không theo cách tôi cần. Đây là mã của tôi:

import numpy as np
import matplotlib.pyplot as plt
import warnings

class Lagrange:
    def __init__(self, xPts, yPts):
        self.xPts = np.array(xPts)
        self.yPts = np.array(yPts)
        self.degree = len(xPts)-1 
        self.weights = np.array([np.product([x_j - x_i for x_j in xPts if x_j != x_i]) for x_i in xPts])

    def __call__(self, x):
        warnings.filterwarnings("error")
        try:
            bigNumerator = np.product(x - self.xPts)
            numerators = np.array([bigNumerator/(x - x_j) for x_j in self.xPts])
            return sum(numerators/self.weights*self.yPts) 
        except Exception, e: # Catch division by 0. Only possible in 'numerators' array
            return yPts[np.where(xPts == x)[0][0]]

L = Lagrange([-1,0,1],[1,0,1]) # Creates quadratic poly L(x) = x^2

L(1) # This should catch an error, then return 1. 

Khi mã này được thực thi, đầu ra tôi nhận được là:

Warning: divide by zero encountered in int_scalars

Đó là lời cảnh báo tôi muốn nắm bắt. Nó nên xảy ra trong danh sách hiểu.


2
Bạn có chắc chắn Warning: ...không? Thử những thứ như np.array([1])/0tôi nhận được RuntimeWarning: ...như đầu ra.
Bakuriu

1
@MadPhysicist Không trùng lặp; NumPy có kiến ​​trúc cảnh báo nội bộ của riêng mình trên đỉnh Pythons, có thể được kiểm soát cụ thể (xem câu trả lời của Bakuríu).
gerrit

@gerrit. Tôi đứng sửa và học một điều mới. Tôi đã xóa bình luận ban đầu của tôi để tránh kích hoạt bộ sưu tập huy hiệu điên cuồng.
Nhà vật lý điên

Một cách tiếp cận khác mà bạn có thể sử dụng là chỉ cần kiểm tra xem mẫu số có phải là 0 trước khi phân chia hay không, điều này tránh được sự quá tải của hệ thống cảnh báo của numpy. (Mặc dù điều này có thể có nghĩa là bạn phải mở rộng việc hiểu danh sách gọn gàng thành kiểm tra vòng lặp nếu có bất kỳ mẫu số nào bằng không.)
Oliver

Câu trả lời:


196

Có vẻ như cấu hình của bạn đang sử dụng printtùy chọn cho numpy.seterr:

>>> import numpy as np
>>> np.array([1])/0   #'warn' mode
__main__:1: RuntimeWarning: divide by zero encountered in divide
array([0])
>>> np.seterr(all='print')
{'over': 'warn', 'divide': 'warn', 'invalid': 'warn', 'under': 'ignore'}
>>> np.array([1])/0   #'print' mode
Warning: divide by zero encountered in divide
array([0])

Điều này có nghĩa là cảnh báo bạn thấy không phải là cảnh báo thực sự, nhưng đó chỉ là một số ký tự được in stdout(xem tài liệu hướng dẫn seterr). Nếu bạn muốn bắt nó, bạn có thể:

  1. Sử dụng numpy.seterr(all='raise')sẽ trực tiếp nâng cao ngoại lệ. Tuy nhiên, điều này thay đổi hành vi của tất cả các hoạt động, vì vậy đó là một thay đổi khá lớn trong hành vi.
  2. Sử dụng numpy.seterr(all='warn'), sẽ chuyển đổi cảnh báo được in trong một cảnh báo thực và bạn sẽ có thể sử dụng giải pháp trên để bản địa hóa sự thay đổi này trong hành vi.

Khi bạn thực sự có cảnh báo, bạn có thể sử dụng warningsmô-đun để kiểm soát cách xử lý các cảnh báo:

>>> import warnings
>>> 
>>> warnings.filterwarnings('error')
>>> 
>>> try:
...     warnings.warn(Warning())
... except Warning:
...     print 'Warning was raised as an exception!'
... 
Warning was raised as an exception!

Đọc kỹ tài liệu filterwarningsvì nó cho phép bạn chỉ lọc cảnh báo bạn muốn và có các tùy chọn khác. Tôi cũng sẽ xem xét xem trình catch_warningsquản lý bối cảnh nào sẽ tự động đặt lại filterwarningschức năng ban đầu :

>>> import warnings
>>> with warnings.catch_warnings():
...     warnings.filterwarnings('error')
...     try:
...         warnings.warn(Warning())
...     except Warning: print 'Raised!'
... 
Raised!
>>> try:
...     warnings.warn(Warning())
... except Warning: print 'Not raised!'
... 
__main__:2: Warning: 

Tôi nghĩ rằng đây là một sự khởi đầu. Nhưng nó không thực sự khắc phục vấn đề của tôi. Nếu tôi thêm warnings.warn (Cảnh báo ())) vào mã của mình trong khối thử, nó sẽ bắt được cảnh báo. Vì một số lý do, nó không bắt được sự phân chia bằng cảnh báo bằng không. Đây là thông điệp cảnh báo chính xác: Cảnh báo: chia cho số 0 gặp phải trong int_scalars
John K.

@ John. Bạn nên chỉnh sửa câu hỏi của mình và thêm đầu ra chính xác, nếu không chúng tôi không thể nói sai. Có thể là numpy định nghĩa lớp cảnh báo này ở đâu đó và bạn phải khám phá xem gói con nào có thể bắt được nó. Không sao, tôi phát hiện ra rằng bạn nên sử dụng RuntimeWarning. Cập nhật câu trả lời.
Bakuriu

Bạn có chắc không? Tôi đã thay đổi mã của mình để sử dụng ngoại trừ RuntimeWarning :. Nó vẫn không hoạt động = /
John K.

@ John. Trong tài liệu có ghi rằng a RuntimeWarningđược nêu ra. Vấn đề có thể là cấu hình numpy của bạn đang sử dụng printtùy chọn, chỉ đơn giản là in cảnh báo nhưng đó không phải là cảnh báo thực sự được xử lý bởi warningsmô-đun ... Nếu đây là trường hợp bạn có thể thử sử dụng numpy.seterr(all='warn')và thử lại.
Bakuriu

3
Trong phiên bản của tôi numpy, bạn không thể sử dụng numpy.seterr(all='error'), errorcần phải có raise.
gièm pha

41

Để thêm một chút vào câu trả lời của @ Bakuriu:

Nếu bạn đã biết cảnh báo có khả năng xảy ra ở đâu thì việc sử dụng trình numpy.errstatequản lý bối cảnh thường sẽ sạch hơn , thay vì numpy.seterrxử lý tất cả các cảnh báo tiếp theo cùng loại bất kể chúng xảy ra ở đâu trong mã của bạn:

import numpy as np

a = np.r_[1.]
with np.errstate(divide='raise'):
    try:
        a / 0   # this gets caught and handled as an exception
    except FloatingPointError:
        print('oh no!')
a / 0           # this prints a RuntimeWarning as usual

Biên tập:

Trong ví dụ ban đầu của tôi, tôi đã có a = np.r_[0], nhưng rõ ràng có một sự thay đổi trong hành vi của numpy sao cho phép chia bằng 0 được xử lý khác nhau trong trường hợp tử số là số không. Ví dụ: trong numpy 1.16.4:

all_zeros = np.array([0., 0.])
not_all_zeros = np.array([1., 0.])

with np.errstate(divide='raise'):
    not_all_zeros / 0.  # Raises FloatingPointError

with np.errstate(divide='raise'):
    all_zeros / 0.  # No exception raised

with np.errstate(invalid='raise'):
    all_zeros / 0.  # Raises FloatingPointError

Các thông điệp cảnh báo tương ứng cũng khác nhau: 1. / 0.được ghi là RuntimeWarning: divide by zero encountered in true_divide, trong khi 0. / 0.được ghi là RuntimeWarning: invalid value encountered in true_divide. Tôi không chắc tại sao chính xác thay đổi này được thực hiện, nhưng tôi nghi ngờ nó phải làm với thực tế là kết quả của 0. / 0.nó không thể biểu thị được như một số (numpy trả về NaN trong trường hợp này) trong khi đó 1. / 0.-1. / 0.trả lại + Inf và -Inf tương ứng , theo tiêu chuẩn IEE 754.

Nếu bạn muốn bắt cả hai loại lỗi, bạn luôn có thể vượt qua np.errstate(divide='raise', invalid='raise')hoặc all='raise'nếu bạn muốn đưa ra một ngoại lệ đối với bất kỳ loại lỗi dấu phẩy động nào.


Đáng chú ý là nó tăng FloatingPointError, không ZeroDivisionError.
gerrit

Điều này không làm việc Python 3.6.3với numpy==1.16.3. Bạn có thể cập nhật nó được không?
anilbey

1
@anilbey Rõ ràng đã có một sự thay đổi trong hành vi của numpy, điều đó có nghĩa là chia cho 0 bây giờ được xử lý khác nhau tùy thuộc vào việc tử số cũng (tất cả) bằng không.
ali_m

27

Để giải thích câu trả lời của @ Bakuriu ở trên, tôi thấy rằng điều này cho phép tôi bắt được cảnh báo thời gian chạy theo cách tương tự như cách tôi sẽ bắt được cảnh báo lỗi, in ra cảnh báo độc đáo:

import warnings

with warnings.catch_warnings():
    warnings.filterwarnings('error')
    try:
        answer = 1 / 0
    except Warning as e:
        print('error found:', e)

Bạn có thể sẽ có thể chơi xung quanh với việc đặt vị trí của warnings.catch_warnings () tùy thuộc vào độ lớn của chiếc ô mà bạn muốn sử dụng khi bắt lỗi theo cách này.


3
answer =
1/0

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.