Why doesn't Python have a sign function?

Davide picture Davide · Dec 31, 2009 · Viewed 153.9k times · Source

I can't understand why Python doesn't have a sign function. It has an abs builtin (which I consider sign's sister), but no sign.

In python 2.6 there is even a copysign function (in math), but no sign. Why bother to write a copysign(x,y) when you could just write a sign and then get the copysign directly from abs(x) * sign(y)? The latter would be much more clear: x with the sign of y, whereas with copysign you have to remember if it's x with the sign of y or y with the sign of x!

Obviously sign(x) does not provide anything more than cmp(x,0), but it would be much more readable that this too (and for a greatly readable language like python, this would have been a big plus).

If I were a python designer, I would been the other way arond: no cmp builtin, but a sign. When you need cmp(x,y), you could just do a sign(x-y) (or, even better for non-numerical stuff, just a x>y - of course this should have required sorted accepting a boolean instead of an integer comparator). This would also be more clear: positive when x>y (whereas with cmp you have to remember the convention positive when the first is bigger, but it could be the other way around). Of course cmp makes sense in its own for other reasons (e.g. when sorting non-numerical things, or if you want the sort to be stable, which is not possible using with simply a boolean)

So, the question is: why did the Python designer(s) decide to leave the sign function out of the language? Why the heck bother with copysign and not its parent sign?

Am I missing something?

EDIT - after Peter Hansen comment. Fair enough that you didn't use it, but you didn't say what you use python for. In 7 years that I use python, I needed it countless times, and the last is the straw that broke the camel's back!

Yes, you can pass cmp around, but 90% of the times that I needed to pass it was in an idiom like lambda x,y: cmp(score(x),score(y)) that would have worked with sign just fine.

Finally, I hope you agree that sign would be more useful than copysign, so even if I bought your view, why bother about defining that in math, instead of sign? How can copysign be so much useful than sign?

Answer

FogleBird picture FogleBird · Dec 31, 2009

EDIT:

Indeed there was a patch which included sign() in math, but it wasn't accepted, because they didn't agree on what it should return in all the edge cases (+/-0, +/-nan, etc)

So they decided to implement only copysign, which (although more verbose) can be used to delegate to the end user the desired behavior for edge cases - which sometimes might require the call to cmp(x,0).


I don't know why it's not a built-in, but I have some thoughts.

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

Most importantly, copysign is a superset of sign! Calling copysign with x=1 is the same as a sign function. So you could just use copysign and forget about it.

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

If you get sick of passing two whole arguments, you can implement sign this way, and it will still be compatible with the IEEE stuff mentioned by others:

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

Secondly, usually when you want the sign of something, you just end up multiplying it with another value. And of course that's basically what copysign does.

So, instead of:

s = sign(a)
b = b * s

You can just do:

b = copysign(b, a)

And yes, I'm surprised you've been using Python for 7 years and think cmp could be so easily removed and replaced by sign! Have you never implemented a class with a __cmp__ method? Have you never called cmp and specified a custom comparator function?

In summary, I've found myself wanting a sign function too, but copysign with the first argument being 1 will work just fine. I disagree that sign would be more useful than copysign, as I've shown that it's merely a subset of the same functionality.