为什么Python没有符号功能?


241

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

在python 2.6中甚至有一个copysign函数(在math中),但是没有符号。copysign(x,y)当您可以只写一个sign然后copysign直接从中获取时,为什么还要麻烦写一个abs(x) * sign(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年中,我无数次需要它,最后一个是打破骆驼背的稻草!

是的,您可以传递cmp,但是我传递它的90%的时间是成语,这样 lambda x,y: cmp(score(x),score(y))就可以很好地使用sign了。

最后,我希望您同意这sign会比有用copysign,所以即使我购买了您的视图,为什么还要在数学中定义它而不是符号呢?copysign如何比sign有用呢?


33
@dmazzoni:此论点不适用于本网站上的所有问题吗?只需关闭stackoverflow并向相关主题开发人员或用户邮件列表提问即可!
Davide

43
问题的适当位置是可能回答的任何位置。因此,stackoverflow是一个适当的地方。
Stefano Borini,2009年

23
-1:@Davide:“为什么”和“为什么不”的问题通常在这里无法回答。由于Python开发的大多数原则在这里都不回答问题,因此您很少(如果有的话)会得到“为什么”或“为什么不”问题的答案。此外,您没有要解决的问题。您听起来好像很。如果您有问题(“在此示例中如何解决缺少信号的问题...”),那是明智的。“为什么不”对这个场所不明智。
S.Lott

31
这个问题可能有点激动,但我认为这不是一个坏问题。我确定很多人都在寻找内置的符号功能,因此很奇怪为什么没有内置符号功能。
FogleBird

17
这是一个完全客观的问题:“为什么” Python缺少任何给定的功能是关于语言设计历史的合法查询,可以通过链接到python-dev或其他论坛(有时是博客文章)中的适当讨论来回答,以进行回答。核心开发人员碰巧将话题散列出来。以前,我自己曾尝试过在Google中找到python-dev的历史记录,所以我能理解为什么该语言的新手可能会走入死胡同,并来这里问问,希望有经验的Python人士来回答!
布兰登·罗兹

Answers:


228

编辑:

确实,数学中包含一个补丁,但未被接受,因为他们在所有极端情况(+/- 0,+ /-nan等)上均未达成共识。sign()

因此,他们决定仅实施copysign,尽管可以使用它(尽管更为冗长),才能将最终情况下所需的行为委托给最终用户 - 有时可能需要调用cmp(x,0)


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

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

最重要的copysign是,是的超集signcopysignx = 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,因为我已经证明它只是相同功能的一部分。


35
使用[int(copysign(1, zero)) for zero in (0, 0.0, -0.0)]给出[1, 1, -1]。那应该是[0, 0, 0]根据en.wikipedia.org/wiki/Sign_function
user238424 2010年

12
@Andrew-@ user238424的呼叫顺序正确。copysign(a,b)返回带有b的符号的a-b是变化的输入,a是要以b的符号归一化的值。在这种情况下,该评论是表示复制符号(1,X)作为符号(x)的一个替换失败,因为它返回1对于x = 0,而符号(0)将评估为0。
PaulMcG

7
浮点数将“符号”与“值”分开;-0.0是负数,即使这似乎是实现错误。只需使用即可cmp()获得所需的结果,可能几乎在任何人都关心的情况下:[cmp(zero, 0) for zero in (0, 0.0, -0.0, -4, 5)]==> [0, 0, 0, -1, 1]
pythonlarry 2013年

11
s = sign(a) b = b * s不等于b = copysign(b, a)!它不考虑b的符号。例如,如果a=b=-1第一个代码将返回1,而第二个代码将返回-1
Johannes Jendersie

14
看到错误的sign()替换定义,与sign(a)相乘的错误等价项,对copysign动机的错误解释以及在问题中已经提到的正确替换“ cmp(x,0)”-没有太多信息,我不清楚为什么这是获得这么多选票的“可接受”答案。
kxr

59

“ 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()更有用。

至于为什么标准的Python中没有IEEE的signbit(x)的具体原因,我不知道。我可以做一些假设,但这是猜测。

数学模块本身使用copysign(1,x)作为检查x是负数还是非负数的方法。在大多数情况下,处理数学函数似乎比使sign(x)返回1、0或-1有用,因为要考虑的情况要少得多。例如,以下内容来自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),但没有提及符号。有“ copysign()”和“ signbit()”,但它们仅适用于IEEE 754数字。

对于复数,如果要实现,sign(-3 + 4j)在Python中返回什么?abs(-3 + 4j)返回5.0。这是一个清晰的例子,说明在sign()毫无意义的地方如何使用abs()。

假设将sign(x)添加到Python,作为对abs(x)的补充。如果“ x”是实现__abs __(self)方法的用户定义类的实例,则abs(x)将调用x .__ abs __()。为了正常工作,以与Python相同的方式处理abs(x),Python将必须获得一个符号(x)插槽。

对于相对不需要的功能来说,这是过多的。此外,为什么应该有sign(x)而不存在nonnegative(x)和nonpositive(x)?我来自Python数学模块实现的代码片段显示了如何使用copybit(x,y)来实现nonnegative(),而简单的sign(x)无法做到这一点。

Python应该支持对IEEE 754 / C99数学函数的更好支持。这将添加一个signbit(x)函数,该函数将在浮点数的情况下实现您想要的功能。它不适用于整数或复数,更不用说字符串了,并且没有您要查找的名称。

您问“为什么”,答案是“ sign(x)没用”。您断言它很有用。然而,您的评论表明,您不足以提出该断言,这意味着您必须提供令人信服的证据来证明其必要性。说NumPy实现了它还不足以令人信服。您将需要展示如何使用符号函数改进现有代码的案例。

而且它超出了StackOverflow的范围。将其改为Python列表之一。


5
好吧,我不希望这会让您高兴,但是Python 3既没有,cmp()也没有sign():-)
Antoine P.

4
编写可以与IEEE 754一起正常工作的好sign()函数并非易事。即使我没有在问题中阐述这一点,也最好将其包含在语言中,而不是将其遗漏掉
Davide

2
您对“如果您希望排序稳定”的评论意味着您也不知道稳定排序的工作原理。您关于copysign和sign的声明是等效的,这表明在发布本文之前,您对IEEE 754数学的了解不多。Python是否应该在内核中实现所有754个数学函数?对于非C99编译器应该怎么做?非754平台?“正负”和“非正”也是有用的功能。Python还应该包括那些吗?abs(x)服从x .__ abs __(),所以sign(x)服从x .__ sign __()吗?几乎没有需求,所以为什么要把它牢牢地牢牢地牢牢地牢牢扎住?
2009年

2
当我在2.7中尝试时,math.copysign(1,float(“-nan”))返回1.0而不是-1.0
dansalmo

34

另一个衬里的sign()

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

如果您希望它在x = 0时返回0:

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

1
为什么?问题本身承认与cmp(x, 0)等同sign,并且lambda x: cmp(x, 0)比您的建议更易读。
ToolmakerSteve

1
确实,我错了。我假设已将'cmp'指定为返回-1,0,+ 1,但是我看到该规范不能保证这一点。
ToolmakerSteve

美丽。回答开始的问题:python int或float到-1,0,1吗?
2015年

1
使用列表代替有什么好处-1 if x < 0 else 1吗?
Mateen Ulhaq

6
sign = lambda x: -1 if x < 0 else 1快15% 。与相同sign = lambda x: x and (-1 if x < 0 else 1)
Mateen Ulhaq '18年

26

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

13

仅符合Wikipedia定义的正确答案

维基百科上定义如下:

标志定义

因此,

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的正确结果(请参见其他错误答案的注释)。

请注意,零(0)既不是正数也不是负数


1
这个答案说明了python多么简洁但功能强大。
NelsonGon

1
Quibble:该代码未实现WP定义,而是在末尾用默认子句替换了中间子句。虽然这对于处理像nan这样的非实数是必需的,但错误地将其显示为直接在WP语句之后(“因此”)。
尔根·斯特罗贝尔

1
@JürgenStrobel我很清楚您的意思,并且我也一直在考虑这个问题。我现在扩展了正确形式主义的答案,同时保留了大多数用例的简化版本。
Serge Stroobandt

10

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

获得定期浮动。


10

尝试运行此命令,其中x是任意数字

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

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


好主意,但我想你的意思是:int_sign = int(x> 0)
-int

我的意思是:int_sign = lambda x:(x> 0)-(x <0)
yucer

1
@yucer不,他确实是指强制转换为布尔型(无论如何,它都是int的子类),因为他将理论上的联系与解释联系在一起。
沃尔特·特罗斯

这个结构的唯一的缺点是,争论出现了两次,这是罚款,只有当它是一个变量
瓦尔特Tross

5

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

math.copysign()也可以独立使用。

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

http://bugs.python.org/issue1640上关于省略的开发参数math.sign()很奇怪,因为:

  • 没有单独的 -NaN
  • sign(nan) == nan 不用担心(如exp(nan)
  • sign(-0.0) == sign(0.0) == 0 不用担心
  • sign(-inf) == -1 不用担心

-因为它在numpy中


4

在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(),因为比较运算符不需要返回booleans

为了解决这种可能性,必须将比较结果强制为布尔值:

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

这适用于所有type完全有序的(包括特殊值,例如NaNinfinies)。


0

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

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

4
需要指出的是,x / abs(x)比仅通过链接if/else来检查变量在0的哪一侧上花费的时间要长一点,或者为此使用粘糊糊的令人满意的值return (x > 0) - (x < 0)减去bool值并返回int

1
Python的对待True,并False10,你完全可以做到这一点,无论是获取10-1def sign(x): return (x > 0) - (x < 0)不会返回a bool,它会返回一个int-如果通过,0您将0返回


-8

之所以没有包含“符号”,是因为如果我们在内置函数列表中包含所有有用的单行代码,那么使用Python就会变得不那么容易和实用。如果您经常使用此功能,那么为什么不自己考虑呢?这样做并不难,甚至乏味。


6
好吧,只有在abs()也被排除在外的情况下,我才买这个。sign()abs()并且经常一起使用,sign()是两者(IMO)中最有用的,并且实现起来都不是一件难事或繁琐的事情(即使它容易出错,请看此答案是如何弄错的:stackoverflow.com/questions/1986152/…
Davide

1
问题在于,其sign()自身的数值结果很少有用。大多数时候,您要做的是根据变量是正数还是负数来采用不同的代码路径,在这种情况下,显式地编写条件更具可读性。
Antoine P.

3
abs()的使用比sign()的使用要频繁得多。我向您介绍了NumPy跟踪器,该跟踪器显示了sign()的实现难度。符号(-3 + 4j)应该是什么?而abs(-3 + 4j)是5.0。您断言sign()和abs()经常一起出现。C标准库没有“符号”功能,那么您在哪里获取数据?
2009年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.