为什么Python没有符号函数?

368 投票
12 回答
274894 浏览
提问于 2025-04-15 17:31

我不明白为什么Python没有一个叫做sign的函数。它有一个内置的abs函数(我觉得可以把它看作是sign的姐妹),但就是没有sign

在Python 2.6中甚至有一个copysign函数(在math库里),但没有sign。既然可以直接写一个sign函数,为什么还要写copysign(x,y)呢?你可以用abs(x) * sign(y)直接得到copysign的效果,这样更清楚: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的七年里,我无数次需要它,最后一次真是让我忍无可忍了!

是的,你可以传递cmp,但我需要传递它的90%的时候都是在像lambda x,y: cmp(score(x),score(y))这样的用法中,而用sign也能很好地工作。

最后,我希望你同意signcopysign更有用,所以即使我接受你的观点,为什么要在数学中定义copysign而不是sign呢?copysign怎么会比sign更有用呢?

12 个回答

65

这是另一个关于sign()函数的一行代码示例。

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

如果你希望当x等于0时,它返回0:

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

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 数字。

对于复数来说,如果在 Python 中实现 sign(-3+4j),它会返回什么?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 的邮件列表中讨论。

340

编辑:

确实有一个补丁,它把sign()加入了math库,但没有被接受,因为大家对在一些特殊情况下应该返回什么结果(比如正负零、正负无效数等)没有达成一致意见

所以他们决定只实现copysign,虽然这个函数的名字比较长,但它可以让用户自己决定在特殊情况下想要的行为,有时候这可能需要调用cmp(x,0)


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

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

最重要的是,copysignsign的超集!调用copysign时如果x=1,就和sign函数的效果一样。所以你可以直接使用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函数,但用copysign,第一个参数为1,也能很好地解决问题。我不同意sign会比copysign更有用,因为我已经证明它只是同一功能的一个子集。

撰写回答