为什么Python没有符号函数?
我不明白为什么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
也能很好地工作。
最后,我希望你同意sign
比copysign
更有用,所以即使我接受你的观点,为什么要在数学中定义copysign
而不是sign
呢?copysign
怎么会比sign
更有用呢?
12 个回答
这是另一个关于sign()函数的一行代码示例。
sign = lambda x: (1, -1)[x<0]
如果你希望当x等于0时,它返回0:
sign = lambda x: x and (1, -1)[x<0]
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
是否为负数或非负数。对于大多数数学函数来说,这种方式似乎比返回 1
、0
或 -1
的 sign(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 的邮件列表中讨论。
编辑:
确实有一个补丁,它把sign()
加入了math库,但没有被接受,因为大家对在一些特殊情况下应该返回什么结果(比如正负零、正负无效数等)没有达成一致意见。
所以他们决定只实现copysign
,虽然这个函数的名字比较长,但它可以让用户自己决定在特殊情况下想要的行为,有时候这可能需要调用cmp(x,0)
。
我不知道为什么它不是内置函数,但我有一些想法。
copysign(x,y):
Return x with the sign of y.
最重要的是,copysign
是sign
的超集!调用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
更有用,因为我已经证明它只是同一功能的一个子集。