我不明白为什么 Python 没有 sign
函数。它有一个 abs
内置函数(我认为它是 sign
的姐妹),但没有 sign
。
在 python 2.6 中甚至有一个 copysign
函数(在 math 中),但没有符号。当您可以写一个 sign
然后直接从 abs(x) * sign(y)
获取 copysign
时,为什么还要写一个 copysign(x,y)
?后者会更清楚:x 带有 y 的符号,而对于 copysign,您必须记住它是带有 y 符号的 x 还是带有 x 符号的 y!
显然 sign(x)
没有提供比 cmp(x,0)
更多的东西,但它也会比这更具可读性(对于像 python 这样的可读性很强的语言,这将是一个很大的优势)。
如果我是 python 设计者,我会反过来:没有 cmp
内置,而是 sign
。当你需要 cmp(x,y)
时,你可以只做一个 sign(x-y)
(或者,对于非数字的东西更好,只是一个 x>y - 当然这应该要求 sorted
接受一个布尔值而不是一个整数比较器) .这也会更清楚:当 x>y
时为正(而对于 cmp
,您必须记住当 first 更大 时为正的约定,但它可能是其他方式)。当然,由于其他原因,cmp
本身是有意义的(例如,在对非数字事物进行排序时,或者如果您希望排序稳定,这不可能仅使用布尔值)
所以,问题是:为什么 Python 设计者决定将 sign
函数排除在语言之外?为什么要打扰 copysign
而不是它的父 sign
?
我错过了什么吗?
编辑 - 在彼得汉森评论之后。很公平,你没有使用它,但你没有说你用 python 做什么。在我使用python的7年里,我无数次需要它,最后是压垮骆驼的最后一根稻草!
是的,您可以传递 cmp,但我需要传递它的 90% 的时间是使用像 lambda x,y: cmp(score(x),score(y))
这样的成语,它可以很好地与 sign 一起使用。
最后,我希望您同意 sign
比 copysign
更有用,所以即使我接受了您的观点,为什么还要费心用数学而不是符号来定义它呢?复制签名怎么能比签名有用呢?
编辑:
确实有一个 patch,其中在 math 中包含 sign()
,但它没有被接受,因为他们不同意 what it should return in all the edge cases(+/-0、+/-nan 等)
所以他们决定只实现copysign,它(虽然更详细)可以是used to delegate to the end user the desired behavior for edge cases - which sometimes might require the call to cmp(x,0)
。
我不知道为什么它不是内置的,但我有一些想法。
copysign(x,y):
Return x with the sign of y.
最重要的是,copysign
是 sign
的超集!使用 x=1 调用 copysign
与 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
函数,但第一个参数为 1 的 copysign
可以正常工作。我不同意 sign
比 copysign
更有用,因为我已经证明它只是相同功能的一个子集。
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 内置,而是一个标志。
这意味着您不知道 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。这是如何在 sign()
没有意义的地方使用 abs()
的一个明显示例。
假设将 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 列表之一。
cmp()
也没有 sign()
:-)
另一个用于 sign() 的衬垫
sign = lambda x: (1, -1)[x<0]
如果您希望它在 x = 0 时返回 0:
sign = lambda x: x and (1, -1)[x<0]
cmp(x, 0)
等同于 sign
,并且 lambda x: cmp(x, 0)
比您建议的更具可读性。
-1 if x < 0 else 1
有什么好处吗?
sign = lambda x: -1 if x < 0 else 1
快 15%。与 sign = lambda x: x and (-1 if x < 0 else 1)
相同。
由于 cmp
已变为 removed,因此您可以使用
def cmp(a, b):
return (a > b) - (a < b)
def sign(a):
return (a > 0) - (a < 0)
它适用于 float
、int
甚至 Fraction
。在 float
的情况下,通知 sign(float("nan"))
为零。
Python 不要求比较返回布尔值,因此将比较强制为 bool() 可以防止允许但不常见的实现:
def sign(a):
return bool(a > 0) - bool(a < 0)
definition on Wikipedia 内容如下:
https://wikimedia.org/api/rest_v1/media/math/render/svg/f8374ef8411ba954fb2655992b6e4496b8710cd6
因此,为了符合定义:
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)
此函数定义 executes fast 并产生 保证 correct results for 0, 0.0, -0.0, -4 and 5(请参阅其他不正确答案的注释)。
请注意 zero (0) is neither positive nor negative。
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))
获得定期浮动。
尝试运行它,其中 x 是任意数字
int_sign = bool(x > 0) - bool(x < 0)
对 bool() 的强制处理比较运算符不返回布尔值的 possibility。
在 Python 2 中,cmp()
返回一个整数:结果不要求为 -1、0 或 1,因此 sign(x)
与 cmp(x,0)
不同。
在 Python 3 中,已删除 cmp()
以支持丰富的比较。对于 cmp()
,Python 3 suggests this:
def cmp(a, b):
return (a > b) - (a < b)
这对于 cmp() 很好,但同样不能用于 sign(),因为比较运算符不需要返回 booleans。
为了处理这种可能性,必须将比较结果强制转换为布尔值:
def sign(x):
return bool(x > 0) - bool(x < 0)
这适用于任何完全有序的 type
(包括特殊值,如 NaN
或无穷大)。
它只是没有。
解决此问题的最佳方法是:
sign = lambda x: bool(x > 0) - bool(x < 0)
此 sign
函数为正值返回 1,为负值返回 -1,为 0.0 和 -0.0(以及 NaNs...)返回 0。
是的,正确的 sign()
函数应该至少在数学模块中 - 因为它在 numpy.因为人们经常需要它来编写面向数学的代码。
但是 math.copysign()
也可以独立使用。
cmp()
和 obj.__cmp__()
...通常独立地具有很高的重要性。不仅仅是面向数学的代码。考虑比较/排序元组,日期对象,......
http://bugs.python.org/issue1640 中关于省略 math.sign()
的开发论点很奇怪,因为:
没有单独的 -NaN
sign(nan) == nan 不用担心(如 exp(nan) )
符号(-0.0) == 符号(0.0) == 0 不用担心
sign(-inf) == -1 不用担心
-- 就像在 numpy 中一样
你不需要一个,你可以使用:
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 / abs(x)
比仅仅链接 if/else
来检查变量在 0 的哪一侧,或者为此使用粘糊糊但令人满意的 return (x > 0) - (x < 0)
减去 bool
值并返回所需的时间稍长int
True
和 False
视为 1
和 0
,您完全可以这样做并获得 1
、0
或 -1
。 def sign(x): return (x > 0) - (x < 0)
不会返回 bool
,它会返回 int
- 如果您通过 0
,您将返回 0
其他答案中列出的许多情况忽略了特殊情况(+/-0)或假设符号(-0.0)==符号(0.0)。这可能是幼稚的,但是在 IEEE 的当前实现中,我们已经有了 -0.0 == 0.0 并且有了 sign() 可以让我们消除两者之间的歧义。
FogleBird 提供的示例似乎是最好的定义,因为它似乎可以处理 +/- 0、INFINITY 和 NaN。
[int(copysign(1, zero)) for zero in (0, 0.0, -0.0)]
给出[1, 1, -1]
。根据 en.wikipedia.org/wiki/Sign_function,这应该是[0, 0, 0]
copysign(a,b)
返回带有 b 符号的 a - b 是变化的输入,a 是用 b 的符号归一化的值。在这种情况下,评论者说明将 copysign(1,x) 作为 sign(x) 的替代品失败,因为它在 x=0 时返回 1,而 sign(0) 将评估为 0。cmp()
将给出所需的结果,可能几乎适用于任何人关心的所有情况:[cmp(zero, 0) for zero in (0, 0.0, -0.0, -4, 5)]
==>[0, 0, 0, -1, 1]
。s = sign(a) b = b * s
不等于b = copysign(b, a)
!它不考虑 b 的符号。例如,如果a=b=-1
,第一个代码将返回 1,而第二个代码将返回 -1