306 votes

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

Je ne comprends pas pourquoi Python ne dispose pas d'une fonction sign fonction. Il dispose d'un abs builtin (que je considère comme sign ), mais pas de sign .

Dans python 2.6, il existe même une fonction copysign (en mathématiques ), mais aucun signe. Pourquoi prendre la peine d'écrire un copysign(x,y) alors que vous pourriez simplement écrire un sign et ensuite obtenir le copysign directement 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 !

Évidemment 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 python, cela aurait été un grand plus).

Si j'étais un concepteur de python, je serais dans l'autre sens : non cmp mais un sign . Lorsque vous avez besoin cmp(x,y) vous pourriez 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 sorted en acceptant un comparateur booléen au lieu d'un comparateur entier). Cela serait également plus clair : positif lorsque x>y (alors qu'avec cmp vous devez vous souvenir de la convention positive lorsque le primero es plus grand mais cela pourrait être l'inverse). Bien entendu cmp a un 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).

La question est donc la suivante : pourquoi le ou les concepteurs de Python ont-ils décidé de quitter l'entreprise ? sign fonction hors de la langue ? Pourquoi s'embêter avec copysign et non son parent sign ?

Est-ce que j'ai manqué quelque chose ?

EDIT - après le commentaire de Peter Hansen. C'est normal que vous ne l'utilisiez pas, mais vous n'avez pas dit à quoi vous utilisez 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 passer cmp, mais 90 % des fois où j'ai eu besoin de le faire, c'était dans un idiome tel que lambda x,y: cmp(score(x),score(y)) qui aurait très bien fonctionné avec le signe.

Enfin, j'espère que vous êtes d'accord pour dire que sign serait plus utile que copysign Donc, même si j'adhère à votre point de vue, pourquoi s'embêter à définir cela en mathématiques, au lieu du signe ? Comment le signe de copie peut-il être aussi utile que le signe ?

1 votes

"Est-ce que je rate quelque chose ?" Si rien d'autre, le fait d'utiliser sign(x-y) exige qu'une expression soit évaluée, ce qui signifie que vous ne pouvez pas la passer en tant que callback à moins de l'envelopper dans un "partial" d'abord. Avec cmp() tel qu'il est défini, vous passez simplement une référence à cmp autour. Aussi "incroyablement utile" ? J'en doute. J'en ai eu besoin une fois en 10 ans d'écriture en Python. Si c'était vraiment si utile, je pense que ce serait là...

43 votes

@dmazzoni : cet argument ne fonctionnerait-il pas pour toutes les questions sur ce site ? il suffit de fermer stackoverflow et de poser toutes les questions à la liste de diffusion pertinente des développeurs ou des utilisateurs !

2 votes

@davide : Premièrement je suis d'accord, une fonction signum est commune et utile. Deuxièmement, je suis d'accord avec dmazzoni pour dire que votre question est mieux posée sur python-dev. C'est une chose de demander comment utiliser une compréhension de liste pour faire xyz : nous pouvons faire un bon travail pour répondre à cela ici. Mais demander pourquoi Guido Van Rossum a laissé de côté signum est difficile à répondre pour quiconque ici.

284voto

FogleBird Points 23405

EDIT :

En effet, il y avait un patch qui comprenait sign() en mathématiques mais il n'a pas été accepté, parce qu'ils n'étaient pas d'accord sur ce qu'il devrait retourner dans tous les cas limites (+/-0, +/-nan, etc)

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


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

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

Le plus important, copysign est un sur-ensemble de sign ! Appel à copysign avec x=1 est identique à un sign fonction. Vous pouvez donc utiliser copysign y oubliez-le .

>>> 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 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 en gros ce que copysign fait.

Donc, au lieu de :

s = sign(a)
b = b * s

Tu peux juste le faire :

b = copysign(b, a)

Et oui, je suis surpris que tu utilises Python depuis 7 ans et que tu penses cmp pourrait être si facilement retiré et remplacé par sign ! N'avez-vous jamais implémenté une classe avec un __cmp__ méthode ? N'avez-vous jamais appelé cmp et spécifié une fonction de comparaison personnalisée ?

En résumé, je me suis retrouvé à vouloir une sign également, mais copysign avec le premier argument étant 1 fonctionnera très bien. Je ne suis pas d'accord avec le fait que sign serait plus utile que copysign car j'ai montré que ce n'est qu'un sous-ensemble de la même fonctionnalité.

43 votes

Utilisation de [int(copysign(1, zero)) for zero in (0, 0.0, -0.0)] donne [1, 1, -1] . Cela aurait dû être [0, 0, 0] en fonction de fr.wikipedia.org/wiki/Sign_function

1 votes

Vous vous trompez dans l'ordre. [int(copysign(zero, 1)) for zero in (0, 0.0, -0.0)] donne le [0,0,0] que vous cherchez.

13 votes

@Andrew - L'ordre d'appel de @user238424 est correct. copysign(a,b) retourne 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 copysign(1,x) en tant que remplacement de sign(x) échoue, puisqu'il retourne 1 pour x=0, alors que sign(0) serait évalué à 0.

70voto

Andrew Dalke Points 7607

Le terme "copysign" est défini par la norme IEEE 754 et fait partie de la spécification C99. C'est pourquoi elle est dans Python. La fonction ne peut pas être implémentée entièrement par abs(x) * sign(y) à cause 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 IEEE n'est pas disponible dans Python standard, je ne sais pas. Je peux faire des hypothèses, mais ce serait une supposition.

Le module mathématique lui-même utilise copysign(1, x) pour vérifier si x est négatif ou non. Pour la plupart des cas traitant des fonctions mathématiques, cela semble plus utile que d'avoir un sign(x) qui renvoie 1, 0 ou -1 car il y a un cas de moins à considérer. Par exemple, l'exemple suivant 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);

Vous pouvez clairement voir que la fonction copysign() est plus efficace que la fonction sign() à trois valeurs.

Vous avez écrit :

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

Cela signifie que vous ne savez pas que cmp() est utilisé pour d'autres choses 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 le fait que abs() et sign() sont souvent vus ensemble. Comme la bibliothèque standard du C ne contient aucune fonction 'sign(x)', je ne vois pas comment vous justifiez votre point de vue. Il y a une fonction abs(int) et fabs(double) et fabsf(float) et fabsl(long) mais aucune mention de sign. Il y a "copysign()" et "signbit()" mais ils ne s'appliquent qu'aux nombres IEEE 754.

Avec des nombres complexes, que retournerait sign(-3+4j) en Python, s'il était implémenté ? abs(-3+4j) retournerait 5.0. C'est un exemple clair de la façon dont abs() peut être utilisé là où sign() n'a aucun sens.

Supposons que sign(x) soit ajouté à Python, comme 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, de gérer abs(x) de la même manière, Python devra acquérir une classe __abs__(self). signe (x) fente.

C'est excessif pour une fonction relativement inutile. D'ailleurs, pourquoi sign(x) devrait-il exister et nonnegative(x) et nonpositive(x) ne pas exister ? 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 sign(x) ne peut pas faire.

Python devrait avoir un meilleur support pour la fonction mathématique IEEE 754/C99. Cela permettrait d'ajouter une fonction signbit(x), qui ferait ce que vous voulez dans le cas des flottants. Elle ne fonctionnerait pas pour les entiers ou les nombres complexes, et encore moins pour les chaînes de caractères, et elle n'aurait pas le nom que vous recherchez.

Vous demandez "pourquoi", et la réponse est "sign(x) n'est pas utile". Vous affirmez qu'il est utile. Pourtant, vos commentaires montrent que vous n'en savez pas assez pour pouvoir faire cette affirmation, ce qui signifie que vous devriez montrer des preuves convaincantes de sa nécessité. Dire que NumPy l'implémente n'est pas assez convaincant. Vous devriez montrer des cas où le code existant serait amélioré par une fonction de signe.

Et c'est en dehors de la portée de StackOverflow. Posez plutôt votre question sur l'une des listes Python.

5 votes

Je ne sais pas si cela va vous faire plaisir, mais Python 3 n'a ni l'un ni l'autre cmp() ni sign() :-)

4 votes

Écrire une bonne fonction sign() qui fonctionnerait correctement avec IEEE 754 n'est pas trivial. Ce serait un bon point pour l'inclure dans le langage, plutôt que de le laisser de côté, même si je n'ai pas développé ce point dans la question.

2 votes

Votre commentaire sur le fait que "si vous voulez que le tri soit stable" signifie que vous ne savez pas non plus comment fonctionne un tri stable. Votre déclaration selon laquelle copysign et sign sont équivalents montre que vous ne connaissiez pas grand-chose aux mathématiques IEEE 754 avant ce message. Python devrait-il implémenter toutes les fonctions mathématiques 754 dans le noyau ? Que devrait-il faire pour les compilateurs non-C99 ? Les plateformes non 754 ? "isnonnegative" et "isnonpositive" sont également des fonctions utiles. Python devrait-il également les inclure ? abs(x) renvoie à x.__abs__(), donc sign(x) devrait-il renvoyer à x.__sign__() ? Il y a peu de demande ou de besoin pour cette fonction, alors pourquoi devrait-elle être intégrée au noyau ?

46voto

dansalmo Points 3220

Une autre doublure pour sign()

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]

2 votes

Pourquoi ? La question elle-même reconnaît que cmp(x, 0) est équivalent à sign y lambda x: cmp(x, 0) est plus lisible que ce que vous suggérez.

1 votes

En effet, j'avais tort. J'avais supposé que 'cmp' était spécifié pour retourner -1,0,+1, mais je vois que la spécification ne le garantit pas.

0 votes

Magnifique. Répond à la question posée : python int ou float vers -1, 0, 1 ?

37voto

Jean-Claude Arbaut Points 1246

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

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

Python n'exige pas que les comparaisons renvoient un booléen, et donc le fait de contraindre les comparaisons à bool() protège contre une implémentation permise, mais peu commune :

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

13voto

Luca Points 155

Numpy a une fonction de signe, et vous donne un bonus d'autres fonctions aussi. Donc :

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 est important, car json ne sait pas comment sérialiser les types numpy.float64. Dans ce cas, vous pouvez le faire :

float(np.sign(y))

pour obtenir un flotteur régulier.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X