Perché Python non ha una funzione di segno?


241

Non riesco a capire perché Python non abbia una signfunzione. Ha un absbuiltin (che considero signla sorella), ma no sign.

In python 2.6 c'è anche una copysignfunzione (in matematica ), ma nessun segno. Perché preoccuparsi di scrivere un copysign(x,y)quando si potrebbe semplicemente scrivere un signe quindi ottenere copysigndirettamente da abs(x) * sign(y)? Quest'ultimo sarebbe molto più chiaro: x con il segno di y, mentre con copysign devi ricordare se è x con il segno di y o y con il segno di x!

Ovviamente sign(x)non fornisce nulla di più cmp(x,0), ma sarebbe molto più leggibile anche questo (e per un linguaggio molto leggibile come Python, questo sarebbe stato un grande vantaggio).

Se fossi un designer di pitone, sarei il contrario: nessun cmpbuiltin, ma a sign. Quando ne hai bisogno cmp(x,y), potresti semplicemente fare un sign(x-y)(o, ancora meglio, per cose non numeriche, solo un x> y - ovviamente questo avrebbe dovuto sortedaccettare un valore booleano invece di un comparatore intero). Questo sarebbe anche più chiaro: positivo quando x>y(mentre con cmpte devi ricordare la convenzione positiva quando il primo è più grande , ma potrebbe essere il contrario). Naturalmente cmpha senso per altri motivi (ad esempio quando si ordinano cose non numeriche o se si desidera che l'ordinamento sia stabile, cosa impossibile con un semplice valore booleano)

Quindi, la domanda è: perché i designer Python hanno deciso di lasciare la signfunzione fuori dal linguaggio? Perché diavolo preoccuparsi copysigne non dei suoi genitori sign?

Mi sto perdendo qualcosa?

EDIT - dopo il commento di Peter Hansen. Abbastanza giusto da non averlo usato, ma non hai detto per cosa usi Python. In 7 anni che uso Python, ne avevo bisogno innumerevoli volte, e l'ultima è la goccia che ha spezzato la schiena del cammello!

Sì, puoi passare cmp in giro, ma il 90% delle volte che ho dovuto passare era in un idioma del genere lambda x,y: cmp(score(x),score(y))avrebbe funzionato con il segno bene.

Infine, spero che tu sia d'accordo sul fatto che signsarebbe più utile di copysign, quindi anche se avessi acquistato il tuo punto di vista, perché preoccuparsi di definirlo in matematica, invece di firmare? Come può copysign essere così utile che firmare?


33
@dmazzoni: questo argomento non funzionerebbe con tutte le domande su questo sito? basta chiudere StackOverflow e porre ogni domanda all'argomento rilevante o alla mailing list dell'utente!
Davide,

43
Il posto giusto per una domanda è qualsiasi posto in cui è probabile che abbia una risposta. Pertanto, StackOverflow è il posto giusto.
Stefano Borini,

23
-1: @Davide: in genere non è possibile rispondere alle domande "Why" e "why not" qui. Poiché la maggior parte dei principi dello sviluppo di Python non risponde alle domande qui, raramente (se mai) otterrai una risposta a una domanda "why" o "why not". Inoltre, non hai problemi da risolvere. Sembra che tu abbia una collera. Se hai un problema ("Come aggirare la mancanza di segno in questo esempio ..."), è ragionevole. "Why not" non è sensato per questo locale.
S.Lott

31
La domanda potrebbe essere un po 'emotiva, ma non penso che sia una cattiva domanda. Sono sicuro che molte persone hanno cercato una funzione di segno integrata, quindi può essere curioso perché non ce n'è una.
FogleBird,

17
Questa è una domanda perfettamente obiettiva: "Perché" Python manca di una data caratteristica è una domanda legittima sulla storia del design del linguaggio a cui si può rispondere collegando alla discussione appropriata da python-dev o altri forum (a volte post di blog) in cui il Python gli sviluppatori di base capiscono di elaborare un argomento. Avendo provato a Google un po 'di storia in Python-dev me stesso prima, posso capire perché un nuovo arrivato nella lingua potrebbe raggiungere un vicolo cieco e venire a chiedere qui nella speranza di una persona Python più esperta che risponde!
Brandon Rhodes,

Risposte:


228

MODIFICARE:

In effetti c'era una patch inclusa sign()in matematica , ma non era accettata, perché non erano d'accordo su cosa dovesse tornare in tutti i casi limite (+/- 0, +/- nan, ecc.)

Così hanno deciso di implementare solo il copysign, che (sebbene più dettagliato) può essere utilizzato per delegare all'utente finale il comportamento desiderato per i casi limite - che a volte potrebbe richiedere la chiamata acmp(x,0) .


Non so perché non sia un built-in, ma ho alcuni pensieri.

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

copysignAncora più importante, è un superset di sign! Chiamare copysigncon x = 1 equivale a una signfunzione. Quindi potresti semplicemente usarlo copysigne dimenticartene .

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

Se ti ammali di passare due interi argomenti, puoi implementarlo in signquesto modo, e sarà comunque compatibile con le cose IEEE menzionate da altri:

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

In secondo luogo, di solito quando vuoi il segno di qualcosa, finisci per moltiplicarlo con un altro valore. E ovviamente questo è fondamentalmente ciò che copysignfa.

Quindi, invece di:

s = sign(a)
b = b * s

Puoi semplicemente fare:

b = copysign(b, a)

E sì, sono sorpreso che tu stia usando Python da 7 anni e penso che cmppossa essere rimosso e sostituito così facilmente sign! Non hai mai implementato una classe con un __cmp__metodo? Non hai mai chiamato cmpe specificato una funzione di confronto personalizzata?

In sintesi, mi sono trovato a desiderare anche una signfunzione, ma copysigncon il primo argomento 1 funzionerà bene. Non sono d'accordo sul fatto che signsarebbe più utile di copysign, poiché ho dimostrato che si tratta semplicemente di un sottoinsieme della stessa funzionalità.


35
Usando [int(copysign(1, zero)) for zero in (0, 0.0, -0.0)][1, 1, -1]. Ciò avrebbe dovuto essere [0, 0, 0]secondo en.wikipedia.org/wiki/Sign_function
user238424

12
@Andrew - L'ordine di chiamata di @ user238424 è corretto. copysign(a,b)restituisce a con il segno di b - b è l'input variabile, a è il valore a cui normalizzare con il segno di b. In questo caso, il commentatore sta illustrando che il copysign (1, x) come sostituto del segno (x) fallisce, poiché restituisce 1 per x = 0, mentre il segno (0) valuterebbe a 0.
PaulMcG

7
I float mantengono il "segno" separato dal "valore"; -0,0 è un numero negativo, anche se sembra un errore di implementazione. Il semplice utilizzo cmp()fornirà i risultati desiderati, probabilmente per quasi tutti i casi a cui qualcuno potrebbe interessare: [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 * snon è equivalente a b = copysign(b, a)! Non considera il segno di b. Ad esempio, se a=b=-1il primo codice restituirà 1 mentre il secondo restituirà -1
Johannes Jendersie,

14
Vedendo la definizione di sostituzione del segno falso (), l'equivalente falso per la moltiplicazione con il segno (a), la falsa spiegazione per la motivazione del copysign e la sostituzione corretta "cmp (x, 0)" sono già menzionati nella domanda - c'è non molte informazioni e non mi è chiaro perché questa sia la risposta "accettata" con così tanti voti.
kxr,

59

"copysign" è definito da IEEE 754 e fa parte della specifica C99. Ecco perché è in Python. La funzione non può essere implementata per intero dal segno abs (x) * (y) a causa di come dovrebbe gestire i valori 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
>>> 

Ciò rende copysign () una funzione più utile di sign ().

Per quanto riguarda i motivi specifici per cui il signbit di IEEE (x) non è disponibile in Python standard, non lo so. Posso fare ipotesi, ma sarebbe indovinare.

Lo stesso modulo di matematica utilizza copysign (1, x) come un modo per verificare se x è negativo o non negativo. Nella maggior parte dei casi si tratta di funzioni matematiche che sembrano più utili che avere un segno (x) che restituisce 1, 0 o -1 perché c'è un caso in meno da considerare. Ad esempio, quanto segue proviene dal modulo di matematica di 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ì puoi vedere chiaramente che copysign () è una funzione più efficace di una funzione sign () a tre valori.

Hai scritto:

Se fossi un designer Python, sarei il contrario: nessun cmp () incorporato, ma un segno ()

Ciò significa che non sai che cmp () è usato per cose oltre ai numeri. cmp ("This", "That") non può essere implementato con una funzione sign ().

Modifica per raccogliere le mie risposte aggiuntive altrove :

Basi le tue giustificazioni su come abs () e sign () sono spesso visti insieme. Dato che la libreria C standard non contiene una funzione "sign (x)" di alcun tipo, non so come giustificare le tue opinioni. C'è un abs (int) e fabs (doppio) e fabsf (float) e fabsl (long) ma nessuna menzione di segno. Esistono "copysign ()" e "signbit ()" ma quelli si applicano solo ai numeri IEEE 754.

Con numeri complessi, cosa significherebbe (-3 + 4j) tornare in Python, se fosse implementato? abs (-3 + 4j) restituisce 5.0. Questo è un chiaro esempio di come abs () può essere usato in luoghi in cui sign () non ha senso.

Supponiamo che il segno (x) sia stato aggiunto a Python, come complemento di abs (x). Se 'x' è un'istanza di una classe definita dall'utente che implementa il metodo __abs __ (self), allora abs (x) chiamerà x .__ abs __ (). Per funzionare correttamente, per gestire abs (x) allo stesso modo, Python dovrà ottenere uno slot segno (x).

Questo è eccessivo per una funzione relativamente non necessaria. Inoltre, perché il segno (x) dovrebbe esistere e non negativo (x) e non positivo (x) non esistere? Il mio frammento dell'implementazione del modulo matematico di Python mostra come copybit (x, y) può essere usato per implementare nonnegative (), cosa che un semplice segno (x) non può fare.

Python dovrebbe supportare un supporto migliore per la funzione matematica IEEE 754 / C99. Ciò aggiungerebbe una funzione signbit (x), che farebbe quello che vuoi in caso di float. Non funzionerebbe con numeri interi o numeri complessi, tanto meno stringhe, e non avrebbe il nome che stai cercando.

Chiedete "perché" e la risposta è "il segno (x) non è utile". Afferma che è utile. Tuttavia, i tuoi commenti mostrano che non sai abbastanza per poter fare questa affermazione, il che significa che dovresti mostrare prove convincenti del suo bisogno. Dire che NumPy lo implementa non è abbastanza convincente. Dovresti mostrare i casi in cui il codice esistente sarebbe migliorato con una funzione di segno.

E questo al di fuori dell'ambito di StackOverflow. Portalo invece in una delle liste di Python.


5
Beh, non lo farei se questo ti rendesse felice, ma Python 3 non ha né cmp()sign():-)
Antoine P.

4
scrivere una buona funzione sign () che funzioni correttamente con IEEE 754 non è banale. Questo sarebbe un buon punto per includerlo nella lingua, piuttosto che lasciarlo fuori, anche se non ho elaborato questo punto nella domanda
Davide,

2
Il tuo commento su come "se vuoi che l'ordinamento sia stabile" significa anche che non sai come funziona l'ordinamento stabile. La tua affermazione che copysign e sign sono equivalenti mostra che non sapevi molto della matematica IEEE 754 prima di questo post. Python dovrebbe implementare tutte le funzioni matematiche 754 nel core? Cosa dovrebbe fare per i compilatori non C99? Piattaforme non 754? "isnonnegative" e "isnonpositive" sono anche funzioni utili. Python dovrebbe includere anche quelli? abs (x) passa a x .__ abs __ (), quindi il segno (x) deve rinviare a x .__ segno __ ()? C'è poca richiesta o necessità, quindi perché dovrebbe essere bloccato nel nocciolo?
Andrew Dalke,

2
math.copysign (1, float ("- nan")) restituisce 1.0 invece di -1.0 quando lo provo in 2.7
dansalmo

34

Un altro liner per sign ()

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

Se vuoi che ritorni 0 per x = 0:

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

1
perché? La domanda stessa riconosce che cmp(x, 0)è equivalente a sign, ed lambda x: cmp(x, 0)è più leggibile di ciò che suggerisci.
ToolmakerSteve

1
Anzi, mi sbagliavo. Avevo supposto che 'cmp' fosse specificato per restituire -1,0, + 1, ma vedo che le specifiche non lo garantiscono.
ToolmakerSteve

Bellissimo. Risponde alla domanda iniziata: python int o float su -1, 0, 1?
scharfmn,

1
C'è qualche vantaggio nell'usare le liste invece di -1 if x < 0 else 1?
Mateen Ulhaq,

6
sign = lambda x: -1 if x < 0 else 1è del 15% più veloce . Lo stesso con sign = lambda x: x and (-1 if x < 0 else 1).
Mateen Ulhaq,

26

Poiché cmpè stato rimosso , puoi ottenere la stessa funzionalità con

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

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

Funziona per float, inte anche Fraction. Nel caso di float, avviso sign(float("nan"))è zero.

Python non richiede che i confronti restituiscano un valore booleano, quindi forzare i confronti a bool () protegge dall'implementazione consentita, ma non comune:

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

13

Solo una risposta corretta conforme alla definizione di Wikipedia

La definizione su Wikipedia recita:

definizione del segno

Quindi,

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

Che a tutti gli effetti può essere semplificato per:

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

Questa definizione di funzione esegue rapidamente e fornisce risultati corretti garantiti per 0, 0,0, -0,0, -4 e 5 (vedere i commenti ad altre risposte errate).

Si noti che zero (0) non è né positivo né negativo .


1
Questa risposta illustra quanto possa essere conciso e potente il pitone.
Nelson Gon,

1
Quibble: il codice non implementa la definizione di WP, sostituisce la clausola centrale con una clausola predefinita alla fine. Sebbene ciò sia necessario per gestire numeri non reali come nan, viene erroneamente mostrato come direttamente seguito all'istruzione WP ("quindi").
Jürgen Strobel,

1
@ JürgenStrobel So esattamente cosa intendi e ho anche a lungo contemplato questo problema. Ho esteso la risposta ora per un corretto formalismo, pur mantenendo la versione semplificata per la maggior parte dei casi d'uso.
Serge Stroobandt,

10

numpy ha una funzione di segno e ti dà anche un bonus di altre funzioni. Così:

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

Fai solo attenzione che il risultato sia numpy.float64:

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

Per cose come json, questo è importante, poiché json non sa come serializzare i tipi numpy.float64. In tal caso, potresti fare:

float(np.sign(y))

per ottenere un galleggiante normale.


10

Prova a eseguire questo, dove x è un numero qualsiasi

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

La coercizione a bool () gestisce la possibilità che l'operatore di confronto non restituisca un booleano.


Buona idea, ma penso che intendi: int_sign = int (x> 0) - int (x <0)
yucer

Intendo: int_sign = lambda x: (x> 0) - (x <0)
yucer

1
@yucer no, intendeva davvero far cantare il cast (che è comunque una sottoclasse di int), a causa della possibilità teorica di cui ha dato il collegamento alla spiegazione.
Walter Tross,

L'unico aspetto negativo di questo costrutto è che l'argomento appare due volte, il che va bene solo se si tratta di una singola variabile
Walter Tross,

5

Sì, una sign()funzione corretta dovrebbe essere almeno nel modulo di matematica, dato che è intorpidito. Perché uno spesso ne ha bisogno per il codice orientato alla matematica.

Ma math.copysign()è anche utile indipendentemente.

cmp()e obj.__cmp__()... hanno generalmente una grande importanza indipendentemente. Non solo per il codice orientato alla matematica. Considera di confrontare / ordinare tuple, oggetti data, ...

Gli argomenti dev su http://bugs.python.org/issue1640 riguardanti l'omissione di math.sign()sono strani, perché:

  • Non c'è separato -NaN
  • sign(nan) == nan senza preoccupazioni (come exp(nan))
  • sign(-0.0) == sign(0.0) == 0 senza preoccupazioni
  • sign(-inf) == -1 senza preoccupazioni

- come è in numpy


4

In Python 2, cmp()restituisce un numero intero: non è necessario che il risultato sia -1, 0 o 1, quindi sign(x)non è lo stesso di cmp(x,0).

In Python 3, cmp()è stato rimosso a favore di un confronto intenso. Per cmp(), Python 3 suggerisce questo :

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

che va bene per cmp (), ma di nuovo non può essere usato per sign () perché gli operatori di confronto non devono restituire valori booleani .

Per far fronte a questa possibilità, i risultati del confronto devono essere forzati a booleani:

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

Questo funziona per tutti quelli typeche sono totalmente ordinati (compresi valori speciali come NaNo infiniti).


0

Non ne hai bisogno, puoi semplicemente usare:

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

4
Vale la pena sottolineare che ci x / abs(x)vuole un po 'più di tempo del semplice concatenamento if/elseper verificare da che parte di 0 è attiva la variabile, o del return (x > 0) - (x < 0)boolint

1
Python tratta Truee Falsecome 1e 0, si può assolutamente fare questo e ottenere sia 1, 0o -1. def sign(x): return (x > 0) - (x < 0)non tornerà una bool, che restituisca un int- se si passa 0si otterrà 0indietro

0

Semplicemente no.

Il modo migliore per risolvere questo problema è:

sign = lambda x: bool(x > 0) - bool(x < 0)

-8

Il motivo per cui "segno" non è incluso è che se includessimo tutti gli utili one-liner nell'elenco delle funzioni integrate, Python non sarebbe più facile e pratico con cui lavorare. Se usi questa funzione così spesso, allora perché non la consideri tu stesso? Non è come se fosse lontanamente difficile o addirittura noioso farlo.


6
Bene, lo comprerei solo se abs()fosse stato lasciato fuori. sign()e abs()sono spesso usati insieme, sign()è il più utile dei due (IMO) e nessuno è remotamente difficile o noioso da implementare (anche se è soggetto a errori, vedi come questa risposta sbaglia: stackoverflow.com/questions/1986152/… )
Davide,

1
Il fatto è che il risultato numerico di sign()se stesso è raramente utile. Quello che fai la maggior parte delle volte è prendere diversi percorsi di codice in base al fatto che una variabile sia positiva o negativa, e in quel caso è più leggibile scrivere la condizione in modo esplicito.
Antoine P.

3
abs () è usato molto più spesso di quanto significhi un segno (). E ti ho indicato il tracker NumPy che mostra quanto può essere difficile implementare sign (). Cosa dovrebbe essere il segno (-3 + 4j)? Mentre abs (-3 + 4j) è 5.0. Fai affermazioni che sign () e abs () sono spesso visti insieme. La libreria standard C non ha una funzione 'segno', quindi dove stai ottenendo i tuoi dati?
Andrew Dalke,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.