Come posso ricevere un avviso intorpidito come se fosse un'eccezione (non solo per i test)?


174

Devo realizzare un polinomio di Lagrange in Python per un progetto che sto realizzando. Sto facendo uno stile baricentrico per evitare di usare un esplicito for-loop invece di uno stile di differenza diviso di Newton. Il problema che ho è che devo catturare una divisione per zero, ma Python (o forse intorpidito) lo rende solo un avvertimento anziché una normale eccezione.

Quindi, quello che devo sapere come fare è prendere questo avviso come se fosse un'eccezione. Alle domande correlate a questo che ho trovato su questo sito non è stata data la risposta di cui avevo bisogno. Ecco il mio codice:

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. 

Quando viene eseguito questo codice, l'output che ottengo è:

Warning: divide by zero encountered in int_scalars

Questo è l'avvertimento che voglio catturare. Dovrebbe verificarsi all'interno della comprensione dell'elenco.


2
Ne sei abbastanza sicuro Warning: ...? Provando cose come np.array([1])/0ottengo RuntimeWarning: ...come output.
Bakuriu,

1
@MadPhysicist Non è un duplicato; NumPy ha una propria architettura di avviso interna su Pythons, che può essere controllata in modo specifico (vedi risposta di Bakuríu).
Gerrit,

@gerrit. Sono corretto e ho imparato una cosa nuova. Ho eliminato il mio commento originale per evitare di scatenare la frenesia della raccolta dei badge.
Fisico pazzo,

Un altro approccio che potresti usare è semplicemente controllare se il denominatore è 0 prima della divisione, il che evita il sovraccarico di armeggiare con il sistema di allarme di Numpy. (Anche se questo significherebbe probabilmente che devi espandere la comprensione della lista ordinata in un ciclo verificando se qualcuno dei denominatori è zero.)
Oliver

Risposte:


198

Sembra che la tua configurazione stia usando l' printopzione per 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])

Ciò significa che l'avviso che vedi non è un vero avvertimento, ma sono solo alcuni caratteri stampati stdout(vedi la documentazione per seterr). Se vuoi prenderlo puoi:

  1. Usa numpy.seterr(all='raise')che solleverà direttamente l'eccezione. Ciò tuttavia modifica il comportamento di tutte le operazioni, quindi è un cambiamento piuttosto grande nel comportamento.
  2. Usa numpy.seterr(all='warn'), che trasformerà l'avviso stampato in un avvertimento reale e sarai in grado di utilizzare la soluzione di cui sopra per localizzare questo cambiamento nel comportamento.

Dopo aver effettivamente ricevuto un avviso, è possibile utilizzare il warningsmodulo per controllare come devono essere trattati gli avvisi:

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

Leggere attentamente la documentazione per filterwarningspoiché consente di filtrare solo l'avviso desiderato e ha altre opzioni. Vorrei anche considerare catch_warningsquale è un gestore di contesto che reimposta automaticamente la filterwarningsfunzione originale :

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

Penso che questo sia un inizio. Ma in realtà non risolve il mio problema. Se aggiungo warnings.warn (Warning ())) nel mio codice nel blocco try, verrà catturato l'avviso. Per qualche motivo non rileva la divisione per avviso zero. Ecco il messaggio di avvertimento esatto: Avviso: dividere per zero riscontrato in int_scalars
John K.

@JohnK. Dovresti modificare la tua domanda e aggiungere l'output esatto, altrimenti non possiamo dire cosa c'è che non va. Si potrebbe essere possibile che NumPy definisce questo avvertimento di classe da qualche parte e si deve Discovere in cui subpackage per essere in grado di prenderlo. Non importa, ho scoperto che dovresti usare RuntimeWarning. Aggiornato la risposta.
Bakuriu,

Sei sicuro? Ho cambiato il mio codice per usare tranne RuntimeWarning :. Non funziona ancora = /
John K.

@JohnK. Nella documentazione si afferma che a RuntimeWarningè sollevato. Il problema potrebbe essere che la tua configurazione intorpidita sta usando l' printopzione, che stampa semplicemente l'avviso ma non è un vero avvertimento gestito dal warningsmodulo ... In questo caso potresti provare a utilizzare numpy.seterr(all='warn')e riprovare.
Bakuriu,

3
Nella mia versione di numpy, non puoi usare numpy.seterr(all='error'), errordeve esserlo raise.
detly

41

Per aggiungere un po 'alla risposta di @ Bakuriu:

Se sai già dove è probabile che si verifichi l'avviso, è spesso più pulito utilizzare il numpy.errstateGestore di contesto, piuttosto numpy.seterrche trattare tutti gli avvisi successivi dello stesso tipo allo stesso modo indipendentemente da dove si verificano nel codice:

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

Modificare:

Nel mio esempio originale ho avuto a = np.r_[0], ma apparentemente c'è stato un cambiamento nel comportamento del numpy tale che la divisione per zero è gestita in modo diverso nei casi in cui il numeratore è tutto zero. Ad esempio, in 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

Anche i corrispondenti messaggi di avviso sono diversi: 1. / 0.è registrato come RuntimeWarning: divide by zero encountered in true_divide, mentre 0. / 0.è registrato come RuntimeWarning: invalid value encountered in true_divide. Non sono sicuro del perché esattamente questa modifica sia stata apportata, ma sospetto che abbia a che fare con il fatto che il risultato di0. / 0. non è rappresentabile come un numero (numpy restituisce un NaN in questo caso) mentre 1. / 0.e-1. / 0. restituiscono + Inf e -Inf rispettivamente , secondo lo standard IEE 754.

Se si desidera rilevare entrambi i tipi di errore, è sempre possibile passare np.errstate(divide='raise', invalid='raise')o all='raise'se si desidera sollevare un'eccezione su qualsiasi tipo di errore in virgola mobile.


In particolare solleva FloatingPointError, no ZeroDivisionError.
Gerrit,

Questo non funziona Python 3.6.3con numpy==1.16.3. Potresti aggiornarlo per favore?
anilbey,

1
@anilbey Apparentemente c'è stato un cambiamento nel comportamento del numpy che significa che la divisione per zero è ora gestita in modo diverso a seconda che anche il numeratore sia (tutto) zero.
ali_m,

27

Per approfondire la risposta di @ Bakuriu sopra, ho scoperto che ciò mi consente di rilevare un avviso di runtime in modo simile a come riceverei un avviso di errore, stampando l'avviso in modo corretto:

import warnings

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

Probabilmente sarai in grado di giocare con il posizionamento del posizionamento warnings.catch_warnings () a seconda di quanto grande di un ombrello vuoi lanciare con errori di cattura in questo modo.


3
answer =
1/0

8

Rimuovere warnings.filterwarnings e aggiungere:

numpy.seterr(all='raise')
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.