Pourquoi Python n'a-t-il pas de fonction de signe?

241

Je ne comprends pas pourquoi Python n'a pas de signfonction. Il a un absintégré (que je considère comme signla sœur de), mais non sign.

En python 2.6, il y a même une copysignfonction (en mathématiques ), mais aucun signe. Pourquoi prendre la peine d'écrire un copysign(x,y)alors que vous pourriez simplement écrire un sign, puis obtenir copysigndirectement le abs(x) * sign(y)? Ce dernier serait beaucoup plus clair: x avec le signe de y, alors qu'avec copysign il faut se rappeler si c'est x avec le signe de y ou y avec le signe de x!

Évidemment, sign(x)cela ne fournit rien de plus que cela cmp(x,0), mais ce serait beaucoup plus lisible que cela aussi (et pour un langage très lisible comme python, cela aurait été un gros plus).

Si j'étais un concepteur de python, j'aurais été dans l'autre sens: pas de cmpbuiltin, mais a sign. Lorsque vous en avez besoin cmp(x,y), vous pouvez simplement faire un sign(x-y)(ou, encore mieux pour les choses non numériques, juste un x> y - bien sûr, cela aurait dû nécessiter l' sortedacceptation d'un booléen au lieu d'un comparateur entier). Ce serait aussi plus clair: positif quand x>y(alors qu'avec cmpvous il faut se souvenir de la convention positive quand la première est plus grosse , mais ça pourrait être l'inverse). Bien sûr, cmpcela a du sens en soi pour d'autres raisons (par exemple lors du tri de choses non numériques, ou si vous voulez que le tri soit stable, ce qui n'est pas possible en utilisant simplement un booléen)

Donc, la question est: pourquoi le ou les concepteurs Python ont-ils décidé de laisser la signfonction hors du langage? Pourquoi diable dérange-t-il copysignet non pas son parent sign?

Suis-je en train de manquer quelque chose?

EDIT - après le commentaire de Peter Hansen. Assez juste que vous ne l'avez pas utilisé, mais vous n'avez pas dit pourquoi vous utilisez python. En 7 ans que j'utilise le python, j'en ai eu besoin d'innombrables fois, et la dernière est la paille qui a cassé le dos du chameau!

Oui, vous pouvez passer le cmp, mais 90% des fois où j'ai eu besoin de le passer, c'était dans un idiome comme lambda x,y: cmp(score(x),score(y))ça aurait très bien fonctionné avec le signe.

Enfin, j'espère que vous conviendrez que ce signserait plus utile que copysign, donc même si j'ai acheté votre point de vue, pourquoi vous soucier de définir cela en mathématiques, au lieu de signer? Comment le copysign peut-il être aussi utile que signer?

Davide
la source
33
@dmazzoni: cet argument ne fonctionnerait-il pas pour toutes les questions sur ce site? fermez simplement stackoverflow et posez toutes les questions à la liste de diffusion du développeur ou de l'utilisateur concerné!
Davide
43
L'endroit approprié pour une question est n'importe quel endroit où il est probable qu'on y réponde. Ainsi, stackoverflow est un endroit approprié.
Stefano Borini
23
-1: @Davide: les questions "pourquoi" et "pourquoi pas" ne peuvent généralement pas être répondues ici. Étant donné que la plupart des principes du développement Python ne répondent pas aux questions ici, vous obtiendrez rarement (voire jamais) une réponse à une question «pourquoi» ou «pourquoi pas». De plus, vous n'avez pas de problème à résoudre. On dirait que vous avez une diatribe. Si vous avez un problème ("Comment contourner le manque de signe dans cet exemple ..."), c'est raisonnable. "Pourquoi pas" n'est pas judicieux pour ce lieu.
S.Lott
31
La question peut être un peu émotionnelle, mais je ne pense pas que ce soit une mauvaise question. Je suis sûr que beaucoup de gens ont recherché une fonction de signe intégrée, donc il peut être curieux de savoir pourquoi il n'y en a pas.
FogleBird
17
C'est une question parfaitement objective: «Pourquoi» Python n'a pas de fonctionnalité donnée est une requête légitime sur l'histoire de la conception du langage qui peut être répondue en reliant à la discussion appropriée de python-dev ou d'autres forums (parfois des articles de blog) où Python les développeurs principaux arrivent à hacher un sujet. Ayant moi-même essayé Google pour des morceaux d'histoire en python-dev auparavant, je peux comprendre pourquoi un nouveau venu dans la langue pourrait se retrouver dans une impasse et venir demander ici dans l'espoir d'une personne Python plus expérimentée répondant!
Brandon Rhodes

Réponses:

228

ÉDITER:

En effet, il y avait un correctif qui était inclus sign()dans les mathématiques , mais il n'a pas été accepté, car ils n'étaient pas d'accord sur ce qu'il devrait retourner dans tous les cas de bord (+/- 0, +/- nan, etc.)

Ils ont donc décidé de n'implémenter que le copysign, qui (bien que plus détaillé) peut être utilisé pour déléguer à l'utilisateur final le comportement souhaité pour les cas limites - ce qui peut parfois nécessiter l'appel àcmp(x,0) .


Je ne sais pas pourquoi ce n'est pas intégré, mais j'ai quelques réflexions.

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

Plus important encore, copysignc'est un surensemble de sign! Appeler copysignavec x = 1 équivaut à une signfonction. Vous pouvez donc simplement l'utiliser copysignet l' oublier .

>>> math.copysign(1, -4)
-1.0
>>> math.copysign(1, 3)
1.0

Si vous en avez marre de passer deux arguments entiers, vous pouvez l'implémenter de signcette façon, et il sera toujours compatible avec les trucs IEEE mentionnés par d'autres:

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

Deuxièmement, généralement lorsque vous voulez le signe de quelque chose, vous finissez par le multiplier par une autre valeur. Et bien sûr, c'est essentiellement ce qui copysignfait.

Donc, au lieu de:

s = sign(a)
b = b * s

Vous pouvez simplement faire:

b = copysign(b, a)

Et oui, je suis surpris que vous utilisiez Python depuis 7 ans et pensez que cela cmppourrait être si facilement supprimé et remplacé par sign! Vous n'avez jamais implémenté une classe avec une __cmp__méthode? Vous n'avez jamais appelé cmpet spécifié une fonction de comparaison personnalisée?

En résumé, je me suis retrouvé à vouloir une signfonction aussi, mais copysignavec le premier argument étant 1 fonctionnera très bien. Je ne suis pas d'accord que ce signserait plus utile que copysign, car j'ai montré qu'il ne s'agit que d'un sous-ensemble de la même fonctionnalité.

FogleBird
la source
35
Utiliser [int(copysign(1, zero)) for zero in (0, 0.0, -0.0)]donne [1, 1, -1]. Cela aurait dû être [0, 0, 0]selon en.wikipedia.org/wiki/Sign_function
user238424
12
@Andrew - L'ordre d'appel de @ user238424 est correct. copysign(a,b)renvoie a avec le signe de b - b est l'entrée variable, a est la valeur à normaliser avec le signe de b. Dans ce cas, le commentateur illustre que le copysign (1, x) en remplacement du signe (x) échoue, car il renvoie 1 pour x = 0, tandis que le signe (0) serait évalué à 0.
PaulMcG
7
Les flotteurs contiennent un «signe» distinct de la «valeur»; -0,0 est un nombre négatif, même si cela semble une erreur de mise en œuvre. Une simple utilisation cmp()donnera les résultats souhaités, probablement pour presque tous les cas, n'importe qui se souciera de: [cmp(zero, 0) for zero in (0, 0.0, -0.0, -4, 5)]==> [0, 0, 0, -1, 1].
pythonlarry
11
s = sign(a) b = b * sn'est pas équivalent à b = copysign(b, a)! Il ne considère pas le signe de b. Par exemple, si a=b=-1le premier code renvoie 1 tandis que le second renvoie -1
Johannes Jendersie
14
En voyant la définition de remplacement de faux signe (), le faux équivalent pour la multiplication avec le signe (a), la fausse explication de la motivation de la copie et le remplacement correct "cmp (x, 0)" étant déjà mentionnés dans la question - il y a pas beaucoup d'informations et je ne comprends pas pourquoi c'est la réponse "acceptée" avec autant de votes.?
kxr
60

"copysign" est défini par IEEE 754 et fait partie de la spécification C99. C'est pourquoi c'est en Python. La fonction ne peut pas être implémentée dans son intégralité par le signe abs (x) * (y) en raison de la façon dont elle est censée gérer les valeurs 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
>>> 

Cela fait de copysign () une fonction plus utile que sign ().

Quant aux raisons spécifiques pour lesquelles le signbit (x) de l'IEEE n'est pas disponible en Python standard, je ne sais pas. Je peux faire des hypothèses, mais ce serait deviner.

Le module mathématique lui-même utilise copysign (1, x) comme moyen de vérifier si x est négatif ou non négatif. Pour la plupart des cas, traiter des fonctions mathématiques semble plus utile que d'avoir un signe (x) qui renvoie 1, 0 ou -1 car il y a un cas de moins à considérer. Par exemple, ce qui suit provient du module mathématique de Python:

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

Là, vous pouvez clairement voir que copysign () est une fonction plus efficace qu'une fonction de signe () à trois valeurs.

Tu as écrit:

Si j'étais un concepteur de python, je serais l'inverse: pas de cmp () intégré, mais un signe ()

Cela signifie que vous ne savez pas que cmp () est utilisé pour des choses autres que les nombres. cmp ("This", "That") ne peut pas être implémenté avec une fonction sign ().

Modifier pour rassembler mes réponses supplémentaires ailleurs :

Vous fondez vos justifications sur la façon dont abs () et sign () sont souvent vus ensemble. Comme la bibliothèque standard C ne contient aucune fonction "signe (x)", je ne sais pas comment vous justifiez vos vues. Il y a un abs (int) et fabs (double) et fabsf (float) et fabsl (long) mais aucune mention de signe. Il existe "copysign ()" et "signbit ()" mais ceux-ci ne s'appliquent qu'aux numéros IEEE 754.

Avec des nombres complexes, que signifierait (-3 + 4j) en Python, devait-il être implémenté? abs (-3 + 4j) renvoie 5,0. C'est un exemple clair de la façon dont abs () peut être utilisé dans des endroits où sign () n'a aucun sens.

Supposons que le signe (x) ait été ajouté à Python, en complément de abs (x). Si 'x' est une instance d'une classe définie par l'utilisateur qui implémente la méthode __abs __ (self) alors abs (x) appellera x .__ abs __ (). Pour fonctionner correctement, pour gérer abs (x) de la même manière, Python devra gagner un emplacement de signe (x).

Ceci est excessif pour une fonction relativement inutile. En outre, pourquoi le signe (x) devrait-il exister et le non négatif (x) et le non positif (x) n'existeraient pas? Mon extrait de l'implémentation du module mathématique de Python montre comment copybit (x, y) peut être utilisé pour implémenter nonnegative (), ce qu'un simple signe (x) ne peut pas faire.

Python devrait prendre en charge une meilleure prise en charge de la fonction mathématique IEEE 754 / C99. Cela ajouterait une fonction signbit (x), qui ferait ce que vous voulez dans le cas des flottants. Cela ne fonctionnerait pas pour les nombres entiers ou complexes, encore moins pour les chaînes, et il n'aurait pas le nom que vous recherchez.

Vous demandez "pourquoi", et la réponse est "le signe (x) n'est pas utile". Vous affirmez que c'est utile. Pourtant, vos commentaires montrent que vous n'en savez pas assez pour être en mesure de faire cette affirmation, ce qui signifie que vous devrez fournir des preuves convaincantes de son besoin. Dire que NumPy l'implémente n'est pas assez convaincant. Vous auriez besoin de montrer des cas d'amélioration du code existant avec une fonction de signe.

Et qu'il sort du cadre de StackOverflow. Prenez-le à la place dans l'une des listes Python.

Andrew Dalke
la source
5
Eh bien, je ne sais pas si cela vous rendra heureux, mais Python 3 n'a ni cmp()ni sign():-)
Antoine P.
4
écrire une bonne fonction sign () qui fonctionnerait correctement avec IEEE 754 n'est pas anodin. Ce serait un bon point pour l'inclure dans la langue, plutôt que de le laisser de côté, même si je n'ai pas développé ce point dans la question
Davide
2
Votre commentaire sur la façon dont "si vous voulez que le tri soit stable" signifie que vous ne savez pas non plus comment fonctionne le tri stable. Votre déclaration selon laquelle le copysign et le signe sont équivalents montre que vous ne saviez pas grand-chose sur les mathématiques IEEE 754 avant ce post. Python doit-il implémenter toutes les 754 fonctions mathématiques de base? Que devrait-il faire pour les compilateurs non C99? Plateformes non 754? "isnonnegative" et "isnonpositive" sont également des fonctions utiles. Python devrait-il également les inclure? abs (x) passe à x .__ abs __ (), donc le signe (x) doit-il se reporter au x .__ signe __ ()? Il y a peu de demande ou de besoin, alors pourquoi devrait-il être coincé dans le cœur?
Andrew Dalke
2
math.copysign (1, float ("- nan")) renvoie 1.0 au lieu de -1.0 lorsque je l'essaye en 2.7
dansalmo
34

Un autre liner pour signe ()

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

Si vous voulez qu'il renvoie 0 pour x = 0:

sign = lambda x: x and (1, -1)[x<0]
dansalmo
la source
1
Pourquoi? La question elle-même reconnaît que cmp(x, 0)c'est équivalent signet lambda x: cmp(x, 0)plus lisible que ce que vous suggérez.
ToolmakerSteve
1
En effet, j'avais tort. J'avais supposé que 'cmp' était spécifié pour renvoyer -1,0, + 1, mais je vois que la spécification ne garantit pas cela.
ToolmakerSteve
Belle. Répond à la question posée: python int ou float à -1, 0, 1?
scharfmn
1
Y a-t-il un avantage à utiliser des listes au lieu de -1 if x < 0 else 1?
Mateen Ulhaq
6
sign = lambda x: -1 if x < 0 else 1est 15% plus rapide . Même chose avec sign = lambda x: x and (-1 if x < 0 else 1).
Mateen Ulhaq
26

Depuis qu'il cmpa été supprimé , vous pouvez obtenir les mêmes fonctionnalités avec

def cmp(a, b):
    return (a > b) - (a < b)

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

Cela fonctionne pour float, intet même Fraction. Dans le cas de float, l'avis sign(float("nan"))est nul.

Python n'exige pas que les comparaisons renvoient un booléen, et donc contraindre les comparaisons à bool () protège contre une implémentation autorisée, mais rare:

def sign(a):
    return bool(a > 0) - bool(a < 0)
AllenT
la source
13

Seule bonne réponse conforme à la définition de Wikipedia

La définition sur Wikipédia lit comme suit:

définition de signe

Par conséquent,

sign = lambda x: -1 if x < 0 else (1 if x > 0 else (0 if x == 0 else NaN))

Qui, à toutes fins utiles, peut être simplifié pour:

sign = lambda x: -1 if x < 0 else (1 if x > 0 else 0)

Cette définition de fonction s'exécute rapidement et donne des résultats corrects garantis pour 0, 0.0, -0.0, -4 et 5 (voir les commentaires des autres réponses incorrectes).

Notez que zéro (0) n'est ni positif ni négatif .

Serge Stroobandt
la source
1
Cette réponse illustre à quel point le python peut être succinct mais puissant.
NelsonGon
1
Quibble: Le code n'implémente pas la définition WP, il remplace la clause du milieu par une clause par défaut à la fin. Bien que cela soit nécessaire pour gérer des nombres non réels comme nan, il est affiché à tort comme suivant directement la déclaration WP («d'où»).
Jürgen Strobel
1
@ JürgenStrobel Je sais exactement ce que vous voulez dire et j'envisage également depuis longtemps ce problème. J'ai étendu la réponse maintenant pour un formalisme correct, tout en conservant la version simplifiée pour la plupart des cas d'utilisation.
Serge Stroobandt
10

numpy a une fonction de signe, et vous donne également un bonus d'autres fonctions. Alors:

import numpy as np
x = np.sign(y)

Faites juste attention à ce que le résultat soit un numpy.float64:

>>> type(np.sign(1.0))
<type 'numpy.float64'>

Pour des choses comme json, cela importe, car json ne sait pas comment sérialiser les types numpy.float64. Dans ce cas, vous pourriez faire:

float(np.sign(y))

pour obtenir un flotteur régulier.

Luca
la source
10

Essayez d'exécuter ceci, où x est un nombre

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

La coercition à bool () gère la possibilité que l'opérateur de comparaison ne retourne pas de booléen.

Eric Song
la source
Bonne idée, mais je pense que vous voulez dire: int_sign = int (x> 0) - int (x <0)
yucer
Je veux dire: int_sign = lambda x: (x> 0) - (x <0)
yucer
1
@yucer non, il voulait vraiment dire que le casting était booléen (qui est de toute façon une sous-classe d'int), à cause de la possibilité théorique dont il a donné le lien vers l'explication.
Walter Tross
Le seul inconvénient de cette construction est que l'argument apparaît deux fois, ce qui est bien uniquement s'il s'agit d'une seule variable
Walter Tross
5

Oui, une sign()fonction correcte devrait être au moins dans le module mathématique - comme dans numpy. Parce qu'on en a souvent besoin pour du code orienté mathématique.

Mais math.copysign()est également utile indépendamment.

cmp()et obj.__cmp__()... ont généralement une grande importance indépendamment. Pas seulement pour le code orienté mathématiques. Pensez à comparer / trier les tuples, les objets de date, ...

Les arguments de développement sur http://bugs.python.org/issue1640 concernant l'omission de math.sign()sont étranges, car:

  • Il n'y a pas de -NaN
  • sign(nan) == nan sans souci (comme exp(nan))
  • sign(-0.0) == sign(0.0) == 0 sans souci
  • sign(-inf) == -1 sans souci

- comme c'est en numpy

kxr
la source
4

En Python 2, cmp()renvoie un entier: il n'est pas nécessaire que le résultat soit -1, 0 ou 1, ce sign(x)n'est donc pas la même chose que cmp(x,0).

En Python 3, cmp()a été supprimé au profit d'une comparaison riche. Pour cmp(), Python 3 suggère ceci :

def cmp(a, b):
    return (a > b) - (a < b)

ce qui est bien pour cmp (), mais encore une fois ne peut pas être utilisé pour sign () car les opérateurs de comparaison n'ont pas besoin de renvoyer de booléens .

Pour faire face à cette possibilité, les résultats de la comparaison doivent être contraints aux booléens:

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

Cela fonctionne pour tout typece qui est totalement ordonné (y compris les valeurs spéciales comme NaNou infinis).

AllenT
la source
0

Vous n'en avez pas besoin, vous pouvez simplement utiliser:

If not number == 0:
    sig = number/abs(number)
else:
    sig = 0
inetphantom
la source
4
Il x / abs(x)if/elsereturn (x > 0) - (x < 0)boolint
1
Traite Python Trueet Falseque 1et 0, vous pouvez absolument faire cela et soit 1, 0ou -1. def sign(x): return (x > 0) - (x < 0)ne reviendra pas un bool, ça va revenir un int- si vous passez 0vous obtenez en 0arrière
0

Ce n'est pas le cas.

La meilleure façon de résoudre ce problème est:

sign = lambda x: bool(x > 0) - bool(x < 0)
Michel de Ruiter
la source
-8

La raison pour laquelle "signe" n'est pas inclus est que si nous incluions toutes les lignes utiles dans la liste des fonctions intégrées, Python ne serait plus facile et pratique à utiliser. Si vous utilisez cette fonction si souvent, alors pourquoi ne pas la prendre en compte vous-même? Ce n'est pas comme si c'était à distance difficile ou même fastidieux de le faire.

Antoine P.
la source
6
Eh bien, je n'achèterais cela que s'il abs()était également omis. sign()et abs()sont souvent utilisés ensemble, sign()est le plus utile des deux (IMO), et aucun n'est à distance difficile ou fastidieux à mettre en œuvre (même s'il est sujet aux erreurs, voyez comment cette réponse se trompe: stackoverflow.com/questions/1986152/… )
Davide
1
Le fait est que le résultat numérique en sign()lui-même est rarement utile. La plupart du temps, vous prenez des chemins de code différents selon que la variable est positive ou négative, et dans ce cas, il est plus lisible d'écrire la condition explicitement.
Antoine P.
3
abs () est utilisé beaucoup plus souvent qu'un signe () ne le serait. Et je vous ai montré le tracker NumPy qui montre à quel point sign () peut être difficile à implémenter. Que doit signifier (-3 + 4j)? Alors que abs (-3 + 4j) est 5,0. Vous affirmez que signe () et abs () sont souvent vus ensemble. La bibliothèque standard C n'a pas de fonction «signe», alors où obtenez-vous vos données?
Andrew Dalke