为什么Python没有符号函数?

我不明白为什么Python没有sign函数。它有一个abs内置(我认为它是sign的姐妹),但没有sign

在python 2.6中,甚至有一个copysign函数(在数学中),但没有符号。当你可以只写一个sign,然后直接从abs(x) * sign(y)得到copysign时,为什么还要麻烦地写copysign(x,y)呢?后者会更清楚:x和y的符号,而对于copysign,你必须记住它是x和y的符号还是y和x的符号!

显然sign(x)没有提供比cmp(x,0)更多的东西,但这也会更具可读性(对于python这样可读性很强的语言来说,这将是一个很大的加分项)。

如果我是python设计师,我会反过来:没有内置cmp,而是sign。当你需要cmp(x,y)时,你可以只做一个sign(x-y)(或者,对于非数值的东西更好,只是一个x>y -当然这应该要求sorted接受一个布尔值而不是整数比较器)。这也会更清楚:当x>y为正数时为正数(而对于cmp,你必须记住当第一个更大的时为正数,但也可以反过来)。当然,出于其他原因,cmp本身是有意义的(例如,在对非数值的东西排序时,或者如果你希望排序是稳定的,这是不可能仅使用布尔值的)

那么,问题是:为什么Python设计者决定将sign函数排除在语言之外?为什么要麻烦copysign而不是它的父sign呢?

我遗漏了什么吗?

编辑-在Peter Hansen评论之后。 很好,你没有使用它,但你没有说你用python做什么。在我使用python的7年里,我需要它无数次,最后一次是压垮骆驼的最后一根稻草!< / p >

是的,你可以传递cmp,但90%的时候,我需要传递它在一个成语

. lambda x,y: cmp(score(x),score(y))将与sign一起工作

最后,我希望你同意sign会比copysign更有用,所以即使我同意你的观点,为什么要在数学中定义它,而不是符号呢?复印签名怎么比签名有用这么多?

233165 次浏览

copysign()由IEEE 754定义,是C99规范的一部分。这就是为什么它在Python中。该函数不能由abs(x) * sign(y)完全实现,因为它应该如何处理NaN值。

>>> import math
>>> math.copysign(1, float("nan"))
1.0
>>> math.copysign(1, float("-nan"))
-1.0
>>> math.copysign(float("nan"), 1)
nan
>>> math.copysign(float("nan"), -1)
nan
>>> float("nan") * -1
nan
>>> float("nan") * 1
nan
>>>

这使得copysign()是一个比sign()更有用的函数。

至于IEEE的signbit(x)在标准Python中不可用的具体原因,我不知道。我可以做假设,但那只是猜测。

数学模块本身使用copysign(1, x)来检查x是否为负或非负。在大多数情况下,处理数学函数似乎比使用返回10-1sign(x)更有用,因为这样可以少考虑一种情况。例如,以下内容来自Python的math模块:

static double
m_atan2(double y, double x)
{
if (Py_IS_NAN(x) || Py_IS_NAN(y))
return Py_NAN;
if (Py_IS_INFINITY(y)) {
if (Py_IS_INFINITY(x)) {
if (copysign(1., x) == 1.)
/* atan2(+-inf, +inf) == +-pi/4 */
return copysign(0.25*Py_MATH_PI, y);
else
/* atan2(+-inf, -inf) == +-pi*3/4 */
return copysign(0.75*Py_MATH_PI, y);
}
/* atan2(+-inf, x) == +-pi/2 for finite x */
return copysign(0.5*Py_MATH_PI, y);

在这里你可以清楚地看到copysign()是一个比三个值的sign()函数更有效的函数。

你写的:

如果我是python设计师,我会反过来做:没有内置cmp,而是sign

这意味着你不知道cmp()是用于数字以外的东西。cmp("This", "That")不能用sign()函数实现。

编辑整理我在其他地方的其他答案:

你的理由是abs()sign()经常一起出现。由于C标准库不包含任何类型的sign(x)函数,我不知道你如何证明你的观点。有abs(int)fabs(double)fabsf(float)fabsl(long),但没有提到sign()。有copysign()signbit(),但它们只适用于IEEE 754数字。

对于复数,sign(-3+4j)在Python中会返回什么,如果要实现的话?abs(-3+4j)返回5.0。这是一个明确的例子,说明了abs()可以在sign()没有意义的地方使用。

假设sign(x)被添加到Python中,作为abs(x)的补充。如果x是实现了__abs__(self)方法的用户定义类的实例,则abs(x)将调用x.__abs__()。为了正确工作,以同样的方式处理abs(x),那么Python必须获得一个__sign__(x)插槽。

这对于一个相对不需要的函数来说是多余的。此外,为什么sign(x)存在,而nonnegative(x)nonpositive(x)不存在?我的Python数学模块实现片段展示了如何使用copysign(x, y)来实现nonnegative(),这是简单的sign(x)无法做到的。

Python应该更好地支持IEEE 754/C99数学函数。这将添加一个signbit(x)函数,它将在浮点数的情况下执行您想要的操作。它不能用于整数或复数,更不用说字符串了,而且它没有您正在寻找的名称。

你问“为什么”,答案是“sign(x)没有用”。你断言它是有用的。然而,你的评论表明,你知道的还不够多,无法做出这样的断言,这意味着你必须拿出令人信服的证据来证明它的必要性。说NumPy实现了它是不够有说服力的。你需要展示如何用sign()函数来改进现有代码。

并且它超出了StackOverflow的范围。相反,请使用Python列表中的一个。

编辑:

确实有一个包含sign()补丁,但它没有被接受,因为他们不同意它在所有边缘情况下都应该返回什么 (+/-0, +/-nan,等等)

所以他们决定只实现copyysign,它(虽然更啰嗦)可以是用于将边界情况所需的行为委托给最终用户 - 有时可能需要调用cmp(x,0)


我不知道为什么它不是内置的,但我有一些想法。

copysign(x,y):
Return x with the sign of y.

最重要的是,copysignsign!在x=1时调用copysignsign函数相同。所以你可以使用copysign忘了它吧

>>> math.copysign(1, -4)
-1.0
>>> math.copysign(1, 3)
1.0

如果你厌倦了传递两个完整的参数,你可以这样实现sign,并且它仍然与其他人提到的IEEE东西兼容:

>>> sign = functools.partial(math.copysign, 1) # either of these
>>> sign = lambda x: math.copysign(1, x) # two will work
>>> sign(-4)
-1.0
>>> sign(3)
1.0
>>> sign(0)
1.0
>>> sign(-0.0)
-1.0
>>> sign(float('nan'))
-1.0

其次,通常当你想要某个值的符号时,你只需要将它与另一个值相乘。当然,这基本上就是copysign所做的。

所以,与其:

s = sign(a)
b = b * s

你可以这样做:

b = copysign(b, a)

是的,我很惊讶你已经使用Python 7年了,并且认为cmp可以如此容易地删除并被sign取代!你是否从未使用__cmp__方法实现过类?你是否从未调用cmp并指定自定义比较器函数?

总之,我发现自己也想要一个sign函数,但是第一个参数为1的copysign就可以了。我不同意sign会比copysign更有用,因为我已经证明了它只是相同功能的一个子集。

sign()的另一行代码

sign = lambda x: (1, -1)[x<0]

如果你想让它在x = 0时返回0

sign = lambda x: x and (1, -1)[x<0]

因为cmp已经是删除,你可以得到相同的功能

def cmp(a, b):
return (a > b) - (a < b)


def sign(a):
return (a > 0) - (a < 0)

它适用于floatint,甚至Fraction。在float的情况下,注意sign(float("nan"))为零。

Python不要求比较返回布尔值,因此将比较强制为bool()可以防止允许但不常见的实现:

def sign(a):
return bool(a > 0) - bool(a < 0)

Numpy有一个符号函数,并为您提供了其他函数。所以:

import numpy as np
x = np.sign(y)

只是要注意结果是numpy.float64:

>>> type(np.sign(1.0))
<type 'numpy.float64'>

对于json之类的东西,这很重要,因为json不知道如何序列化numpy。float64类型。在这种情况下,你可以这样做:

float(np.sign(y))

得到一个常规的浮动。

你不需要,你可以使用:

if not number == 0:
sig = number/abs(number)
else:
sig = 0

或者创建其他人描述的函数:

sign = lambda x: bool(x > 0) - bool(x < 0)


def sign(x):
return bool(x > 0) - bool(x < 0)

试着运行这个,其中x是任意数

int_sign = bool(x > 0) - bool(x < 0)

对bool()的强制转换处理比较运算符不返回布尔值的可能性

是的,一个正确的sign()函数至少应该在math模块中-就像它在numpy中一样。因为面向数学的代码经常需要它。

但是math.copysign()单独也是有用的。

cmp()obj.__cmp__()…通常具有较高的独立性。不仅仅是面向数学的代码。考虑比较/排序元组,日期对象,…

http://bugs.python.org/issue1640中关于省略math.sign()的dev参数是奇怪的,因为:

  • 没有单独的-NaN
  • sign(nan) == nan无忧(像exp(nan)一样)
  • sign(-0.0) == sign(0.0) == 0没有担心
  • sign(-inf) == -1没有担心

——就像numpy中的一样

在Python 2中,cmp()返回一个整数:不要求结果为-1、0或1,因此sign(x)cmp(x,0)不同。

在Python 3中,cmp()已被删除,以支持丰富的比较。对于cmp(), Python 3 表明这:

def cmp(a, b):
return (a > b) - (a < b)

这对于cmp()来说很好,但同样不能用于sign(),因为比较操作符不需要返回布尔值

为了处理这种可能性,比较结果必须被强制为布尔值:

 def sign(x):
return bool(x > 0) - bool(x < 0)

这适用于任何完全有序的type(包括特殊值,如NaN或无穷大)。

维基百科定义的内容如下:

sign definition

因此,为了符合定义:

sign = lambda x: -1 if x < 0 else (1 if x > 0 else (0 if x == 0 else NaN))

就所有意图和目的而言,可以简化为:

sign = lambda x: -1 if x < 0 else (1 if x > 0 else 0)

这个函数定义< >强劲快速执行< / >强并产生保证正确的结果为0,0.0,-0.0,-4和5(参见对其他错误答案的注释)。

注意零既不是正的也不是负的.;

它就是没有。

解决这个问题的最好方法是:

sign = lambda x: bool(x > 0) - bool(x < 0)

sign函数返回1表示正值,-1表示负值,0表示0.0和-0.0(和nan…)

其他答案中列出的许多情况忽略了特殊情况(+/-0)或假设符号(-0.0)==符号(0.0)。它可能是naïve,但是对于IEEE的当前实现,我们已经有了-0.0 == 0.0,并且使用sign()可以消除两者之间的歧义。

FogleBird提供的例子似乎是迄今为止最好的定义,因为它似乎可以处理+/- 0,INFINITY和NaN。