Por que o Python não possui uma função de sinal?

241

Não consigo entender por que o Python não tem uma signfunção. Ele tem um absbuiltin (que eu considero signa irmã de), mas nãosign .

No python 2.6 há até uma copysignfunção (em matemática ), mas nenhum sinal. Por que se preocupar em escrever um copysign(x,y)quando você pode simplesmente escrever um signe depois obter o copysigndiretamente abs(x) * sign(y)? O último seria muito mais claro: x com o sinal de y, enquanto no copysign você deve se lembrar se é x com o sinal de y ou y com o sinal de x!

Obviamente sign(x), não fornece nada além disso cmp(x,0), mas seria muito mais legível que isso também (e para uma linguagem muito legível como python, isso seria uma grande vantagem).

Se eu fosse um designer de python, seria o contrário: não cmpembutido, mas a sign. Quando você precisar cmp(x,y), você pode simplesmente fazer um sign(x-y)(ou, melhor ainda, para coisas não numéricas, apenas um x> y - é claro que isso deveria exigir a sortedaceitação de um booleano em vez de um comparador inteiro). Isso também ficaria mais claro: positivo quando x>y(considerando que cmpvocê deve se lembrar da convenção positiva quando o primeiro for maior , mas pode ser o contrário). É claro que cmpfaz sentido por outras razões (por exemplo, ao classificar coisas não numéricas, ou se você deseja que a classificação seja estável, o que não é possível usando simplesmente com um booleano)

Então, a pergunta é: por que os designers de Python decidiram deixar a signfunção fora da linguagem? Por que diabos se incomoda com copysigne não com seus paissign ?

Estou esquecendo de algo?

EDIT - após o comentário de Peter Hansen. É justo o suficiente que você não o tenha usado, mas não disse para que usa o python. Em 7 anos que uso python, precisei inúmeras vezes, e o último é o canudo que quebrou as costas do camelo!

Sim, você pode passar o cmp por aí, mas 90% das vezes que eu precisava passá-lo estavam em um idioma como lambda x,y: cmp(score(x),score(y))aquele que funcionaria bem com o sinal.

Finalmente, espero que você concorde que isso signseria mais útil do que copysign, mesmo que eu comprei sua opinião, por que se preocupar em definir isso em matemática, em vez de assinar? Como o copysign pode ser tão útil quanto assinar?

Davide
fonte
33
@dmazzoni: esse argumento não funcionaria para todas as perguntas deste site? basta fechar o stackoverflow e fazer todas as perguntas ao dev relevante tópico ou à lista de discussão do usuário!
31410 Davide
43
O local apropriado para uma pergunta é qualquer lugar em que provavelmente seja respondido. Assim, o stackoverflow é um local apropriado.
Stefano Borini
23
-1: @ David: As perguntas "Por que" e "Por que não" geralmente não podem ser respondidas aqui. Como a maioria dos princípios de desenvolvimento do Python não responde perguntas aqui, você raramente (se alguma vez) recebe uma resposta para uma pergunta "por que" ou "por que não". Além disso, você não tem um problema para resolver. Você parece ter um discurso retórico. Se você tiver um problema ("Como solucionar a falta de sinal neste exemplo ..."), isso é sensato. "Por que não" não é sensato para este local.
S.Lott
31
A pergunta pode ser um pouco emocional, mas não acho que seja uma pergunta ruim. Tenho certeza de que muitas pessoas procuraram por uma função de sinal embutida, portanto, pode ser curioso por que não existe uma.
FogleBird
17
Esta é uma pergunta perfeitamente objetiva: “Por que o Python não possui um recurso específico é uma consulta legítima sobre o histórico do design da linguagem que pode ser respondida ao se vincular à discussão apropriada do python-dev ou outros fóruns (às vezes posts do blog) em que o Python os desenvolvedores principais passam por hash um tópico. Tendo tentado pesquisar no Google por bits de história em python-dev antes, posso entender por que um iniciante na linguagem pode chegar a um beco sem saída e vir perguntar aqui, na esperança de uma pessoa mais experiente em Python responder!
Brandon Rhodes

Respostas:

228

EDITAR:

Na verdade, havia um patch incluído sign()na matemática , mas não era aceito, porque eles não concordavam com o que deveria retornar em todos os casos extremos (+/- 0, +/- nan, etc)

Por isso, eles decidiram implementar apenas o copysign, que (embora mais detalhado) pode ser usado para delegar ao usuário final o comportamento desejado para casos extremos - o que às vezes pode exigir a chamadacmp(x,0) .


Não sei por que não é um built-in, mas tenho alguns pensamentos.

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

Mais importante, copysigné um superconjunto de sign! Ligar copysigncom x = 1 é o mesmo que uma signfunção. Então você pode simplesmente usar copysigne esquecer .

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

Se você ficar cansado de passar dois argumentos inteiros, poderá implementar signdessa maneira, e ainda será compatível com o material IEEE mencionado por outros:

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

Em segundo lugar, geralmente quando você quer o sinal de algo, acaba multiplicando-o por outro valor. E é claro que é basicamente o que copysignfaz.

Então, em vez de:

s = sign(a)
b = b * s

Você pode apenas fazer:

b = copysign(b, a)

E sim, estou surpreso que você esteja usando o Python por 7 anos e pense que cmppoderia ser facilmente removido e substituído por sign! Você nunca implementou uma classe com um __cmp__método? Você nunca ligou cmpe especificou uma função comparadora personalizada?

Em resumo, também me vi querendo uma signfunção, mas copysigncom o primeiro argumento sendo 1, funcionará bem. Discordo que signseria mais útil do que copysign, como mostrei que é apenas um subconjunto da mesma funcionalidade.

FogleBird
fonte
35
Usando [int(copysign(1, zero)) for zero in (0, 0.0, -0.0)][1, 1, -1]. Isso deveria estar de [0, 0, 0]acordo com en.wikipedia.org/wiki/Sign_function
user238424
12
@Andrew - a ordem de chamada de @ user238424 está correta. copysign(a,b)retorna a com o sinal de b - b é a entrada variável, a é o valor para normalizar com o sinal de b. Nesse caso, o comentarista está ilustrando que o copysign (1, x) como substituto do sinal (x) falha, pois retorna 1 para x = 0, enquanto o sinal (0) seria avaliado como 0.
PaulMcG
7
Os carros alegóricos mantêm "sinal" separado de "valor"; -0,0 é um número negativo, mesmo que isso pareça um erro de implementação. Simplesmente usar cmp()dará os resultados desejados, provavelmente para quase todos os casos com os quais alguém se preocuparia: [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ão é equivalente a b = copysign(b, a)! Não considera o sinal de b. Por exemplo, se a=b=-1o primeiro código retornar 1 enquanto o segundo retornar -1
Johannes Jendersie
14
Vendo a definição de substituição de sinal falso (), o equivalente falso para multiplicação com o sinal (a), a explicação falsa para a motivação do copysign e a substituição correta "cmp (x, 0)" já sendo mencionada na pergunta - existe não há muita informação e não está claro para mim por que essa é a resposta "aceita" com tantos votos.?
KXR
59

"copysign" é definido pela IEEE 754 e faz parte da especificação C99. É por isso que está em Python. A função não pode ser implementada integralmente pelo sinal abs (x) * (y), devido à forma como deve lidar com os 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
>>> 

Isso torna o copysign () uma função mais útil do que sign ().

Quanto a razões específicas pelas quais o signbit do IEEE (x) não está disponível no Python padrão, eu não sei. Eu posso fazer suposições, mas seria adivinhar.

O próprio módulo matemático usa o copysign (1, x) como uma maneira de verificar se x é negativo ou não negativo. Na maioria dos casos, lidar com funções matemáticas parece mais útil do que ter um sinal (x) que retorna 1, 0 ou -1 porque há um caso a menos a ser considerado. Por exemplo, o seguinte é do módulo de matemática do 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á você pode ver claramente que copysign () é uma função mais efetiva que uma função sign () de três valores.

Você escreveu:

Se eu fosse um designer de python, seria o contrário: nenhum cmp () embutido, mas um sinal ()

Isso significa que você não sabe que cmp () é usado para outras coisas além de números. O cmp ("This", "That") não pode ser implementado com uma função sign ().

Editar para agrupar minhas respostas adicionais em outro lugar :

Você baseia suas justificativas em como abs () e sign () são frequentemente vistos juntos. Como a biblioteca padrão C não contém uma função 'sign (x)' de qualquer espécie, não sei como você justifica seus pontos de vista. Há um abs (int) e fabs (double) e fabsf (float) e fabsl (long), mas nenhuma menção de sinal. Há "copysign ()" e "signbit ()", mas eles se aplicam apenas aos números IEEE 754.

Com números complexos, o que o sinal (-3 + 4j) retornaria em Python, se fosse implementado? abs (-3 + 4j) retorna 5.0. Esse é um exemplo claro de como o abs () pode ser usado em lugares onde sign () não faz sentido.

Suponha que o sinal (x) foi adicionado ao Python, como um complemento ao abs (x). Se 'x' for uma instância de uma classe definida pelo usuário que implementa o método __abs __ (self), o abs (x) chamará x .__ abs __ (). Para funcionar corretamente, para lidar com abs (x) da mesma maneira, o Python precisará obter um slot de sinal (x).

Isso é excessivo para uma função relativamente desnecessária. Além disso, por que o sinal (x) existe e o não negativo (x) e o não positivo (x) não existem? Meu snippet da implementação do módulo matemático do Python mostra como o copybit (x, y) pode ser usado para implementar o nonnegative (), o que um simples sinal (x) não pode fazer.

O Python deve suportar um suporte melhor para a função matemática IEEE 754 / C99. Isso adicionaria uma função signbit (x), que faria o que você deseja no caso de carros alegóricos. Não funcionaria para números inteiros ou complexos, muito menos cadeias, e não teria o nome que você está procurando.

Você pergunta "por que" e a resposta é "o sinal (x) não é útil". Você afirma que é útil. No entanto, seus comentários mostram que você não sabe o suficiente para fazer essa afirmação, o que significa que você teria que mostrar evidências convincentes de sua necessidade. Dizer que o NumPy implementa isso não é suficientemente convincente. Você precisaria mostrar casos de como o código existente seria aprimorado com uma função de sinal.

E que está fora do escopo do StackOverflow. Leve-o para uma das listas de Python.

Andrew Dalke
fonte
5
Bem, não sei se isso vai fazer você feliz, mas o Python 3 não tem cmp()nem sign():-)
Antoine P.
4
escrever uma boa função sign () que funcione corretamente com o IEEE 754 não é trivial. Este seria um bom ponto para incluí-lo no idioma, em vez de deixá-lo de fora, mesmo que eu não tenha elaborado esse ponto na pergunta
Davide
2
Seu comentário sobre como "se você deseja que a classificação seja estável" significa que você também não sabe como a classificação estável funciona. Sua declaração de que copysign e assinar são equivalentes mostra que você não sabia muito sobre matemática IEEE 754 antes deste post. O Python deve implementar todas as 754 funções matemáticas no núcleo? O que deve fazer com os compiladores não C99? Plataformas não 754? "isnonnegative" e "isnonpositive" também são funções úteis. O Python também deve incluir esses? abs (x) adia para x .__ abs __ (), então o sinal (x) deve diferir para x .__ sign __ ()? Há pouca demanda ou necessidade disso, então por que deveria ficar preso no núcleo?
Andrew Dalke
2
math.copysign (1, float ( "- nan")) retorna 1.0 em vez de -1,0 quando eu experimentá-lo em 2,7
dansalmo
34

Outro forro para sinal ()

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

Se você deseja retornar 0 para x = 0:

sign = lambda x: x and (1, -1)[x<0]
dansalmo
fonte
1
porque? A pergunta em si reconhece que cmp(x, 0)é equivalente signe lambda x: cmp(x, 0)é mais legível do que você sugere.
Home
1
Na verdade, eu estava errado. Eu assumi que 'cmp' foi especificado para retornar -1,0, + 1, mas vejo que a especificação não garante isso.
Página Inicial>
Lindo. Responde à pergunta iniciada: python int ou float para -1, 0, 1?
Scharfmn
1
Existe alguma vantagem em usar listas em vez de -1 if x < 0 else 1?
Mateen Ulhaq
6
sign = lambda x: -1 if x < 0 else 1é 15% mais rápido . O mesmo com sign = lambda x: x and (-1 if x < 0 else 1).
Mateen Ulhaq
26

Desde a cmp que foi removido , você pode obter a mesma funcionalidade com

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

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

Trabalha para float , inte mesmo Fraction. No caso de float, avisosign(float("nan")) é zero.

O Python não exige que as comparações retornem um valor booleano; portanto, coagir as comparações a bool () protege contra a implementação permitida, mas incomum:

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

Somente resposta correta compatível com a definição da Wikipedia

A definição na Wikipedia diz:

definição de sinal

Conseqüentemente,

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

Que, para todos os efeitos, pode ser simplificado para:

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

Essa definição de função executa rapidamente e produz resultados corretos garantidos para 0, 0,0, -0,0, -4 e 5 (consulte os comentários para outras respostas incorretas).

Observe que zero (0) não é positivo nem negativo .

Serge Stroobandt
fonte
1
Esta resposta ilustra como um python poderoso e sucinto pode ser.
NelsonGon
1
Quibble: O código não implementa a definição de WP, substitui a cláusula do meio por uma cláusula padrão no final. Embora isso seja necessário para lidar com números não reais, como nan, é incorretamente mostrado como seguindo diretamente a instrução WP ('Portanto').
Jürgen Strobel
1
@ JürgenStrobel Eu sei exatamente o que você quer dizer e também há muito tempo estou contemplando esse problema. Estendi a resposta agora para o formalismo correto, mantendo a versão simplificada para a maioria dos casos de uso.
Serge Stroobandt
10

o numpy tem uma função de sinal e também oferece um bônus de outras funções. Assim:

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

Apenas tome cuidado para que o resultado seja um numpy.float64:

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

Para coisas como json, isso importa, pois o json não sabe como serializar tipos numpy.float64. Nesse caso, você pode fazer:

float(np.sign(y))

para obter uma flutuação regular.

Luca
fonte
10

Tente executar isso, onde x é qualquer número

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

A coerção para bool () lida com a possibilidade de o operador de comparação não retornar um booleano.

Eric Song
fonte
Boa ideia, mas acho que você quer dizer: int_sign = int (x> 0) - int (x <0)
yucer
Eu quero dizer: int_sign = lambda x: (x> 0) - (x <0)
yucer
1
@yucer não, ele realmente quis dizer o elenco para bool (que é uma subclasse de int de qualquer maneira), devido à possibilidade teórica da qual ele deu o link para a explicação.
Walter Tross
A única desvantagem desta construção é que o argumento aparece duas vezes, o que é bom só se for uma única variável
Walter Tross
5

Sim, uma sign()função correta deve estar pelo menos no módulo de matemática - como é numpy. Porque freqüentemente é necessário para código orientado a matemática.

Mas math.copysign()também é útil de forma independente.

cmp()e obj.__cmp__()... geralmente têm alta importância independentemente. Não apenas para código orientado a matemática. Considere comparar / classificar tuplas, objetos de data, ...

Os argumentos dev em http://bugs.python.org/issue1640 sobre a omissão de math.sign()são estranhos, porque:

  • Não há separado -NaN
  • sign(nan) == nan sem preocupação (como exp(nan))
  • sign(-0.0) == sign(0.0) == 0 Sem se preocupar
  • sign(-inf) == -1 Sem se preocupar

- como está entorpecido

kxr
fonte
4

No Python 2, cmp()retorna um número inteiro: não é necessário que o resultado seja -1, 0 ou 1; portanto, sign(x)não é o mesmo quecmp(x,0) .

No Python 3, cmp()foi removido em favor da comparação rica. Pois cmp(), o Python 3 sugere o seguinte :

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

o que é bom para cmp (), mas novamente não pode ser usado para sign () porque os operadores de comparação não precisam retornar booleanos .

Para lidar com essa possibilidade, os resultados da comparação devem ser coagidos aos booleanos:

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

Isso funciona para qualquer um typeque seja totalmente ordenado (incluindo valores especiais como NaNou infinitos).

AllenT
fonte
0

Você não precisa de um, basta usar:

If not number == 0:
    sig = number/abs(number)
else:
    sig = 0
inetphantom
fonte
4
Vale a pena destacar que x / abs(x)demora um pouco mais do que apenas encadeamento if/elsepara verificar qual o lado de 0 a variável está ligada, ou para que o assunto usando o viscoso-ainda-satisfazendo return (x > 0) - (x < 0)para subtrair boolvalores e retornar umint
1
Trata Python Truee Falsecomo 1e 0, você pode absolutamente fazer isso e obter qualquer 1, 0ou -1. def sign(x): return (x > 0) - (x < 0)não retornará um bool, retornará um int- se você passar, 0você 0retornará #
0

Simplesmente não.

A melhor maneira de corrigir isso é:

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

A razão pela qual "sign" não está incluída é que, se incluíssemos todas as linhas úteis na lista de funções internas, o Python não seria mais fácil e prático de se trabalhar. Se você usa essa função com tanta frequência, por que não faz isso sozinha? Não é remotamente difícil ou mesmo tedioso fazê-lo.

Antoine P.
fonte
6
Bem, eu compraria isso apenas se abs()fosse deixado de fora também. sign()e abs()são frequentemente usados ​​juntos, sign()é o mais útil dos dois (IMO), e nenhum é remotamente difícil ou tedioso de implementar (mesmo sendo propenso a erros, veja como esta resposta está errada: stackoverflow.com/questions/1986152/… )
Davide
1
O fato é que o resultado numérico por sign()si só raramente é útil. O que você faz na maioria das vezes é seguir caminhos de código diferentes, com base no fato de uma variável ser positiva ou negativa e, nesse caso, é mais legível escrever a condição explicitamente.
Antoine P.
3
abs () é usado com muito mais freqüência do que um sinal () seria. E eu apontei para o rastreador NumPy, que mostra como o sinal duro () pode ser implementado. O que deve ser o sinal (-3 + 4j)? Enquanto abs (-3 + 4j) é 5,0. Você faz afirmações de que sign () e abs () são frequentemente vistos juntos. A biblioteca padrão C não possui uma função 'sign', então onde você está obtendo seus dados?
31720 Andrew Dalke