Bagaimana cara saya menangkap peringatan numpy seperti pengecualian (tidak hanya untuk pengujian)?

174

Saya harus membuat polinomial Lagrange dengan Python untuk proyek yang saya lakukan. Saya sedang melakukan gaya barycentric untuk menghindari menggunakan for-loop eksplisit sebagai lawan dari gaya perbedaan perbedaan Newton. Masalah yang saya miliki adalah bahwa saya perlu menangkap pembagian dengan nol, tetapi Python (atau mungkin numpy) hanya menjadikannya peringatan, bukan pengecualian normal.

Jadi, apa yang perlu saya ketahui bagaimana melakukannya adalah menangkap peringatan ini seolah-olah itu pengecualian. Pertanyaan terkait dengan ini saya temukan di situs ini dijawab tidak seperti yang saya butuhkan. Ini kode saya:

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. 

Ketika kode ini dieksekusi, output yang saya dapatkan adalah:

Warning: divide by zero encountered in int_scalars

Itu peringatan yang ingin saya tangkap. Itu harus terjadi di dalam daftar pemahaman.

John K.
sumber
2
Apakah Anda yakin itu Warning: ...? Mencoba hal-hal seperti yang np.array([1])/0saya dapatkan RuntimeWarning: ...sebagai output.
Bakuriu
1
@MadPhysicist Bukan duplikat; NumPy memiliki arsitektur peringatan internal sendiri di atas Python, yang dapat dikontrol secara khusus (lihat jawaban oleh Bakuríu).
gerrit
@gerrit. Saya berdiri dikoreksi dan belajar hal baru. Saya menghapus komentar asli saya untuk menghindari memicu kegilaan koleksi lencana.
Fisikawan Gila
Pendekatan lain yang bisa Anda gunakan adalah dengan hanya memeriksa apakah penyebutnya 0 sebelum pembagian, yang menghindari overhead mengutak-atik sistem peringatan numpy. (Meskipun ini mungkin berarti Anda harus memperluas pemahaman daftar yang rapi ke dalam satu lingkaran yang memeriksa apakah ada penyebutnya nol.)
Oliver

Jawaban:

198

Tampaknya konfigurasi Anda menggunakan printopsi untuk 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])

Ini berarti bahwa peringatan yang Anda lihat bukan peringatan yang sebenarnya, tetapi itu hanya beberapa karakter yang dicetak stdout(lihat dokumentasi untuk seterr). Jika Anda ingin menangkapnya, Anda dapat:

  1. Gunakan numpy.seterr(all='raise')yang secara langsung akan meningkatkan pengecualian. Namun ini mengubah perilaku semua operasi, jadi itu adalah perubahan perilaku yang cukup besar.
  2. Gunakan numpy.seterr(all='warn'), yang akan mengubah peringatan tercetak menjadi peringatan nyata dan Anda akan dapat menggunakan solusi di atas untuk melokalisasi perubahan perilaku ini.

Setelah Anda benar-benar memiliki peringatan, Anda dapat menggunakan warningsmodul untuk mengontrol bagaimana peringatan harus diperlakukan:

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

Baca dengan seksama dokumentasi filterwarningskarena memungkinkan Anda untuk memfilter hanya peringatan yang Anda inginkan dan memiliki opsi lain. Saya juga akan mempertimbangkan untuk melihat catch_warningsmana yang merupakan manajer konteks yang secara otomatis me-reset filterwarningsfungsi asli :

>>> 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: 
Bakuriu
sumber
Saya pikir ini awal. Tapi itu sebenarnya tidak memperbaiki masalah saya. Jika saya menambahkan warnings.warn (Peringatan ())) dalam kode saya di blok coba, itu akan menangkap peringatan. Untuk beberapa alasan itu tidak menangkap kesenjangan dengan peringatan nol. Inilah pesan peringatan yang tepat: Peringatan: bagi dengan nol dijumpai di int_scalars
John K.
@ JohnK. Anda harus mengedit pertanyaan Anda dan menambahkan output yang tepat, jika tidak kami tidak bisa mengatakan apa yang salah. Ini mungkin menjadi mungkin bahwa numpy mendefinisikan peringatan ini kelas di suatu tempat dan Anda harus Discovere di mana sub-paket untuk dapat menangkapnya. Sudahlah, saya menemukan bahwa Anda harus menggunakan RuntimeWarning. Diperbarui jawabannya.
Bakuriu
Apakah kamu yakin Saya mengubah kode saya untuk digunakan kecuali RuntimeWarning :. Masih tidak berfungsi = /
John K.
@ JohnK. Dalam dokumentasi itu disebutkan bahwa a RuntimeWarningdinaikkan. Masalahnya mungkin bahwa konfigurasi numpy Anda menggunakan printopsi, yang hanya mencetak peringatan tetapi itu bukan peringatan nyata yang ditangani oleh warningsmodul ... Jika ini adalah kasus Anda dapat mencoba menggunakan numpy.seterr(all='warn')dan coba lagi.
Bakuriu
3
Dalam versi saya numpy, Anda tidak dapat menggunakan numpy.seterr(all='error'), errorharus raise.
detly
41

Untuk menambahkan sedikit pada jawaban @ Bakuriu:

Jika Anda sudah tahu di mana peringatan itu mungkin terjadi maka sering kali lebih baik menggunakan numpy.errstatemanajer konteks, daripada numpy.seterryang memperlakukan semua peringatan berikutnya dari jenis yang sama sama terlepas di mana mereka muncul dalam kode Anda:

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

Edit:

Dalam contoh asli saya a = np.r_[0], tetapi ternyata ada perubahan dalam perilaku numpy sehingga pembagian-by-nol ditangani secara berbeda dalam kasus di mana pembilangnya adalah semua-nol. Misalnya, dalam 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

Pesan peringatan yang sesuai juga berbeda: 1. / 0.dicatat sebagai RuntimeWarning: divide by zero encountered in true_divide, sedangkan 0. / 0.dicatat sebagai RuntimeWarning: invalid value encountered in true_divide. Saya tidak yakin mengapa tepatnya perubahan ini dibuat, tetapi saya menduga itu ada hubungannya dengan fakta bahwa hasil 0. / 0.tidak dapat diwakili sebagai angka (numpy mengembalikan NaN dalam kasus ini) sedangkan 1. / 0.dan -1. / 0.return + Inf dan -Inf masing-masing , sesuai standar IEE 754.

Jika Anda ingin menangkap kedua jenis kesalahan, Anda selalu dapat lulus np.errstate(divide='raise', invalid='raise'), atau all='raise'jika Anda ingin meningkatkan pengecualian pada segala jenis kesalahan floating point.

ali_m
sumber
Khususnya itu memunculkan FloatingPointError, bukan ZeroDivisionError.
gerrit
Ini tidak bekerja Python 3.6.3dengan numpy==1.16.3. Bisakah Anda memperbaruinya?
anilbey
1
@ anilbey Rupanya ada perubahan dalam perilaku numpy yang berarti pembagian demi nol sekarang ditangani secara berbeda tergantung pada apakah pembilangnya juga (semua) nol.
ali_m
27

Untuk menguraikan jawaban @ Bakuriu di atas, saya menemukan bahwa ini memungkinkan saya menangkap peringatan runtime dengan cara yang mirip dengan cara saya menangkap peringatan kesalahan, mencetak peringatan dengan baik:

import warnings

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

Anda mungkin dapat bermain-main dengan penempatan penempatan peringatan.catch_warnings () tergantung pada seberapa besar payung yang ingin Anda buat dengan kesalahan menangkap dengan cara ini.

ntk4
sumber
3
answer =
1/0
8

Hapus warnings.filterwarnings dan tambahkan:

numpy.seterr(all='raise')
Shital Shah
sumber