ฉันจะจับคำเตือน numpy ได้อย่างไรว่าเป็นข้อยกเว้น (ไม่ใช่แค่การทดสอบ)


173

ฉันต้องสร้างพหุนาม Lagrange ใน Python สำหรับโครงการที่ฉันทำ ฉันกำลังทำรูปแบบบาริเซนทริกเพื่อหลีกเลี่ยงการใช้ลูปแบบชัด ๆ ซึ่งตรงข้ามกับสไตล์ที่แตกต่างของนิวตัน ปัญหาที่ฉันมีคือฉันต้องจับแบ่งเป็นศูนย์ แต่ Python (หรืออาจจะเป็นก้อน) เพียงแค่ทำให้มันเป็นคำเตือนแทนข้อยกเว้นปกติ

ดังนั้นสิ่งที่ฉันต้องรู้วิธีการทำคือการจับคำเตือนนี้ราวกับว่ามันเป็นข้อยกเว้น คำถามที่เกี่ยวข้องกับสิ่งที่ฉันพบในเว็บไซต์นี้ไม่ได้ตอบในวิธีที่ฉันต้องการ นี่คือรหัสของฉัน:

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. 

เมื่อรันโค้ดนี้เอาต์พุตที่ฉันได้รับคือ:

Warning: divide by zero encountered in int_scalars

นั่นคือคำเตือนที่ฉันต้องการจับ มันควรจะเกิดขึ้นภายในรายการความเข้าใจ


2
คุณแน่ใจWarning: ...หรือไม่ พยายามทำสิ่งต่าง ๆ เช่นnp.array([1])/0รับRuntimeWarning: ...เอาท์พุท
Bakuriu

1
@MadPhysicist ไม่ซ้ำกัน; NumPy มีสถาปัตยกรรมการเตือนภายในของตัวเองบน Pythons ซึ่งสามารถควบคุมได้เป็นพิเศษ (ดูคำตอบจากBakuríu)
gerrit

@gerrit ฉันยืนแก้ไขและเรียนรู้สิ่งใหม่ ฉันลบความคิดเห็นดั้งเดิมของฉันเพื่อหลีกเลี่ยงการเรียกคอลเลกชันความบ้าคลั่งป้าย
นักฟิสิกส์บ้า

อีกวิธีที่คุณสามารถใช้คือการตรวจสอบว่าตัวส่วนเป็น 0 ก่อนการหารซึ่งจะช่วยหลีกเลี่ยงค่าใช้จ่ายในการเล่นซอกับระบบเตือนของนัมมี่ (แม้ว่านี้อาจจะหมายความว่าคุณต้องขยายความเข้าใจในรายชื่อเรียบร้อยเป็นห่วงการตรวจสอบถ้ามีตัวหารเป็นศูนย์.)
โอลิเวอร์

คำตอบ:


197

ดูเหมือนว่าการกำหนดค่าของคุณใช้printตัวเลือกสำหรับ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])

ซึ่งหมายความว่าคำเตือนที่คุณเห็นไม่ใช่คำเตือนที่แท้จริง แต่เป็นเพียงตัวอักษรบางตัวที่พิมพ์ไปstdout(ดูเอกสารประกอบสำหรับseterr) หากคุณต้องการจับคุณสามารถ:

  1. ใช้numpy.seterr(all='raise')ซึ่งจะเพิ่มข้อยกเว้นโดยตรง อย่างไรก็ตามนี่เป็นการเปลี่ยนแปลงพฤติกรรมของการดำเนินการทั้งหมดดังนั้นจึงเป็นการเปลี่ยนแปลงครั้งใหญ่ในพฤติกรรม
  2. ใช้numpy.seterr(all='warn')ซึ่งจะเปลี่ยนคำเตือนที่พิมพ์ออกมาในการเตือนจริงและคุณจะสามารถใช้วิธีการแก้ปัญหาข้างต้นเพื่อ จำกัด การเปลี่ยนแปลงพฤติกรรมนี้

เมื่อคุณมีคำเตือนจริงแล้วคุณสามารถใช้warningsโมดูลเพื่อควบคุมวิธีการปฏิบัติต่อคำเตือน:

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

อ่านเอกสารอย่างละเอียดfilterwarningsเนื่องจากช่วยให้คุณกรองเฉพาะคำเตือนที่คุณต้องการและมีตัวเลือกอื่น ๆ ฉันยังลองพิจารณาดูcatch_warningsว่าตัวจัดการบริบทตัวใดที่รีเซ็ตfilterwarningsฟังก์ชันต้นฉบับโดยอัตโนมัติ:

>>> 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: 

ฉันคิดว่านี่เป็นการเริ่มต้น แต่มันก็ไม่ได้แก้ปัญหาของฉัน ถ้าฉันเพิ่ม warnings.warn (คำเตือน ())) ในรหัสของฉันในบล็อกลองมันจะจับคำเตือน ด้วยเหตุผลบางอย่างมันไม่จับการหารด้วยคำเตือนเป็นศูนย์ นี่คือข้อความเตือนที่แน่นอน: คำเตือน: หารด้วยศูนย์ที่พบใน int_scalars
John K.

@JohnK คุณควรแก้ไขคำถามของคุณและเพิ่มผลลัพธ์ที่แน่นอนมิฉะนั้นเราไม่สามารถบอกได้ว่ามีอะไรผิดปกติ มันอาจเป็นไปได้ว่า numpy กำหนดนี้ที่ไหนสักแห่งระดับเตือนภัยและคุณจะต้อง discovere ซึ่งแพคเกจย่อยเพื่อให้สามารถจับมัน RuntimeWarningไม่เคยคิดผมค้นพบว่าคุณควรใช้ อัปเดตคำตอบ
Bakuriu

คุณแน่ใจไหม? ฉันเปลี่ยนรหัสเพื่อใช้ยกเว้น RuntimeWarning: มันยังไม่ทำงาน = /
John K.

@JohnK ในเอกสารอธิบายว่ามีการRuntimeWarningยก ปัญหาอาจเกิดจากการกำหนดค่า numpy ของคุณกำลังใช้printตัวเลือกซึ่งพิมพ์คำเตือน แต่ไม่ใช่คำเตือนจริงที่จัดการโดยwarningsโมดูล ... หากเป็นกรณีนี้คุณสามารถลองใช้numpy.seterr(all='warn')และลองอีกครั้ง
Bakuriu

3
ในรุ่นของฉันnumpyคุณจะไม่สามารถใช้งานnumpy.seterr(all='error'), ความต้องการที่จะerror raise
detly

41

วิธีเพิ่มคำตอบของ @ Bakuriu เล็กน้อย:

หากคุณทราบอยู่แล้วว่าคำเตือนมีแนวโน้มที่จะเกิดขึ้นที่ไหนบ่อยครั้งคุณควรใช้ตัวnumpy.errstateจัดการบริบทแทนที่จะจัดการ numpy.seterrกับคำเตือนประเภทถัดไปทั้งหมดซึ่งเหมือนกันไม่ว่าจะเกิดขึ้นที่ใดในรหัสของคุณ:

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

แก้ไข:

ในตัวอย่างดั้งเดิมของฉันฉันมีa = np.r_[0]แต่เห็นได้ชัดว่ามีการเปลี่ยนแปลงในพฤติกรรมของ 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

ข้อความเตือนที่สอดคล้องกันนอกจากนี้ยังมีที่แตกต่างกัน1. / 0.จะถูกบันทึกเป็นRuntimeWarning: divide by zero encountered in true_divideในขณะที่มีการบันทึกไว้เป็น0. / 0. RuntimeWarning: invalid value encountered in true_divideฉันไม่แน่ใจว่าทำไมการเปลี่ยนแปลงนี้จึงเกิดขึ้น แต่ฉันสงสัยว่ามันเกี่ยวข้องกับความจริงที่ว่าผลลัพธ์ของการ0. / 0.ไม่สามารถแทนได้เป็นตัวเลข (จำนวน numpy ส่งกลับ NaN ในกรณีนี้) ในขณะที่1. / 0.และ-1. / 0.กลับ + Inf และ -Inf ตามลำดับ ตามมาตรฐาน IEE 754

หากคุณต้องการจับข้อผิดพลาดทั้งสองประเภทคุณสามารถส่งผ่านได้ตลอดเวลาnp.errstate(divide='raise', invalid='raise')หรือall='raise'หากคุณต้องการเพิ่มข้อยกเว้นสำหรับข้อผิดพลาดจุดลอยตัวชนิดใด ๆ


โดยเฉพาะอย่างยิ่งมันจะเพิ่มไม่ได้FloatingPointError ZeroDivisionError
gerrit

นี้ไม่ได้ทำงานบนด้วยPython 3.6.3 numpy==1.16.3คุณช่วยอัพเดทได้ไหม
anilbey

1
@anilbey เห็นได้ชัดว่ามีการเปลี่ยนแปลงพฤติกรรมของ numpy นั่นหมายความว่าตอนนี้การจัดการโดยศูนย์ถูกจัดการแตกต่างกันไปขึ้นอยู่กับว่าตัวเศษยังเป็นศูนย์ (ทั้งหมด)
ali_m

27

ในการอธิบายอย่างละเอียดเกี่ยวกับคำตอบของ @ Bakuriu ข้างต้นฉันพบว่าสิ่งนี้ทำให้ฉันได้รับคำเตือนรันไทม์ในลักษณะเดียวกันกับวิธีที่ฉันจะจับคำเตือนข้อผิดพลาดโดยพิมพ์คำเตือนออกมาอย่างดี:

import warnings

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

คุณอาจสามารถเล่นด้วยการวางคำเตือนตำแหน่ง catch_warnings () ขึ้นอยู่กับว่าคุณต้องการโยนร่มขนาดใหญ่ด้วยการจับข้อผิดพลาดด้วยวิธีนี้


3
answer =
1/0

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.