web-dev-qa-db-fra.com

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

Je ne comprends pas pourquoi Python n'a pas de fonction sign. Il a une variable abs (que je considère comme la sœur de sign), mais pas sign.

En python 2.6, il existe même une fonction copysign (dans math ), mais aucun signe. Pourquoi se donner la peine d'écrire une copysign(x,y) alors que vous pouvez simplement écrire une sign et obtenir ensuite la copysign directement à partir de abs(x) * sign(y)? Ce dernier serait beaucoup plus clair: x avec le signe de y, alors qu'avec copysign, vous devez vous rappeler si c'est x avec le signe de y ou y avec le signe de x!

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

Si j'étais un concepteur de python, je serais dans l'autre sens: pas de variable cmp, mais une variable sign. Lorsque vous avez besoin de cmp(x,y), vous pouvez simplement faire un sign(x-y) (ou, mieux encore, pour les éléments non numériques, juste un x> y - bien sûr, cela aurait dû impliquer que sorted accepte un booléen au lieu d'un comparateur entier). Cela serait également plus clair: positif lorsque x>y (alors qu'avec cmp, vous devez vous rappeler de la convention positive lorsque le premier est plus grand, mais il pourrait en être autrement). Bien entendu, cmp a un sens en soi pour d'autres raisons (par exemple, lors du tri d'éléments non numériques, ou si vous souhaitez que le tri soit stable, ce qui n'est pas possible avec un booléen).

La question est donc de savoir pourquoi le ou les concepteurs Python ont décidé de ne pas utiliser la fonction sign dans le langage. Pourquoi diable se préoccuper de copysign et non de son parent sign?

Est-ce que je manque quelque chose?

EDIT - après le commentaire de Peter Hansen. Assez juste pour que vous ne l'utilisiez pas, mais vous ne disiez pas pourquoi vous utilisiez python. En 7 ans d'utilisation de python, j'en ai eu besoin d'innombrables fois, et la dernière est la goutte d'eau qui a fait déborder le vase!

Oui, vous pouvez faire circuler cmp, mais 90% des fois que j’avais besoin de le faire était dans un idiome comme lambda x,y: cmp(score(x),score(y)) qui aurait très bien fonctionné avec signe.

Enfin, j'espère que vous conviendrez que sign serait plus utile que copysign, donc même si j'achète votre point de vue, pourquoi se soucier de définir cela en maths plutôt qu'en signe? Comment copysign peut-il être aussi utile qu'un signe?

185
Davide

EDIT:

En effet, il y avait un patch qui incluait sign() dans math , mais cela n'a pas été accepté, car ils n'étaient pas d'accord sur ce qu'il devrait renvoyer dans tous les Edge cases (+/- 0, +/- nan, etc.)

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


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

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

Plus important encore, copysign est un sur-ensemble de sign! L'appel de copysign avec x = 1 est identique à une fonction sign. Donc, vous pouvez simplement utiliser copysign et 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 implémenter sign de cette façon, et il sera toujours compatible avec les éléments 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 que fait copysign.

Donc, au lieu de:

s = sign(a)
b = b * s

Vous pouvez juste faire:

b = copysign(b, a)

Et oui, je suis surpris que vous utilisiez Python pendant 7 ans et que vous pensiez que cmp pourrait être si facilement supprimé et remplacé par sign! Avez-vous jamais implémenté une classe avec une méthode __cmp__? Avez-vous jamais appelé cmp et spécifié une fonction de comparaison personnalisée?

En résumé, je me suis retrouvé à vouloir aussi une fonction sign, mais copysign avec le premier argument étant 1 fonctionnera parfaitement. Je ne suis pas d'accord sur le fait que sign serait plus utile que copysign, car j'ai déjà montré qu'il ne s'agissait que d'un sous-ensemble des mêmes fonctionnalités.

198
FogleBird

"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 intégralement par abs (x) * sign (y) en raison de la manière dont elle est supposé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 ().

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

Le module mathématique lui-même utilise Signbit (1, x) comme moyen de vérifier si x est négatif ou non négatif. Dans la plupart des cas, traiter avec des fonctions mathématiques semble plus utile que d’avoir un signe (x) qui renvoie 1, 0 ou -1 car il ya un cas de moins à prendre en compte. 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 dans l'autre sens: pas de cmp () intégré, mais un signe ()

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

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

Vous basez vos justifications sur la manière dont abs () et le signe () sont souvent vus ensemble. Comme la bibliothèque standard C ne contient pas de fonction 'signe (x)', je ne sais pas comment vous justifiez vos opinions. Il y a un abs (int) et des 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 signerait (-3 + 4j) le retour en Python, si cela devait être implémenté? abs (-3 + 4j) retour 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) soit 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 __ (). Afin de fonctionner correctement, pour gérer abs (x) de la même manière, Python devra alors obtenir un emplacement sign (x).

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

Python devrait supporter mieux la fonction mathématique IEEE 754/C99. Cela ajouterait une fonction signbit (x), qui ferait ce que vous voulez dans le cas des floats. Cela ne fonctionnerait pas pour des nombres entiers ou complexes, et encore moins des 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 pouvoir faire cette affirmation, ce qui signifie que vous devrez prouver de manière convaincante que ce besoin existe. Dire que NumPy l'implémente n'est pas assez convaincant. Vous aurez besoin de montrer comment le code existant pourrait être amélioré avec une fonction de signe.

Et que cela ne relève pas de StackOverflow. Prenez plutôt l'une des listes Python.

50
Andrew Dalke

Une autre doublure pour le 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]
30
dansalmo

Puisque cmp a été supprimé , vous pouvez obtenir la même fonctionnalité avec

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

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

Cela fonctionne pour float, int et même Fraction. Dans le cas de float, notez que sign(float("nan")) est à zéro.

Python n'exige pas que les comparaisons renvoient un booléen. Par conséquent, contraindre les comparaisons à bool () protège contre les implémentations autorisées mais peu communes:

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

Essayez de lancer ceci, où x est un nombre quelconque

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

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

7
Eric Song

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

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

Faites juste attention que le résultat est un numpy.float64: 

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

Cela est important pour des choses comme json, car json ne sait pas sérialiser les types numpy.float64. Dans ce cas, vous pourriez faire: 

float(np.sign(y))

pour obtenir un char régulier. 

5
Luca

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

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

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

Les arguments de dev sur http://bugs.python.org/issue1640 concernant l'omission de math.sign() sont impairs, car:

  • Il n'y a pas de -NaN séparé
  • 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

3
kxr

Conforme à la définition de Wikipedia

La définition sur Wikipedia se lit comme suit:

sign definition

Par conséquent,

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

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

2
Serge Stroobandt

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

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

Dans Python 2, cmp () renvoie un entier: il n'est pas nécessaire que le résultat soit égal à -1, 0 ou 1, de sorte que le signe (x) n'est pas identique à cmp (x, 0).

Dans Python 3, cmp () a été supprimé en faveur de la comparaison riche. Pour cmp (), Python 3 suggère ( https://docs.python.org/3/whatsnew/3.0.html ):

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

ce qui est correct pour cmp (), mais encore une fois ne peut pas être utilisé pour sign () car les opérateurs de comparaison ne doivent pas renvoyer de booléens ( https://docs.python.org/3/reference/datamodel.html#object. lt ).

Pour traiter cette possibilité, les résultats de la comparaison doivent être forcés à des booléens:

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

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

0
AllenT