¿Por qué Python no tiene una función de signo?

241

No puedo entender por qué Python no tiene una signfunción. Tiene un absincorporado (que considero signla hermana), pero nosign .

En python 2.6 hay incluso una copysignfunción (en matemáticas ), pero no hay signo. ¿Por qué molestarse en escribir un copysign(x,y)cuando podría simplemente escribir un signy luego obtener el copysigndirectamente abs(x) * sign(y)? Lo último sería mucho más claro: x con el signo de y, mientras que con copysign debe recordar si es x con el signo de y o y con el signo de x.

Obviamente sign(x)no proporciona nada más quecmp(x,0) , pero sería mucho más legible que esto también (y para un lenguaje muy legible como python, esto habría sido una gran ventaja).

Si yo fuera un diseñador de Python, sería lo contrario: no cmpincorporado, sino a sign. Cuando lo necesite cmp(x,y), podría hacer un sign(x-y)(o, mejor aún para cosas no numéricas, solo una x> y, por supuesto, esto debería haber requerido sortedaceptar un booleano en lugar de un comparador de enteros). Esto también sería más claro: positivo cuando x>y(mientras cmpque hay que recordar la convención positiva cuando la primera es más grande , pero podría ser al revés). Por supuestocmp tiene sentido en sí mismo por otras razones (por ejemplo, al ordenar cosas no numéricas, o si desea que la clasificación sea estable, lo que no es posible usar simplemente con un valor booleano)

Entonces, la pregunta es: ¿por qué los diseñadores de Python decidieron dejar la signfunción fuera del lenguaje? ¿Por qué se molestan los diablos copysigny no su padre sign?

¿Me estoy perdiendo de algo?

EDITAR - después del comentario de Peter Hansen. Es justo que no lo haya usado, pero no dijo para qué usa Python. ¡En 7 años que uso Python, lo necesité incontables veces, y la última es la gota que colmó el vaso!

Sí, puedes pasar cmp, pero el 90% de las veces que tuve que pasar fue en un idioma como lambda x,y: cmp(score(x),score(y))ese hubiera funcionado bien con el signo.

Finalmente, espero que esté de acuerdo en que signsería más útil que copysign, así que incluso si compré su vista, ¿por qué molestarse en definir eso en matemáticas, en lugar de firmar? ¿Cómo puede ser tan útil Copysign que Sign?

Davide
fuente
33
@dmazzoni: ¿este argumento no funcionaría para todas las preguntas en este sitio? ¡simplemente cierre stackoverflow y haga todas las preguntas al desarrollador del tema relevante o a la lista de correo del usuario!
Davide
43
El lugar adecuado para una pregunta es cualquier lugar donde es probable que se responda. Por lo tanto, stackoverflow es un lugar apropiado.
Stefano Borini
23
-1: @Davide: las preguntas "Por qué" y "por qué no" generalmente no se pueden responder aquí. Dado que la mayoría de los principios del desarrollo de Python no responden preguntas aquí, rara vez (si alguna vez) va a obtener una respuesta a una pregunta de "por qué" o "por qué no". Además, no tienes un problema que resolver. Suenas como si tuvieras una queja. Si tiene un problema ("¿Cómo puedo solucionar la falta de señal en este ejemplo ..."), es sensato. "Por qué no" no es sensato para este lugar.
S.Lott
31
La pregunta puede ser un poco emotiva, pero no creo que sea una mala pregunta. Estoy seguro de que muchas personas han buscado una función de señal integrada, por lo que puede ser curioso por qué no hay una.
FogleBird
17
Esta es una pregunta perfectamente objetiva: "Por qué" Python carece de una característica dada es una consulta legítima sobre la historia del diseño del lenguaje que se puede responder mediante un enlace a la discusión adecuada desde python-dev u otros foros (a veces publicaciones de blog) donde Python Los desarrolladores principales tienen que discutir un tema. Después de haber intentado buscar en Google fragmentos de historia en python-dev antes, puedo entender por qué un recién llegado al lenguaje podría llegar a un callejón sin salida y venir a preguntar aquí con la esperanza de que una persona con más experiencia en Python responda.
Brandon Rhodes

Respuestas:

228

EDITAR:

De hecho, había un parche que incluíasign() en las matemáticas , pero no fue aceptado, porque no estaban de acuerdo en lo que debería devolver en todos los casos extremos (+/- 0, +/- nan, etc.)

Por lo tanto, decidieron implementar solo copysign, que (aunque más detallado) se puede usar para delegar al usuario final el comportamiento deseado para casos extremos , que a veces pueden requerir la llamadacmp(x,0) .


No sé por qué no está integrado, pero tengo algunas ideas.

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

Lo más importante, copysignes un superconjunto de sign! Llamar copysigncon x = 1 es lo mismo que una signfunción. Entonces puedes usarlo copysigny olvidarte de él .

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

Si te cansas de pasar dos argumentos completos, puedes implementar sign esta manera, y seguirá siendo compatible con el material IEEE mencionado por otros:

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

En segundo lugar, generalmente cuando desea el signo de algo, simplemente termina multiplicándolo con otro valor. Y, por supuesto, eso es básicamente lo que copysignhace.

Entonces, en lugar de:

s = sign(a)
b = b * s

Solo puedes hacer:

b = copysign(b, a)

Y sí, ¡me sorprende que hayas estado usando Python durante 7 años y creo que cmppodría eliminarse y reemplazarse tan fácilmente sign! ¿Nunca ha implementado una clase con un__cmp__ método? ¿Nunca ha llamado cmpy especificado una función de comparación personalizada?

En resumen, me he encontrado queriendo una signfunción también, pero copysigncon el primer argumento siendo 1 funcionará bien. No estoy de acuerdo con que signsería más útil copysign, ya que he demostrado que es simplemente un subconjunto de la misma funcionalidad.

FogleBird
fuente
35
Usando [int(copysign(1, zero)) for zero in (0, 0.0, -0.0)]da [1, 1, -1]. Eso debería haber sido [0, 0, 0]según en.wikipedia.org/wiki/Sign_function
user238424
12
@Andrew - El orden de llamada de @ user238424 es correcto. copysign(a,b)devuelve a con el signo de b - b es la entrada variable, a es el valor a normalizar con el signo de b. En este caso, el comentarista está ilustrando que copysign (1, x) como un reemplazo para el signo (x) falla, ya que devuelve 1 para x = 0, mientras que el signo (0) se evaluaría a 0.
PaulMcG
77
Los flotadores tienen "signo" separado del "valor"; -0.0 es un número negativo, incluso si eso parece un error de implementación. Simplemente usando cmp()dará los resultados deseados, probablemente para casi todos los casos que a alguien le interesaría: [cmp(zero, 0) for zero in (0, 0.0, -0.0, -4, 5)]==> [0, 0, 0, -1, 1].
pitonaria
11
s = sign(a) b = b * sno es equivalente a b = copysign(b, a)! No considera el signo de b. Por ejemplo, si a=b=-1el primer código devolverá 1 mientras que el segundo devuelve -1
Johannes Jendersie
14
Al ver la definición de reemplazo del signo falso (), el falso equivalente para la multiplicación con el signo (a), la explicación falsa para la motivación del signo de copia y el reemplazo correcto "cmp (x, 0)" ya se menciona en la pregunta: hay no mucha información y no me queda claro por qué esta es la respuesta "aceptada" con tantos votos.
kxr
59

"copysign" está definido por IEEE 754 y forma parte de la especificación C99. Por eso está en Python. La función no se puede implementar en su totalidad por abs (x) * signo (y) debido a cómo se supone que debe manejar los valores de 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
>>> 

Eso hace que copysign () sea una función más útil que sign ().

En cuanto a las razones específicas por las que el signbit (x) de IEEE no está disponible en Python estándar, no lo sé. Puedo hacer suposiciones, pero sería adivinar.

El módulo matemático en sí usa copysign (1, x) como una forma de verificar si x es negativo o no negativo. Para la mayoría de los casos, tratar con funciones matemáticas que parece más útil que tener un signo (x) que devuelve 1, 0 o -1 porque hay un caso menos a tener en cuenta. Por ejemplo, lo siguiente es del módulo matemático 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);

Allí puede ver claramente que copysign () es una función más efectiva que una función de signo de tres valores ().

Tu escribiste:

Si fuera un diseñador de Python, sería al revés: no cmp () incorporado, sino un signo ()

Eso significa que no sabes que cmp () se usa para otras cosas además de los números. cmp ("This", "That") no se puede implementar con una función sign ().

Editar para recopilar mis respuestas adicionales en otro lugar :

Basas tus justificaciones en cómo a menudo se ven juntos abs () y sign (). Como la biblioteca estándar de C no contiene ningún tipo de función 'sign (x)', no sé cómo justifica sus puntos de vista. Hay un abs (int) y fabs (doble) y fabsf (flotante) y fabsl (largo) pero no se menciona el signo. Hay "copysign ()" y "signbit ()", pero solo se aplican a los números IEEE 754.

Con números complejos, ¿qué signo (-3 + 4j) volvería en Python, si se implementara? abs (-3 + 4j) devuelve 5.0. Ese es un claro ejemplo de cómo abs () se puede usar en lugares donde sign () no tiene sentido.

Supongamos que se agrega el signo (x) a Python, como complemento de abs (x). Si 'x' es una instancia de una clase definida por el usuario que implementa el método __abs __ (self), entonces abs (x) llamará a x .__ abs __ (). Para funcionar correctamente, para manejar abs (x) de la misma manera, Python tendrá que obtener un signo espacio de (x).

Esto es excesivo para una función relativamente innecesaria. Además, ¿por qué debería existir el signo (x) y no existir el negativo (x) y el no positivo (x)? Mi fragmento de la implementación del módulo matemático de Python muestra cómo se puede usar copybit (x, y) para implementar nonnegative (), lo que no puede hacer un simple signo (x).

Python debería admitir tener un mejor soporte para la función matemática IEEE 754 / C99. Eso agregaría una función signbit (x), que haría lo que quieras en el caso de flotadores. No funcionaría para enteros o números complejos, mucho menos cadenas, y no tendría el nombre que está buscando.

Usted pregunta "por qué", y la respuesta es "el signo (x) no es útil". Afirmas que es útil. Sin embargo, sus comentarios muestran que no sabe lo suficiente como para poder hacer esa afirmación, lo que significa que tendría que mostrar evidencia convincente de su necesidad. Decir que NumPy lo implementa no es lo suficientemente convincente. Debería mostrar casos de cómo se mejoraría el código existente con una función de signo.

Y eso está fuera del alcance de StackOverflow. Llévelo a una de las listas de Python.

Andrew Dalke
fuente
55
Bueno, no sé si eso te hará feliz, pero Python 3 no tiene cmp()ni sign():-)
Antoine P.
44
escribir una buena función sign () que funcione correctamente con IEEE 754 no es trivial. Este sería un buen punto para incluirlo en el idioma, en lugar de dejarlo fuera, a pesar de que no elaboré este punto en la pregunta
Davide
2
Su comentario acerca de cómo "si desea que la ordenación sea estable" significa que tampoco sabe cómo funciona la ordenación estable. Su afirmación de que copysign y sign son equivalentes muestra que no sabía mucho acerca de las matemáticas IEEE 754 antes de esta publicación. ¿Python debería implementar todas las funciones matemáticas 754 en core? ¿Qué debería hacer para los compiladores que no son C99? ¿Plataformas no 754? "isnonnegative" y "isnonpositive" también son funciones útiles. ¿Python también debería incluir esos? abs (x) difiere a x .__ abs __ (), entonces ¿debería firmar (x) diferir a x .__ signo __ ()? Hay poca demanda o necesidad de ella, entonces, ¿por qué debería estar atrapada en el núcleo?
Andrew Dalke
2
math.copysign (1, float ("- nan")) devuelve 1.0 en lugar de -1.0 cuando lo intento en 2.7
dansalmo
34

Otro trazador de líneas para sign ()

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

Si desea que devuelva 0 para x = 0:

sign = lambda x: x and (1, -1)[x<0]
dansalmo
fuente
1
¿por qué? La pregunta en sí misma reconoce que cmp(x, 0)es equivalente signy lambda x: cmp(x, 0)es más legible de lo que sugiere.
ToolmakerSteve
1
De hecho, estaba equivocado. Supuse que se especificó 'cmp' para devolver -1,0, + 1, pero veo que la especificación no garantiza eso.
ToolmakerSteve
Hermoso. Responde la pregunta iniciada: python int o float a -1, 0, 1?
scharfmn
1
¿Hay alguna ventaja en usar listas en lugar de -1 if x < 0 else 1?
Mateen Ulhaq
66
sign = lambda x: -1 if x < 0 else 1Es un 15% más rápido . Lo mismo con sign = lambda x: x and (-1 if x < 0 else 1).
Mateen Ulhaq
26

Como cmpse ha eliminado , puede obtener la misma funcionalidad con

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

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

Funciona para float, inte incluso Fraction. En el caso de float, avisosign(float("nan")) es cero.

Python no requiere que las comparaciones devuelvan un valor booleano, por lo que obligar a las comparaciones a bool () protege contra la implementación permitida, pero poco común:

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

Solo la respuesta correcta cumple con la definición de Wikipedia

La definición en Wikipedia dice:

definición de signo

Por lo tanto,

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

Que a todos los efectos puede simplificarse para:

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

Esta definición de función se ejecuta rápidamente y produce resultados correctos garantizados para 0, 0.0, -0.0, -4 y 5 (ver comentarios a otras respuestas incorrectas).

Tenga en cuenta que cero (0) no es ni positivo ni negativo .

Serge Stroobandt
fuente
1
Esta respuesta ilustra cuán sucinta pero poderosa puede ser Python.
NelsonGon
1
Quibble: el código no implementa la definición de WP, reemplaza la cláusula intermedia con una cláusula predeterminada al final. Si bien esto es necesario para manejar números no reales como nan, se muestra erróneamente como directamente después de la declaración WP ('Por lo tanto').
Jürgen Strobel
1
@ JürgenStrobel Sé exactamente lo que quieres decir y también llevo mucho tiempo contemplando este tema. Extendí la respuesta ahora para el formalismo correcto, mientras mantenía la versión simplificada para la mayoría de los casos de uso.
Serge Stroobandt
10

numpy tiene una función de signo, y también le brinda una ventaja de otras funciones. Entonces:

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

Solo tenga cuidado de que el resultado sea numpy.float64:

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

Para cosas como json, esto es importante, ya que json no sabe cómo serializar los tipos numpy.float64. En ese caso, podrías hacer:

float(np.sign(y))

para obtener un flotador regular.

Luca
fuente
10

Intenta ejecutar esto, donde x es cualquier número

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

La coerción a bool () maneja la posibilidad de que el operador de comparación no devuelva un booleano.

Eric Song
fuente
Buena idea, pero creo que te refieres a: int_sign = int (x> 0) - int (x <0)
yucer
Quiero decir: int_sign = lambda x: (x> 0) - (x <0)
yucer
1
@yucer no, realmente quiso decir que el elenco estaba en bool (que de todos modos es una subclase de int), debido a la posibilidad teórica de que dio el enlace a la explicación.
Walter Tross
El único inconveniente de esta construcción es que el argumento aparece dos veces, lo cual está bien solo si se trata de una sola variable
Walter Tross, el
5

Si un correcto sign() función debería estar al menos en el módulo matemático, ya que está en numpy. Porque uno lo necesita con frecuencia para el código orientado a las matemáticas.

Pero math.copysign()también es útil de forma independiente.

cmp()y obj.__cmp__()... tienen generalmente una gran importancia independientemente. No solo para el código orientado a las matemáticas. Considere comparar / ordenar tuplas, objetos de fecha, ...

Los argumentos de desarrollo en http://bugs.python.org/issue1640 con respecto a la omisión de math.sign()son extraños, porque:

  • No hay por separado -NaN
  • sign(nan) == nan sin preocupaciones (como exp(nan))
  • sign(-0.0) == sign(0.0) == 0 sin preocupación
  • sign(-inf) == -1 sin preocupación

- como está en numpy

kxr
fuente
4

En Python 2, cmp()devuelve un entero: no hay requisito de que el resultado sea -1, 0 o 1, por sign(x)lo que no es lo mismo quecmp(x,0) .

En Python 3, cmp()se ha eliminado a favor de una comparación rica. Para cmp(), Python 3 sugiere esto :

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

lo cual está bien para cmp (), pero nuevamente no se puede usar para sign () porque los operadores de comparación no necesitan devolver booleanos .

Para lidiar con esta posibilidad, los resultados de la comparación deben ser obligados a booleanos:

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

Esto funciona para cualquiera typeque esté totalmente ordenado (incluidos valores especiales como NaNo infinitos).

AllenT
fuente
0

No necesita uno, solo puede usar:

If not number == 0:
    sig = number/abs(number)
else:
    sig = 0
inetphantom
fuente
44
Vale la pena señalar que x / abs(x)lleva un poco más de tiempo que simplemente encadenar if/elsepara verificar en qué lado de 0 está return (x > 0) - (x < 0)boolint
1
Trata de Python Truey Falsecomo 1y 0, se puede hacer absolutamente esto y conseguir o 1, 0o -1. def sign(x): return (x > 0) - (x < 0)no devolverá un bool, devolverá un int- si pasa 0, 0volverá
0

Simplemente no lo hace.

La mejor manera de solucionar esto es:

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

La razón por la que el "signo" no está incluido es porque si incluyéramos todas las líneas útiles en la lista de funciones integradas, Python ya no sería fácil y práctico trabajar con ellas. Si usa esta función con tanta frecuencia, ¿por qué no la factoriza usted mismo? No es que sea remotamente difícil o incluso tedioso hacerlo.

Antoine P.
fuente
66
Bueno, compraría esto solo si también abs()se dejara fuera sign()y a abs()menudo se usan juntos, sign()es el más útil de los dos (IMO), y ninguno es remotamente difícil o tedioso de implementar (a pesar de que es propenso a errores, vea cómo esta respuesta se equivoca: stackoverflow.com/questions/1986152/… )
Davide
1
La cuestión es que el resultado numérico de sign()sí mismo rara vez es útil. Lo que haces la mayoría de las veces es tomar diferentes rutas de código en función de si una variable es positiva o negativa, y en ese caso es más legible escribir la condición explícitamente.
Antoine P.
3
abs () se usa con mucha más frecuencia que un signo (). Y te señalé el rastreador NumPy que muestra lo difícil que puede ser implementar sign (). ¿Cuál debería ser el signo (-3 + 4j)? Mientras que abs (-3 + 4j) es 5.0. Haces afirmaciones de que sign () y abs () a menudo se ven juntos. La biblioteca estándar de C no tiene una función de 'signo', entonces, ¿de dónde obtiene sus datos?
Andrew Dalke