3

通常,NaN(不是数字)通过计算传播,因此我不需要在每个步骤中检查 NaN。这几乎总是有效,但显然也有例外。例如:

>>> nan = float('nan')
>>> pow(nan, 0)
1.0

我发现了以下评论

通过算术运算传播安静的 NaN 允许在一系列运算结束时检测到错误,而无需在中间阶段进行大量测试。但是,请注意,根据语言和函数的不同,NaN 可以在表达式中以静默方式删除,这将为所有其他浮点值提供恒定结果,例如 NaN^0,它可以定义为 1,因此通常稍后进行测试需要设置一个 INVALID 标志来检测所有引入 NaN 的情况。

为了满足那些希望更严格地解释幂函数应该如何作用的人,2008 年标准定义了两个额外的幂函数;pown(x, n) 其中指数必须是整数,而 powr(x, y) 则在参数为 NaN 时返回 NaN,否则求幂将给出不确定的形式。

有没有办法通过 Python 检查上面提到的 INVALID 标志?或者,是否有任何其他方法可以捕获 NaN 不传播的情况?

动机:我决定使用 NaN 来处理缺失数据。在我的应用程序中,缺少输入会导致结果丢失。它工作得很好,除了我描述的。

4

4 回答 4

3

我意识到自从被问到这个问题已经过去了一个月,但是我遇到了一个类似的问题(即pow(float('nan'), 1)在一些 Python 实现中抛出异常,例如 Jython 2.52b2),我发现上面的答案并不完全是我的寻找。

使用 6502 建议的 MissingData 类型似乎是可行的方法,但我需要一个具体的例子。我尝试了 Ethan Furman 的 NullType 类,但发现它不适用于任何算术运算,因为它不会强制数据类型(见下文),而且我也不喜欢它明确命名每个被覆盖的算术函数。

从 Ethan 的示例和我在这里找到的调整代码开始,我来到了下面的课程。尽管该类被大量注释,但您可以看到它实际上只有几行功能代码。

关键点是: 1.使用 coerce() 返回两个 NoData 对象,用于混合类型(例如 NoData + float)算术运算,以及两个字符串用于基于字符串(例如 concat)的操作。 2.使用 getattr() 为所有其他属性/方法访问返回一个可调用的 NoData() 对象 3.使用 call() 来实现 NoData() 对象的所有其他方法:通过返回一个 NoData() 对象

以下是它的一些使用示例。

>>> nd = NoData()
>>> nd + 5
NoData()
>>> pow(nd, 1)
NoData()
>>> math.pow(NoData(), 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: nb_float should return float object
>>> nd > 5
NoData()
>>> if nd > 5:
...     print "Yes"
... else:
...     print "No"
... 
No
>>> "The answer is " + nd
'The answer is NoData()'
>>> "The answer is %f" % (nd)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: float argument required, not instance
>>> "The answer is %s" % (nd)
'The answer is '
>>> nd.f = 5
>>> nd.f
NoData()
>>> nd.f()
NoData()

我注意到将 pow 与 NoData() 一起使用会调用 ** 运算符,因此可以与 NoData 一起使用,但使用 math.pow 并不会,因为它首先尝试将 NoData() 对象转换为浮点数。我很高兴使用非数学 pow - 希望 6502 等在他们在上面的评论中遇到 pow 问题时使用 math.pow。

我想不出解决方法的另一个问题是使用格式 (%f) 运算符...在这种情况下,没有调用 NoData 的方法,如果您不提供浮点数,运算符就会失败。无论如何,这就是课程本身。

class NoData():
"""NoData object - any interaction returns NoData()"""
def __str__(self):
    #I want '' returned as it represents no data in my output (e.g. csv) files
    return ''        

def __unicode__(self):
    return ''

def __repr__(self):
    return 'NoData()'

def __coerce__(self, other_object):
    if isinstance(other_object, str) or isinstance(other_object, unicode):
        #Return string objects when coerced with another string object.
        #This ensures that e.g. concatenation operations produce strings.
        return repr(self), other_object  
    else:
        #Otherwise return two NoData objects - these will then be passed to the appropriate
        #operator method for NoData, which should then return a NoData object
        return self, self

def __nonzero__(self):
    #__nonzero__ is the operation that is called whenever, e.g. "if NoData:" occurs
    #i.e. as all operations involving NoData return NoData, whenever a 
    #NoData object propagates to a test in branch statement.       
    return False        

def __hash__(self):
    #prevent NoData() from being used as a key for a dict or used in a set
    raise TypeError("Unhashable type: " + self.repr())

def __setattr__(self, name, value):
    #This is overridden to prevent any attributes from being created on NoData when e.g. "NoData().f = x" is called
    return None       

def __call__(self, *args, **kwargs):
    #if a NoData object is called (i.e. used as a method), return a NoData object
    return self    

def __getattr__(self,name):
    #For all other attribute accesses or method accesses, return a NoData object.
    #Remember that the NoData object can be called (__call__), so if a method is called, 
    #a NoData object is first returned and then called.  This works for operators,
    #so e.g. NoData() + 5 will:
    # - call NoData().__coerce__, which returns a (NoData, NoData) tuple
    # - call __getattr__, which returns a NoData object
    # - call the returned NoData object with args (self, NoData)
    # - this call (i.e. __call__) returns a NoData object   

    #For attribute accesses NoData will be returned, and that's it.

    #print name #(uncomment this line for debugging purposes i.e. to see that attribute was accessed/method was called)
    return self
于 2012-05-03T12:34:56.710 回答
2

如果它只是pow()让您头疼,您可以轻松地重新定义它以NaN在您喜欢的任何情况下返回。

def pow(x, y):
    return x ** y if x == x else float("NaN")

如果NaN可以用作指数,您还需要检查它;这引发了一个ValueError例外,除非基数为 1(显然根据理论,任何幂的 1,即使不是数字,也是 1)。

(当然pow()实际上需要三个操作数,第三个是可选的,我将作为练习留下哪个省略...)

不幸的是,**运算符具有相同的行为,并且无法为内置数字类型重新定义它。抓住这一点的一种可能性是编写float该实现的子类__pow__()并将__rpow__()该类用作您的NaN值。

Python 似乎不提供对计算设置的任何标志的访问。即使确实如此,您也必须在每次单独操作后进行检查。

事实上,进一步考虑,我认为最好的解决方案可能是简单地使用一个虚拟类的实例来处理缺失值。Python 将阻塞您尝试对这些值执行的任何操作,引发异常,您可以捕获异常并返回默认值或其他任何内容。如果缺少所需的值,则没有理由继续进行其余的计算,因此应该可以例外。

于 2012-04-05T19:14:25.553 回答
2

回答您的问题:不,无法使用普通浮点数检查标志。但是,您可以使用提供更多控制的 Decimal 类。. . 但速度有点慢。

您的另一种选择是使用EmptyDataorNull类,例如这个:

class NullType(object):
    "Null object -- any interaction returns Null"
    def _null(self, *args, **kwargs):
        return self
    __eq__ = __ne__ = __ge__ = __gt__ = __le__ = __lt__ = _null
    __add__ = __iadd__ = __radd__ = _null
    __sub__ = __isub__ = __rsub__ = _null
    __mul__ = __imul__ = __rmul__ = _null
    __div__ = __idiv__ = __rdiv__ = _null
    __mod__ = __imod__ = __rmod__ = _null
    __pow__ = __ipow__ = __rpow__ = _null
    __and__ = __iand__ = __rand__ = _null
    __xor__ = __ixor__ = __rxor__ = _null
    __or__ = __ior__ = __ror__ = _null
    __divmod__ = __rdivmod__ = _null
    __truediv__ = __itruediv__ = __rtruediv__ = _null
    __floordiv__ = __ifloordiv__ = __rfloordiv__ = _null
    __lshift__ = __ilshift__ = __rlshift__ = _null
    __rshift__ = __irshift__ = __rrshift__ = _null
    __neg__ = __pos__ = __abs__ = __invert__ = _null
    __call__ = __getattr__ = _null

    def __divmod__(self, other):
        return self, self
    __rdivmod__ = __divmod__

    if sys.version_info[:2] >= (2, 6):
        __hash__ = None
    else:
        def __hash__(yo):
            raise TypeError("unhashable type: 'Null'")

    def __new__(cls):
        return cls.null
    def __nonzero__(yo):
        return False
    def __repr__(yo):
        return '<null>'
    def __setattr__(yo, name, value):
        return None
    def __setitem___(yo, index, value):
        return None
    def __str__(yo):
        return ''
NullType.null = object.__new__(NullType)
Null = NullType()

您可能想要更改__repr____str__方法。另外,请注意Null不能用作字典键,也不能存储在集合中。

于 2012-04-05T20:54:54.203 回答
2

为什么使用NaN它已经有另一个语义而不是使用MissingData你自己定义的类的实例?

在实例上定义操作MissingData以获取传播应该很容易......

于 2012-04-05T19:23:42.470 回答