On Sat, Dec 28, 2019 at 11:29 PM Steve Barnes <gadgetst...@live.co.uk> wrote:
> > > How about something along the lines of: > > def isnan(num): > > """ Attempt at generic NaN check """ > > if hasattr(num, "is_nan"): # Does it have its own method? > > return num.is_nan() > > else: > > return num != num > > Then decimal would work nicely as would any potential types that are not > self-equal would just need to define their own method albeit with a > prescribed name. Of course it will return false for non-numeric types such > as strings. > one trick here is sequences with a NaN in them: In [56]: [1, 2, 3, float('NaN')] != [1, 2, 3, float('NaN')] Out[56]: True Which is why I'm leaning toward checking for a Number type: def is_nan(num): """ This version works as well """ try: return num.is_nan() except AttributeError: if isinstance(num, Number): return num != num return False This doesn't work with single-element numpy arrays (it does with numpy scalars). Which is maybe fine -- at least for the statistics module, as folks using numpy are probaly using a numpy statistics package anyway. Though I'm not sure why it's better than: def is_nan(num): """ This version works for everything I've tried """ try: return num.is_nan() except AttributeError: if isinstance(num, complex): return cmath.isnan(num) try: return math.isnan(num) except: return False Which passes all the tests I came up with. (code enclosed) -CHB -- Christopher Barker, PhD Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython
""" some tests of an is_nan function """ import math import cmath from decimal import Decimal from fractions import Fraction from numbers import Number import numpy as np import pytest def is_nan(num): """ This version works for everything I've tried """ try: return num.is_nan() except AttributeError: if isinstance(num, complex): return cmath.isnan(num) try: return math.isnan(num) except: return False # def is_nan(num): # """ # This version works as well # """ # try: # return num.is_nan() # except AttributeError: # if isinstance(num, Number): # return num != num # return False nan_vals = [Decimal('nan'), Decimal('snan'), float('nan'), np.nan, np.array(np.nan), # zero dim array np.array([np.nan, ]), # one element array np.array([np.nan]).reshape((1, 1, 1)), # one element 3d array np.float32(np.nan), # numpy scalar np.array(np.nan, dtype=np.float128), complex(float('nan'), 1), complex(0, float('nan')), complex(float('nan'), float('nan')), ] non_nan_vals = [34, 10**1000, # an int too big for a float 3.4, np.array(1.2e34), # zero dim array np.array([1.2e34, ]), # one element array np.array([1.2e34]).reshape((1, 1, 1)), # one element 3d array np.float32(1.234), # numpy scalar np.array(1.23, dtype=np.float32), np.array(1e200, dtype=np.float128) * 1e200, # a numpy float128 too big for a float np.array([1.23, 3.45], dtype=np.float128)[0], Decimal('1e500'), Fraction(1, 10), Fraction(1, 1000000)**100, # fraction too small for a float Fraction(10, 1) ** 500, # fraction too large for a float complex(1, 2), complex(-123, 154), "a string", # a string can never be a NaN [1, 2, 3], # nor is a list [1, 2, 3, float('NaN')], # even if it has a NaN in it. np.array([np.nan, np.nan]), # more than one element array -- non NaN ] exception_raising = [] @pytest.mark.parametrize("num", nan_vals) def test_nan(num): assert is_nan(num) @pytest.mark.parametrize("num", non_nan_vals) def test_not_nan(num): assert not is_nan(num) @pytest.mark.parametrize("num, exp", exception_raising) def test_exception(num, exp): with pytest.raises(Exception): is_nan(num)
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/PGDUY4ZRJX7VSKO62CRJDUOISA5PPA2S/ Code of Conduct: http://python.org/psf/codeofconduct/