ChatGPT解决这个技术问题 Extra ChatGPT

为什么 Python 没有符号函数?

我不明白为什么 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 一起使用。

最后,我希望您同意 signcopysign 更有用,所以即使我接受了您的观点,为什么还要费心用数学而不是符号来定义它呢?复制签名怎么能比签名有用呢?

@dmazzoni:这个论点是否适用于本网站上的所有问题?只需关闭 stackoverflow 并向相关主题开发人员或用户邮件列表询问每个问题!
问题的适当位置是任何可能被回答的地方。因此,stackoverflow 是一个合适的地方。
-1:@Davide:这里通常无法回答“为什么”和“为什么不”的问题。由于 Python 开发的大多数负责人不在这里回答问题,因此您很少(如果有的话)会得到“为什么”或“为什么不”问题的答案。此外,您没有要解决的问题。你听起来像是在咆哮。如果您有问题(“我如何解决此示例中缺少符号的问题...”),这是明智的。 “为什么不”对于这个场地来说是不明智的。
这个问题可能有点情绪化,但我不认为这是一个坏问题。我敢肯定,很多人都在寻找一个内置的标志功能,所以它可能很好奇为什么没有一个。
这是一个非常客观的问题:“为什么”Python 缺少任何给定的特性是一个关于语言设计历史的合法查询,可以通过链接到 python-dev 或其他论坛(有时是博客文章)的适当讨论来回答,其中 Python核心开发人员碰巧讨论了一个主题。之前我自己尝试过用谷歌搜索 python-dev 的一些历史,我可以理解为什么该语言的新手可能会遇到死胡同,然后来这里提问,希望更有经验的 Python 人回答!

E
Eric

编辑:

确实有一个 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.

最重要的是,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 可以正常工作。我不同意 signcopysign 更有用,因为我已经证明它只是相同功能的一个子集。


使用 [int(copysign(1, zero)) for zero in (0, 0.0, -0.0)] 给出 [1, 1, -1]。根据 en.wikipedia.org/wiki/Sign_function,这应该是 [0, 0, 0]
@Andrew - @user238424 的调用顺序是正确的。 copysign(a,b) 返回带有 b 符号的 a - b 是变化的输入,a 是用 b 的符号归一化的值。在这种情况下,评论者说明将 copysign(1,x) 作为 sign(x) 的替代品失败,因为它在 x=0 时返回 1,而 sign(0) 将评估为 0。
浮点数将“符号”与“值”分开; -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
看到错误的 sign() 替换定义、与 sign(a) 相乘的错误等效项、对 copysign 动机的错误解释以及问题中已经提到的正确替换“cmp(x, 0)” - 有信息不多,我不清楚为什么这是有这么多票的“接受”答案。?
m
martineau

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 内置,而是一个标志。

这意味着您不知道 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 列表之一。


好吧,如果那会让你开心的话,我不知道,但是 Python 3 既没有 cmp() 也没有 sign() :-)
编写一个可以与 IEEE 754 一起正常工作的好 sign() 函数并非易事。即使我没有在问题中详细说明这一点,也可以将其包含在语言中,而不是将其排除在外,这将是一个好点
您对“如果您希望排序稳定”的评论意味着您也不知道稳定排序的工作原理。您关于 copysign 和 sign 等效的声明表明您在这篇文章之前对 IEEE 754 数学知之甚少。 Python 是否应该在核心中实现所有 754 个数学函数?它应该为非 C99 编译器做什么?非754平台? "isnonnegative" 和 "isnonpositive" 也是有用的函数。 Python 是否也应该包含这些? abs(x) 服从于 x.__abs__(),所以 sign(x) 应该服从于 x.__sign__() 吗?对它的需求或需求很少,那么为什么要把它卡在核心中呢?
当我在 2.7 中尝试时,math.copysign(1, float("-nan")) 返回 1.0 而不是 -1.0
符号或符号函数是定义为符号(z) = z/|z| 的标准数学函数。它对复数的含义是明确定义的;只需对除法和绝对值的复杂版本使用相同的定义即可。结果是一个复数,其大小为 1;例如符号(-3+4j)是-0.6+0.8j。我看不出函数在 C 中的缺失与它在 Python 中的缺失有何关系。它存在于 Microsoft BASIC 中;那有关系吗?
d
dansalmo

另一个用于 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) 比您建议的更具可读性。
的确,我错了。我假设'cmp'被指定返回-1,0,+1,但我看到规范并不能保证这一点。
美丽的。回答开始的问题: python int or float to -1, 0, 1 ?
使用列表而不是 -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) 相同。
A
AllenT

由于 cmp 已变为 removed,因此您可以使用

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)

不错:) 漂亮的解决方案
C
Cristik

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


这个答案说明了 python 是多么简洁而强大。
Quibble:代码没有实现 WP 定义,它在末尾用默认子句替换了中间子句。虽然这是处理像 nan 这样的非实数所必需的,但它被错误地显示为直接跟在 WP 语句之后(“因此”)。
@JürgenStrobel 我确切地知道你的意思,我也一直在考虑这个问题。我现在扩展了正确形式的答案,同时为大多数用例保留了简化版本。
(a > 0) - (a < 0) 也符合。
@PaulCrowley 非常好!您应该编写自己的答案并解释其工作原理。
L
Luca

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))

获得定期浮动。


c
cs95

尝试运行它,其中 x 是任意数字

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

对 bool() 的强制处理比较运算符不返回布尔值的 possibility


好主意,但我认为您的意思是:int_sign = int(x > 0) - int(x < 0)
我的意思是:int_sign = lambda x: (x > 0) - (x < 0)
@yucer 不,他的意思是强制转换为 bool (无论如何它是 int 的子类),因为他给出了解释的链接的理论上的可能性。
这种结构的唯一缺点是参数出现了两次,只有当它是单个变量时才可以
N
Nick stands with Ukraine

在 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 或无穷大)。


M
Michel de Ruiter

它只是没有。

解决此问题的最佳方法是:

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

sign 函数为正值返回 1,为负值返回 -1,为 0.0 和 -0.0(以及 NaNs...)返回 0。


k
kxr

是的,正确的 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 中一样


i
inetphantom

你不需要一个,你可以使用:

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
Python 将 TrueFalse 视为 10,您完全可以这样做并获得 10-1def sign(x): return (x > 0) - (x < 0) 不会返回 bool,它会返回 int - 如果您通过 0,您将返回 0
这是唯一适用于复杂值的解决方案。
M
Mark Gershaft

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

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