web-dev-qa-db-fra.com

Comment puis-je détecter un avertissement numpy comme s'il s'agissait d'une exception (pas seulement pour les tests)?

Je dois créer un polynôme de Lagrange dans Python pour un projet en cours. Je fais un style barycentrique pour éviter d'utiliser une boucle for explicite, par opposition au style une différence divisée de Newton. Le problème que j'ai est que je dois attraper une division par zéro, mais Python (ou peut-être numpy) en fait simplement un avertissement au lieu d'une exception normale.

Donc, ce que j'ai besoin de savoir faire, c'est capter cet avertissement comme s'il s'agissait d'une exception. Les questions liées à ce que j'ai trouvé sur ce site ont reçu une réponse différente de celle dont j'avais besoin. Voici mon code:

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. 

Lorsque ce code est exécuté, le résultat obtenu est:

Warning: divide by zero encountered in int_scalars

C'est l'avertissement que je veux attraper. Cela devrait se produire dans la compréhension de la liste.

145
John K.

Il semble que votre configuration utilise l'option print pour 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])
_

Cela signifie que l'avertissement que vous voyez est pas un véritable avertissement, mais qu'il ne s'agit que de quelques caractères imprimés avec stdout (voir la documentation de seterr ). Si vous voulez l'attraper, vous pouvez:

  1. Utilisez numpy.seterr(all='raise') qui lèvera directement l'exception. Cela change cependant le comportement de toutes les opérations, donc c'est un très gros changement de comportement.
  2. Utilisez numpy.seterr(all='warn'), qui transformera l'avertissement imprimé en un véritable avertissement et vous pourrez utiliser la solution ci-dessus pour localiser ce changement de comportement.

Une fois que vous avez reçu un avertissement, vous pouvez utiliser le module warnings pour contrôler le traitement des avertissements:

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

Lisez attentivement la documentation de filterwarnings , car elle vous permet de filtrer uniquement l’avertissement souhaité et d’autres options. J'aimerais aussi envisager de regarder catch_warnings , gestionnaire de contexte qui réinitialise automatiquement la fonction filterwarnings d'origine:

_>>> 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: 
_
170
Bakuriu

Pour ajouter un peu à la réponse de @ Bakuriu:

Si vous savez déjà où l'avertissement est susceptible de se produire, il est souvent préférable d'utiliser le gestionnaire de contexte numpy.errstate , plutôt que numpy.seterr qui traite tous les avertissements ultérieurs du même type sont les mêmes quel que soit leur emplacement dans votre code:

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

Modifier:

Dans mon exemple initial, j'avais _a = np.r_[0]_, mais apparemment, le comportement de numpy a changé: la division par zéro est gérée différemment dans les cas où le numérateur est composé de zéros. Par exemple, dans 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
_

Les messages d'avertissement correspondants sont également différents: _1. / 0._ est consigné en tant que _RuntimeWarning: divide by zero encountered in true_divide_, alors que _0. / 0._ est consigné en tant que _RuntimeWarning: invalid value encountered in true_divide_. Je ne sais pas exactement pourquoi ce changement a été apporté, mais je suppose que cela a à voir avec le fait que le résultat de _0. / 0._ n'est pas représentable sous forme de nombre (numpy renvoie un NaN dans ce cas) alors que _1. / 0._ et _-1. / 0._ return + Inf et -Inf, respectivement, conformément à la norme IEE 754.

Si vous voulez intercepter les deux types d’erreur, vous pouvez toujours passer np.errstate(divide='raise', invalid='raise'), ou _all='raise'_ si vous voulez déclencher une exception sur any type d'erreur en virgule flottante.

34
ali_m

Pour élaborer sur la réponse de @ Bakuriu ci-dessus, j'ai constaté que cela me permettait de détecter un avertissement d'exécution de la même manière que pour détecter un avertissement d'erreur et d'afficher correctement cet avertissement:

import warnings

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

Vous pourrez probablement jouer avec le placement du placement warnings.catch_warnings () en fonction de la taille du parapluie que vous souhaitez lancer avec des erreurs de capture de cette façon.

24
ntk4

Supprimez warnings.filterwarnings et ajoutez:

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