Dlaczego Python nie ma funkcji znaku?

241

Nie rozumiem, dlaczego Python nie ma signfunkcji. Ma abswbudowaną (którą uważam za signsiostrę), ale niesign .

W Pythonie 2.6 jest nawet copysignfunkcja (w matematyce ), ale nie ma znaku. Po co męczyć się, aby napisać, copysign(x,y)kiedy możesz po prostu napisać a, signa następnie uzyskać copysignbezpośrednio odabs(x) * sign(y) ? To drugie byłoby znacznie wyraźniejsze: x ze znakiem y, podczas gdy przy copysign musisz pamiętać, czy to x ze znakiem y lub y ze znakiem x!

Oczywiście sign(x)nie zapewnia nic więcej niżcmp(x,0) , ale byłoby to o wiele bardziej czytelne (i dla bardzo czytelnego języka, takiego jak python, byłby to duży plus).

Gdybym był projektantem Pythona, byłbym jeszcze inny: brak cmpwbudowanego, ale sign. Gdy potrzebujesz cmp(x,y), możesz po prostu zrobić sign(x-y)(lub nawet lepiej dla liczb nienumerycznych, tylko x> y - oczywiście powinno to wymagać sortedzaakceptowania wartości logicznej zamiast komparatora liczb całkowitych). Byłoby to również bardziej jasne: pozytywne, gdy x>y(podczas gdy cmpmusisz pamiętać, że konwencja jest pozytywna, gdy pierwszy jest większy , ale może być odwrotnie). Oczywiście cmpma to sens z innych powodów (np. Podczas sortowania rzeczy nienumerycznych lub jeśli chcesz, aby sort był stabilny, co nie jest możliwe przy użyciu zwykłej wartości logicznej)

Pytanie brzmi: dlaczego projektanci Pythona postanowili zrezygnować z signfunkcji poza językiem? Dlaczego, u licha, niepokoił się, copysigna nie jego rodzic sign?

Czy coś brakuje?

EDYCJA - po komentarzu Petera Hansena. W porządku, że go nie używałeś, ale nie powiedziałeś, do czego używasz Pythona. W ciągu 7 lat używania Pythona potrzebowałem go niezliczoną ilość razy, a ostatnia to słoma, która złamała grzbiet wielbłąda!

Tak, możesz przekazać cmp, ale 90% razy, które musiałem przekazać, było w takim idiomie lambda x,y: cmp(score(x),score(y)), że działałoby to ze znakiem w porządku.

Wreszcie, mam nadzieję, że zgadzasz się, że signbyłoby to bardziej przydatne niż copysign, więc nawet jeśli kupiłem twój pogląd, po co zawracać sobie głowę definiowaniem go w matematyce zamiast znaku? W jaki sposób copysign może być tak użyteczny niż znak?

Davide
źródło
33
@dmazzoni: czy ten argument nie zadziała w przypadku wszystkich pytań na tej stronie? wystarczy zamknąć stackoverflow i zadać każde pytanie odpowiedniemu tematowi lub liście mailingowej użytkowników!
Davide,
43
Właściwym miejscem na pytanie jest każde miejsce, na które można uzyskać odpowiedź. Zatem przepełnienie stosu jest właściwym miejscem.
Stefano Borini,
23
-1: @Davide: Na pytania „dlaczego” i „dlaczego nie” na ogół nie można tutaj odpowiedzieć. Ponieważ większość zasad programowania w Pythonie nie odpowiada tutaj na pytania, rzadko (jeśli w ogóle) otrzymasz odpowiedź na pytanie „dlaczego” lub „dlaczego nie”. Co więcej, nie masz problemu do rozwiązania. Brzmisz jakbyś miał rant. Jeśli masz problem („Jak obejść brak znaku w tym przykładzie ...”), to rozsądne. „Dlaczego nie” nie ma sensu w tym miejscu.
S.Lott,
31
Pytanie może być trochę emocjonalne, ale nie sądzę, że jest to złe pytanie. Jestem pewien, że wiele osób szukało wbudowanej funkcji znakowania, więc może być ciekawe, dlaczego jej nie ma.
FogleBird,
17
To jest całkowicie obiektywne pytanie: „Dlaczego” w Pythonie brakuje jakiejkolwiek funkcji, jest to uzasadnione zapytanie dotyczące historii projektowania języka, na które można odpowiedzieć, łącząc się z odpowiednią dyskusją z python-dev lub innych forów (czasami postów na blogu), na których Python trzej programiści zdarzają się mieszać temat. Próbowałem już wcześniej wyszukiwać w Google fragmenty historii w Python-dev, rozumiem, dlaczego nowicjusz w tym języku może trafić w ślepy zaułek i przyjechać tutaj w nadziei bardziej doświadczonej osoby pytającej!
Brandon Rhodes

Odpowiedzi:

228

EDYTOWAĆ:

Rzeczywiście była łatka, która obejmowałasign() w matematyce , ale nie została zaakceptowana, ponieważ nie zgodzili się, co powinna zwrócić we wszystkich przypadkach na krawędzi (+/- 0, +/- nan itp.)

Postanowili więc wdrożyć tylko copysign, który (choć bardziej szczegółowy) może zostać użyty do przekazania użytkownikowi końcowemu pożądanego zachowania w przypadkach brzegowych - które czasami mogą wymagać wywołaniacmp(x,0) .


Nie wiem, dlaczego to nie jest wbudowane, ale mam pewne przemyślenia.

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

Co najważniejsze, copysignjest nadzbiorem sign! Wywołanie copysignprzy x = 1 jest takie samo jak signfunkcja. Więc może po prostu użyć copysigni zapomnieć o nim .

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

Jeśli masz dość przekazywania dwóch całych argumentów, możesz je wdrożyć sign ten sposób i nadal będzie on zgodny z materiałami IEEE wspomnianymi przez innych:

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

Po drugie, zwykle, gdy chcesz znak czegoś, po prostu pomnóż to przez inną wartość. I oczywiście to w zasadzie to, co copysignrobi.

Zamiast więc:

s = sign(a)
b = b * s

Możesz po prostu zrobić:

b = copysign(b, a)

I tak, jestem zaskoczony, że używasz Pythona od 7 lat i myślę, że cmpmożna go tak łatwo usunąć i zastąpić sign! Czy nigdy nie wdrożyłeś klasy z__cmp__ metody? Czy nigdy nie zadzwoniłeś cmpi nie określiłeś niestandardowej funkcji porównawczej?

Podsumowując, odkryłem, że chcę też signfunkcji, ale copysignz pierwszym argumentem 1 będzie działać dobrze. Nie zgadzam się, że signbyłoby to bardziej przydatne niż copysign, ponieważ pokazałem, że jest to tylko podzbiór tej samej funkcjonalności.

FogleBird
źródło
35
Korzystanie [int(copysign(1, zero)) for zero in (0, 0.0, -0.0)]daje [1, 1, -1]. To powinno być [0, 0, 0]zgodne z en.wikipedia.org/wiki/Sign_function
user238424
12
@Andrew - kolejność połączeń @ user238424 jest poprawna. copysign(a,b)zwraca a ze znakiem b - b jest zmiennym wejściem, a jest wartością normalizowaną do znaku b. W tym przypadku komentator pokazuje, że copysign (1, x) jako zamiennik znaku (x) kończy się niepowodzeniem, ponieważ zwraca 1 dla x = 0, podczas gdy znak (0) miałby wartość 0.
PaulMcG
7
Pływaki zawierają „znak” oddzielony od „wartości”; -0,0 jest liczbą ujemną, nawet jeśli wydaje się to błędem implementacji. Proste użycie cmp()da pożądane rezultaty, prawdopodobnie w prawie każdym przypadku, na którym wszyscy by się przejmowali: [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 * snie jest równoważne z b = copysign(b, a)! Nie uwzględnia znaku b. Np. Jeśli a=b=-1pierwszy kod zwróci 1, a drugi -1
Johannes Jendersie
14
Widząc definicję zastąpienia fałszywego znaku (), fałszywy ekwiwalent mnożenia ze znakiem (a), fałszywe wyjaśnienie motywacji copysign i poprawne zastąpienie „cmp (x, 0)” jest już wspomniane w pytaniu - istnieje niewiele informacji i nie jest dla mnie jasne, dlaczego jest to „zaakceptowana” odpowiedź z tyloma głosami.
kxr
59

„copysign” jest zdefiniowany przez IEEE 754 i stanowi część specyfikacji C99. Właśnie dlatego jest w Pythonie. Funkcji nie można w pełni zaimplementować znakiem abs (x) * (y), ponieważ ma ona obsługiwać wartości 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
>>> 

To sprawia, że ​​copysign () jest bardziej przydatną funkcją niż sign ().

Jeśli chodzi o konkretne powody, dla których znak IEEE (x) nie jest dostępny w standardowym języku Python, nie wiem. Potrafię poczynić założenia, ale byłoby to zgadywanie.

Sam moduł matematyczny używa copysign (1, x) jako sposobu sprawdzenia, czy x jest ujemne czy nieujemne. W większości przypadków zajmujących się funkcjami matematycznymi, które wydają się bardziej przydatne niż posiadanie znaku (x), który zwraca 1, 0 lub -1, ponieważ jest jeden mniej przypadku do rozważenia. Na przykład następujące informacje pochodzą z modułu matematycznego Pythona:

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

Widać tam wyraźnie, że copysign () jest bardziej efektywną funkcją niż trzywartościowa funkcja sign ().

Napisałeś:

Gdybym był projektantem Pythona, byłbym na odwrót: nie ma wbudowanego cmp (), ale sign ()

Oznacza to, że nie wiesz, że cmp () jest używany do rzeczy oprócz liczb. cmp („This”, „That”) nie można zaimplementować za pomocą funkcji sign ().

Edytuj, aby zestawić moje dodatkowe odpowiedzi w innym miejscu :

Opierasz swoje uzasadnienia na tym, jak często abs () i sign () są często postrzegane razem. Ponieważ biblioteka standardowa C nie zawiera żadnej funkcji „znak (x)”, nie wiem, jak uzasadniasz swoje poglądy. Jest abs (int) i fabs (double) i fabsf (float) i fabsl (long), ale nie ma wzmianki o znaku. Istnieją „copysign ()” i „signbit ()”, ale dotyczą one tylko numerów IEEE 754.

W przypadku liczb zespolonych, co by znak (-3 + 4j) zwrócił w Pythonie, gdyby miał zostać zaimplementowany? abs (-3 + 4j) zwraca 5.0. To wyraźny przykład użycia abs () w miejscach, w których sign () nie ma sensu.

Załóżmy, że do Pythona dodano znak (x) jako uzupełnienie abs (x). Jeśli „x” jest instancją klasy zdefiniowanej przez użytkownika, która implementuje metodę __abs __ (self), wówczas abs (x) wywoła x .__ abs __ (). Aby działać poprawnie, aby obsługiwać abs (x) w ten sam sposób, Python będzie musiał uzyskać znak szczelinę (x).

Jest to nadmierne jak na względnie niepotrzebną funkcję. Poza tym, dlaczego znak (x) powinien istnieć, a nieujemna (x) i niepodatnicza (x) nie istnieć? Mój fragment kodu z implementacji modułu matematycznego Pythona pokazuje, w jaki sposób copybit (x, y) może być użyty do implementacji wartości nieujemnej (), czego nie potrafi prosty znak (x).

Python powinien obsługiwać lepszą obsługę funkcji matematycznych IEEE 754 / C99. Dodałoby to funkcję signbit (x), która zrobiłaby co chcesz w przypadku float. Nie działałoby to dla liczb całkowitych lub liczb zespolonych, a tym bardziej dla łańcuchów i nie miałoby nazwy, której szukasz.

Pytasz „dlaczego”, a odpowiedź brzmi „znak (x) nie jest użyteczny”. Zapewniasz, że jest to przydatne. Jednak twoje komentarze pokazują, że nie wiesz wystarczająco dużo, aby móc twierdzić, co oznacza, że ​​musiałbyś wykazać przekonujące dowody na jego potrzebę. Mówienie, że NumPy to implementuje, nie jest wystarczająco przekonujące. Musisz pokazać przypadki, w których istniejący kod zostałby ulepszony za pomocą funkcji znaku.

I że wykracza to poza StackOverflow. Zamiast tego przenieś go na jedną z list w języku Python.

Andrew Dalke
źródło
5
Cóż, nie wiem czy to cię uszczęśliwi, ale Python 3 nie ma cmp()ani sign():-)
Antoine P.
4
napisanie funkcji good sign (), która działałaby poprawnie z IEEE 754, nie jest trywialne. Byłoby to dobre miejsce, aby włączyć to do języka, zamiast go pomijać, nawet jeśli nie rozwinąłem tego punktu w pytaniu
Davide,
2
Twój komentarz na temat tego, jak „jeśli chcesz, aby sort był stabilny” oznacza, że ​​nie wiesz również, jak działa stabilne sortowanie. Twoje oświadczenie, że copysign i znak są równoważne, pokazuje, że nie wiedziałeś wiele o matematyce IEEE 754 przed tym postem. Czy Python powinien implementować wszystkie funkcje matematyczne 754 w rdzeniu? Co powinien zrobić w przypadku kompilatorów innych niż C99? Platformy inne niż 754? „isnonnegative” i „isnonpositive” są również przydatnymi funkcjami. Czy Python powinien je również uwzględniać? abs (x) odkłada na x .__ abs __ (), więc znak (x) powinien odraczać na x .__ znak __ ()? Jest mało popytu lub potrzeby, więc dlaczego miałby być w centrum?
Andrew Dalke,
2
math.copysign (1, float ("- nan")) zwraca 1,0 zamiast -1,0, gdy próbuję w wersji 2.7
dansalmo
34

Kolejna linijka dla sign ()

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

Jeśli chcesz, aby zwracał 0 dla x = 0:

sign = lambda x: x and (1, -1)[x<0]
dansalmo
źródło
1
czemu? Samo pytanie potwierdza, że cmp(x, 0)jest ono równoważne signi lambda x: cmp(x, 0)jest bardziej czytelne niż sugerujesz.
ToolmakerSteve,
1
Rzeczywiście się myliłem. Zakładałem, że „cmp” został określony, aby zwrócić -1,0, + 1, ale widzę, że specyfikacja tego nie gwarantuje.
ToolmakerSteve
Piękny. Odpowiedzi na rozpoczęte pytanie: python int lub float na -1, 0, 1?
scharfmn
1
Czy jest jakaś korzyść z używania list zamiast -1 if x < 0 else 1?
Mateen Ulhaq
6
sign = lambda x: -1 if x < 0 else 1jest 15% szybszy . To samo z sign = lambda x: x and (-1 if x < 0 else 1).
Mateen Ulhaq
26

Ponieważ cmpzostał usunięty , możesz uzyskać tę samą funkcjonalność dzięki

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

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

Działa float, inta nawet Fraction. W przypadku floatzawiadomienia sign(float("nan"))zero.

Python nie wymaga, aby porównania zwracały wartość logiczną, a więc wymuszanie porównań na bool () chroni przed dopuszczalną, ale rzadką implementacją:

def sign(a):
    return bool(a > 0) - bool(a < 0)
AllenT
źródło
13

Tylko poprawna odpowiedź zgodna z definicją Wikipedii

Definicja Wikipedia czytamy:

definicja znaku

W związku z tym,

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

Który dla wszystkich celów i celów można uprościć:

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

Ta definicja funkcji działa szybko i daje wynik zapewnia prawidłowe wyniki dla 0, 0,0, -0,0, -4 i 5 (patrz komentarze do innych niepoprawnych odpowiedzi).

Zauważ, że zero (0) nie jest ani dodatnie, ani ujemne .

Serge Stroobandt
źródło
1
Ta odpowiedź pokazuje, jak zwięzły, ale potężny może być Python.
NelsonGon
1
Quibble: Kod nie implementuje definicji WP, zastępuje klauzulę środkową klauzulą ​​domyślną na końcu. Chociaż jest to konieczne do obsługi liczb nierealnych, takich jak nan, jest błędnie pokazane jako bezpośrednio po wyrażeniu WP („stąd”).
Jürgen Strobel
1
@ JürgenStrobel Wiem dokładnie, co masz na myśli i od dawna zastanawiam się nad tym problemem. Rozszerzyłem teraz odpowiedź na poprawny formalizm, zachowując jednocześnie uproszczoną wersję dla większości przypadków użycia.
Serge Stroobandt,
10

numpy ma funkcję znaku i daje ci również bonus innych funkcji. Więc:

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

Uważaj tylko, aby wynik był liczbą numpy.float64:

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

W przypadku rzeczy takich jak json ma to znaczenie, ponieważ json nie wie, jak serializować typy numpy.float64. W takim przypadku możesz wykonać:

float(np.sign(y))

aby uzyskać zwykły pływak.

Luca
źródło
10

Spróbuj uruchomić to, gdzie x jest dowolną liczbą

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

Koercja na bool () obsługuje możliwość, że operator porównania nie zwraca wartości logicznej.

Eric Song
źródło
Dobry pomysł, ale myślę, że masz na myśli: int_sign = int (x> 0) - int (x <0)
yucer
Mam na myśli: int_sign = lambda x: (x> 0) - (x <0)
yumper
1
@ yucer no, naprawdę miał na myśli obsadę bool (która i tak jest podklasą int), ze względu na teoretyczną możliwość, której podał link do wyjaśnienia.
Walter Tross,
Jedynym minusem tego konstruktu jest to, że argument pojawia się dwa razy, co jest w porządku tylko wtedy, gdy jest to jedna zmienna
Walter Tross,
5

Tak, poprawna sign()funkcja powinna znajdować się przynajmniej w module matematycznym - tak jak w numpy. Ponieważ często jest potrzebny do kodu zorientowanego na matematykę.

Ale math.copysign()jest także przydatny niezależnie.

cmp()i obj.__cmp__()... mają ogólnie duże znaczenie niezależnie. Nie tylko dla kodu zorientowanego na matematykę. Rozważ porównanie / sortowanie krotek, obiektów daty, ...

Argumenty deweloperów na http://bugs.python.org/issue1640 dotyczące pominięcia math.sign()są dziwne, ponieważ:

  • Nie ma oddzielnego -NaN
  • sign(nan) == nan bez obaw (jak exp(nan))
  • sign(-0.0) == sign(0.0) == 0 bez obaw
  • sign(-inf) == -1 bez obaw

- jak to jest w numpy

kxr
źródło
4

W Pythonie 2 cmp()zwraca liczbę całkowitą: nie ma wymogu, aby wynik wynosił -1, 0 lub 1, więc sign(x)nie jest taki sam jakcmp(x,0) .

W Pythonie 3 cmp()został usunięty na rzecz bogatego porównania. Dla cmp()Python 3 sugeruje to :

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

co jest w porządku dla cmp (), ale znowu nie może być użyte do sign (), ponieważ operatory porównania nie muszą zwracać boolanów .

Aby poradzić sobie z tą możliwością, wyniki porównania muszą zostać wymuszone na logiczne:

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

Działa to dla wszystkich, typektóre są całkowicie uporządkowane (w tym specjalne wartości, takie jak NaNlub nieskończoności).

AllenT
źródło
0

Nie potrzebujesz jednego, możesz po prostu użyć:

If not number == 0:
    sig = number/abs(number)
else:
    sig = 0
fantom
źródło
4
Warto zauważyć, że x / abs(x)zajmuje to nieco więcej czasu niż tylko łańcuchowe if/elsesprawdzenie, po której stronie 0 jest zmienna, lub w tym przypadku za pomocą obleśnego, ale satysfakcjonującego, return (x > 0) - (x < 0)aby odjąć boolwartości i zwrócićint
1
Traktuje Python Truei Falsejak 1i 0można absolutnie to zrobić i dostać albo 1, 0albo -1. def sign(x): return (x > 0) - (x < 0)nie zwróci a bool, zwróci i int- jeśli zdasz 0, 0wrócisz
0

Po prostu nie.

Najlepszym sposobem na rozwiązanie tego jest:

sign = lambda x: bool(x > 0) - bool(x < 0)
Michel de Ruiter
źródło
-8

Powodem, dla którego „znak” nie jest uwzględniony, jest to, że gdybyśmy umieścili każdy przydatny jednowierszowy na liście wbudowanych funkcji, Python nie byłby łatwy i praktyczny do pracy z nim. Jeśli używasz tej funkcji tak często, dlaczego jej nie rozliczyć? Nie jest to wcale trudne, ani nawet żmudne.

Antoine P.
źródło
6
Cóż, kupiłbym to tylko wtedy, gdyby abs()zostało pominięte. sign()i abs()są często używane razem, sign()jest najbardziej przydatny z nich (IMO) i żaden z nich nie jest zbyt trudny ani żmudny do wdrożenia (nawet jeśli jest podatny na błędy, zobacz, jak ta odpowiedź jest błędna: stackoverflow.com/questions/1986152/... )
Davide,
1
Chodzi o to, że sam wynik liczbowy sign()rzadko jest użyteczny. Najczęściej zajmujesz się różnymi ścieżkami kodu w zależności od tego, czy zmienna jest dodatnia czy ujemna, i w takim przypadku bardziej czytelne jest wyraźne zapisanie warunku.
Antoine P.
3
abs () jest używane znacznie częściej niż znak (). I wskazałem ci tracker NumPy, który pokazuje, jak trudne może być wdrożenie znaku (). Czym powinien być znak (-3 + 4j)? Podczas gdy abs (-3 + 4j) wynosi 5.0. Stawiasz twierdzenia, że ​​sign () i abs () są często spotykane razem. Biblioteka standardowa C nie ma funkcji „podpisywania”, więc skąd bierzesz swoje dane?
Andrew Dalke,