Como capto um aviso entorpecido como se fosse uma exceção (não apenas para testes)?

174

Eu tenho que fazer um polinômio Lagrange em Python para um projeto que estou fazendo. Eu estou fazendo um estilo baricêntrico para evitar o uso de um loop for explícito, em oposição ao estilo de diferença dividida de Newton. O problema que tenho é que preciso capturar uma divisão por zero, mas o Python (ou talvez o numpy) apenas faz disso um aviso em vez de uma exceção normal.

Então, o que eu preciso saber como fazer é capturar esse aviso como se fosse uma exceção. As perguntas relacionadas a este que encontrei neste site foram respondidas da maneira que eu precisava. Aqui está o meu código:

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 esse código é executado, a saída que recebo é:

Warning: divide by zero encountered in int_scalars

Esse é o aviso que eu quero pegar. Deve ocorrer dentro da compreensão da lista.

John K.
fonte
2
Você tem certeza de que é Warning: ...? Tentando coisas como np.array([1])/0eu recebo RuntimeWarning: ...como saída.
Bakuriu
1
@MadPhysicist Não é uma duplicata; O NumPy possui sua própria arquitetura de aviso interno sobre Pythons, que pode ser controlada especificamente (ver resposta por Bakuríu).
Gerrit
@gerrit. Fico corrigido e aprendi uma coisa nova. Excluí meu comentário original para evitar o frenesi da coleção de crachás.
Mad Physicist
Outra abordagem que você pode usar é simplesmente verificar se o denominador é 0 antes da divisão, o que evita a sobrecarga de mexer no sistema de aviso de numpy. (Embora isso provavelmente significa que você tem para expandir a compreensão lista puro em um loop verificando se algum dos denominadores é zero.)
Oliver

Respostas:

198

Parece que sua configuração está usando a printopção para 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])

Isso significa que o aviso que você vê não é um aviso real, mas são apenas alguns caracteres impressos stdout(consulte a documentação para seterr). Se você quiser pegá-lo, você pode:

  1. Uso numpy.seterr(all='raise')que gerará diretamente a exceção. No entanto, isso altera o comportamento de todas as operações, portanto, é uma grande mudança de comportamento.
  2. Use numpy.seterr(all='warn'), que transformará o aviso impresso em um aviso real e você poderá usar a solução acima para localizar essa alteração no comportamento.

Depois de receber um aviso, você pode usar o warningsmódulo para controlar como os avisos devem ser tratados:

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

Leia atentamente a documentação, filterwarningspois ela permite filtrar apenas o aviso desejado e tem outras opções. Também consideraria analisar catch_warningsqual é um gerenciador de contexto que redefine automaticamente a filterwarningsfunção original :

>>> 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
fonte
Eu acho que isso é um começo. Mas na verdade não resolve o meu problema. Se eu adicionar warnings.warn (Warning ())) no meu código no bloco try, ele receberá o aviso. Por alguma razão, ele não captura a divisão por zero aviso. Aqui está a mensagem de aviso exata: Aviso: divida pelo zero encontrado em int_scalars
John K.
@JohnK. Você deve editar sua pergunta e adicionar a saída exata, caso contrário, não podemos dizer o que está errado. Ele pode ser possível que numpy define esta classe em algum lugar aviso e você tem que discovere no qual subpacote para ser capaz de pegá-lo. Não importa, eu descobri que você deveria usar RuntimeWarning. Atualizado a resposta.
Bakuriu
Você tem certeza? Mudei meu código para usar, exceto RuntimeWarning :. Ainda não está funcionando = /
John K.
@JohnK. Na documentação, afirma que a RuntimeWarningé gerado. O problema pode ser que sua configuração numpy esteja usando a printopção, que simplesmente imprime o aviso, mas não é um aviso real tratado pelo warningsmódulo ... Se esse for o caso, você pode tentar usar numpy.seterr(all='warn')e tentar novamente.
Bakuriu
3
Na minha versão de numpy, você não pode usar numpy.seterr(all='error'), errorprecisa ser raise.
detly 14/05
41

Para adicionar um pouco à resposta de @ Bakuriu:

Se você já sabe onde o aviso provavelmente ocorrerá, geralmente é mais fácil usar o numpy.errstategerenciador de contexto, em vez de numpy.seterrtratar todos os avisos subsequentes do mesmo tipo da mesma forma, independentemente de onde eles ocorram no seu código:

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

Editar:

No meu exemplo original, eu tinha a = np.r_[0], mas aparentemente houve uma mudança no comportamento de numpy, de modo que a divisão por zero é tratada de maneira diferente nos casos em que o numerador é todo-zero. Por exemplo, no 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

As mensagens de aviso correspondentes também são diferentes: 1. / 0.é registrada como RuntimeWarning: divide by zero encountered in true_divide, enquanto 0. / 0.é registrada como RuntimeWarning: invalid value encountered in true_divide. Não sei por que exatamente essa alteração foi feita, mas desconfio que isso tenha a ver com o fato de o resultado de 0. / 0.não ser representável como um número (numpy retorna um NaN nesse caso), enquanto 1. / 0.e -1. / 0.retornar + Inf e -Inf, respectivamente , de acordo com o padrão IEE 754.

Se você deseja capturar os dois tipos de erro, sempre pode passar np.errstate(divide='raise', invalid='raise')ou all='raise'se deseja gerar uma exceção para qualquer tipo de erro de ponto flutuante.

ali_m
fonte
Notavelmente aumenta FloatingPointError, não ZeroDivisionError.
Gerrit # 6/16
Isso não funciona Python 3.6.3com numpy==1.16.3. Você poderia atualizá-lo, por favor?
Anilbey
1
@anilbey Aparentemente, houve uma mudança no comportamento do numpy, o que significa que a divisão por zero agora é tratada de maneira diferente, dependendo se o numerador também é zero (todos).
ali_m
27

Para elaborar a resposta de @ Bakuriu acima, descobri que isso me permite capturar um aviso de tempo de execução de maneira semelhante a como capturaria um aviso de erro, imprimindo bem o aviso:

import warnings

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

Você provavelmente poderá brincar com a colocação do posicionamento warnings.catch_warnings (), dependendo do tamanho de um guarda-chuva que deseja lançar com erros de captura dessa maneira.

ntk4
fonte
3
resposta = 1/0 irá gerar um erro de forma independente ... você poderia dar um exemplo correto ...
Amirkhm
8

Remova warnings.filterwarnings e adicione:

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