Почему в Python нет функции знака?

241

Я не могу понять, почему у Python нет signфункции. Он имеет absвстроенную функцию (которую я считаю signсестрой), но нет sign.

В Python 2.6 есть даже copysignфункция (по математике ), но без знака. Зачем писать, copysign(x,y)когда вы можете просто написать, signа затем получить copysignнепосредственно от abs(x) * sign(y)? Последнее было бы намного понятнее: x со знаком y, тогда как с copysign вы должны помнить, если это x со знаком y или y со знаком x!

Очевидно sign(x), ничего более не дает cmp(x,0), но это было бы гораздо более читабельно, чем это тоже (и для очень удобочитаемого языка, такого как python, это было бы большим плюсом).

Если бы я был дизайнером Python, я был бы по-другому: не cmpвстроенный, а a sign. Когда вам нужно cmp(x,y), вы можете просто сделать a sign(x-y)(или, что еще лучше для нечисловых вещей, просто x> y - конечно, для этого нужно было sortedпринять логическое значение вместо целочисленного компаратора). Это также будет более ясно: положительным , когда x>y( в то время как с cmpвы должны помнить конвенции плюсовой , когда первый является большим , но это может быть наоборот). Конечно, cmpимеет смысл сам по себе по другим причинам (например, при сортировке нечисловых вещей или если вы хотите, чтобы сортировка была стабильной, что невозможно использовать просто с логическим значением)

Итак, вопрос: почему разработчики Python решили оставить signфункцию вне языка? Почему, черт возьми, беспокоиться, copysignа не его родитель sign?

Я что-то упускаю?

РЕДАКТИРОВАТЬ - после комментария Питера Хансена. Достаточно справедливо, что вы не использовали его, но вы не сказали, для чего вы используете python. За 7 лет, что я использую питон, я нуждался в нем бесчисленное количество раз, и последняя - соломинка, которая сломала спину верблюду!

Да, вы можете передавать cmp, но в 90% случаев, когда мне нужно было передать его, была такая идиома lambda x,y: cmp(score(x),score(y)), которая отлично работала бы со знаком.

Наконец, я надеюсь, что вы согласны с тем, что это signбыло бы более полезно, чем copysign, поэтому, даже если бы я купил ваше мнение, зачем беспокоиться об определении этого в математике, а не в знаке? Как может copysign быть настолько полезным, чем sign?

Davide
источник
33
@dmazzoni: этот аргумент не будет работать для всех вопросов на этом сайте? просто закройте stackoverflow и задайте каждый вопрос соответствующей теме dev или списку рассылки пользователя!
Давиде
43
Подходящим местом для вопроса является любое место, где на него можно ответить. Таким образом, stackoverflow - это правильное место.
Стефано Борини
23
-1: @Davide: вопросы «почему» и «почему нет», как правило, здесь невозможно ответить. Поскольку большинство принципов разработки Python здесь не отвечают на вопросы, вы редко (если вообще) собираетесь получить ответ на вопрос «почему» или «почему нет». Кроме того, вам не нужно решать проблему. Похоже, у вас есть напыщенная речь. Если у вас есть проблема («Как мне обойти отсутствие знака в этом примере ...»), это разумно. «Почему нет» не имеет смысла для этого места.
С.Лотт
31
Вопрос может быть немного эмоциональным, но я не думаю, что это плохой вопрос. Я уверен, что многие люди искали встроенную функцию знака, поэтому может быть любопытно, почему ее нет.
FogleBird
17
Это совершенно объективный вопрос: «Почему» в Python отсутствует какая-либо особенность - это законный запрос об истории языкового дизайна, на который можно ответить, связавшись с соответствующим обсуждением на python-dev или других форумах (иногда в блогах), где Python основные разработчики случайно выделили тему. Попробовав в Google некоторые кусочки истории в Python-dev и раньше, я могу понять, почему новичок в этом языке может зайти в тупик и спросить здесь в надежде, что более опытный Python ответит!
Брэндон Родс

Ответы:

228

РЕДАКТИРОВАТЬ:

Действительно, был патч, который включал sign()в математику , но он не был принят, потому что они не согласились с тем, что он должен возвращать во всех крайних случаях (+/- 0, +/- nan и т. Д.)

Поэтому они решили реализовать только copysign, который (хотя и более многословный) можно использовать для делегирования конечному пользователю желаемого поведения для крайних случаев - что иногда может потребовать вызоваcmp(x,0) .


Я не знаю, почему он не встроенный, но у меня есть некоторые мысли.

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

Самое главное, copysignэто суперсет из sign! Вызов copysignс x = 1 аналогичен вызову signфункции. Таким образом, вы можете просто использовать copysignи забыть об этом .

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

Если вам надоело передавать два целых аргумента, вы можете реализовать signэтот способ, и он все равно будет совместим с материалом IEEE, упомянутым другими:

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

Во-вторых, обычно, когда вам нужен знак чего-либо, вы просто умножаете его на другое значение. И, конечно, это в основном то, что copysignделает.

Итак, вместо:

s = sign(a)
b = b * s

Вы можете просто сделать:

b = copysign(b, a)

И да, я удивлен, что вы используете Python 7 лет и думаете, что cmpего так легко удалить и заменить на sign! Вы никогда не реализовывали класс с помощью __cmp__метода? Вы никогда не вызывали cmpи не указывали пользовательскую функцию сравнения?

Таким образом, я обнаружил, что тоже хочу signфункцию, но copysignс первым аргументом 1 будет работать нормально. Я не согласен, что это signбыло бы более полезно, чем copysign, поскольку я показал, что это просто подмножество той же функциональности.

FogleBird
источник
35
Использование [int(copysign(1, zero)) for zero in (0, 0.0, -0.0)]дает [1, 1, -1]. Это должно было быть в [0, 0, 0]соответствии с en.wikipedia.org/wiki/Sign_function
user238424
12
@Andrew - @ user238424 порядок вызова правильный. copysign(a,b)возвращает a со знаком b - b - переменный ввод, a - значение, которое нужно нормализовать со знаком b. В этом случае комментатор иллюстрирует, что copysign (1, x) в качестве замены для sign (x) завершается неудачно, так как он возвращает 1 для x = 0, тогда как sign (0) будет
иметь
7
Поплавки держат «знак» отдельно от «значения»; -0.0 - отрицательное число, даже если это кажется ошибкой реализации. Простое использование cmp()даст желаемые результаты, вероятно, почти для каждого случая, о котором все будут заботиться: [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 * sне эквивалентно b = copysign(b, a)! Это не считает знак б. Например, если a=b=-1первый код вернет 1, в то время как второй вернет -1
Йоханнес Джендерси
14
Видя определение замены ложного знака (), ложный эквивалент умножения со знаком (а), ложное объяснение мотивации copysign и правильную замену "cmp (x, 0)", уже упоминавшуюся в вопросе, - есть не так много информации, и мне неясно, почему это «принятый» ответ с таким большим количеством голосов.
KXR
60

«copysign» определяется IEEE 754 и частью спецификации C99. Вот почему это в Python. Функция не может быть полностью реализована с помощью abs (x) * sign (y) из-за того, как она должна обрабатывать значения 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
>>> 

Это делает copysign () более полезной функцией, чем sign ().

Что касается конкретных причин, по которым IEEE signbit (x) не доступен в стандартном Python, я не знаю. Я могу делать предположения, но это было бы предположение.

Сам математический модуль использует copysign (1, x) как способ проверки, является ли x отрицательным или неотрицательным. В большинстве случаев иметь дело с математическими функциями, которые кажутся более полезными, чем иметь знак (x), который возвращает 1, 0 или -1, потому что есть еще один случай для рассмотрения. Например, следующее из математического модуля 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);

Там вы можете ясно увидеть, что функция copysign () является более эффективной, чем трехзначная функция sign ().

Вы написали:

Если бы я был дизайнером Python, я был бы наоборот: не встроенный cmp (), а sign ()

Это означает, что вы не знаете, что cmp () используется не только для чисел, но и для других вещей. cmp ("This", "That") нельзя реализовать с помощью функции sign ().

Изменить, чтобы сопоставить мои дополнительные ответы в другом месте :

Вы обосновываете, что abs () и sign () часто рассматриваются вместе. Поскольку стандартная библиотека C не содержит какой-либо функции sign (x), я не знаю, как вы обосновываете свои взгляды. Есть abs (int) и fabs (double) и fabsf (float) и fabsl (long), но нет упоминания о знаке. Существуют «copysign ()» и «signbit ()», но они применяются только к номерам IEEE 754.

Что бы сложное число возвращало знак (-3 + 4j) в Python? abs (-3 + 4j) возврат 5.0. Это наглядный пример того, как abs () может использоваться в местах, где sign () не имеет смысла.

Предположим, что знак (x) был добавлен в Python как дополнение к abs (x). Если 'x' является экземпляром пользовательского класса, который реализует метод __abs __ (self), то abs (x) вызовет x .__ abs __ (). Для правильной работы, для обработки abs (x) таким же образом, Python должен получить слот sign (x).

Это чрезмерно для относительно ненужной функции. Кроме того, почему знак (x) должен существовать, а неотрицательный (x) и неположительный (x) не существует? Мой фрагмент из реализации математического модуля Python показывает, как copybit (x, y) можно использовать для реализации nonnegative (), чего не может сделать простой знак (x).

Python должен поддерживать лучшую поддержку математической функции IEEE 754 / C99. Это добавит функцию signbit (x), которая будет делать то, что вы хотите в случае с плавающей точкой. Он не будет работать для целых чисел или комплексных чисел, а тем более для строк, и не будет иметь имя, которое вы ищете.

Вы спрашиваете «почему», а ответ «знак (х) бесполезен». Вы утверждаете, что это полезно. Тем не менее, ваши комментарии показывают, что вы не знаете достаточно, чтобы иметь возможность сделать такое утверждение, а это значит, что вам придется показать убедительные доказательства его необходимости. Сказать, что NumPy реализует это, недостаточно убедительно. Вам нужно будет показать случаи улучшения существующего кода с помощью функции знака.

И что это выходит за рамки StackOverflow. Взять его вместо одного из списков Python.

Эндрю Далке
источник
5
Ну, если не так, то это сделает вас счастливыми, но в Python 3 нет cmp()ни sign():-)
Antoine P.
4
Написание хорошей функции sign (), которая бы правильно работала с IEEE 754, не тривиально. Было бы неплохо включить его в язык, а не пропустить, хотя я не уточнил этот вопрос в вопросе
Davide
2
Ваш комментарий о том, что «если вы хотите, чтобы сортировка была стабильной» означает, что вы также не знаете, как работает стабильная сортировка. Ваше заявление о том, что copysign и подпись эквивалентны, показывает, что вы не много знали о математике IEEE 754 до этой публикации. Должен ли Python реализовать все 754 математических функции в ядре? Что он должен делать для не-C99 компиляторов? Не-754 платформы? «isnonnegative» и «isnonpositive» также являются полезными функциями. Должен ли Python также включать их? abs (x) откладывается до x .__ abs __ (), поэтому должен ли знак (x) откладывать до x .__ знак __ ()? Существует небольшой спрос или потребность в этом, так почему же он должен застрять в ядре?
Эндрю Далке
2
math.copysign (1, float ("- nan")) возвращает 1.0 вместо -1.0, когда я пробую это в 2.7
dansalmo
34

Еще один лайнер для знака ()

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

Если вы хотите, чтобы он возвращал 0 для x = 0:

sign = lambda x: x and (1, -1)[x<0]
dansalmo
источник
1
Зачем? Сам вопрос признает, что cmp(x, 0)это эквивалентно signи lambda x: cmp(x, 0)более читабельно, чем вы предлагаете.
ToolmakerSteve
1
Действительно, я был неправ. Я предполагал, что 'cmp' был указан для возврата -1,0, + 1, но я вижу, что спецификация не гарантирует этого.
ToolmakerSteve
Прекрасный. Отвечает на запущенный вопрос: python int или float to -1, 0, 1?
scharfmn
1
Есть ли преимущество в использовании списков вместо -1 if x < 0 else 1?
Матин Улхак,
6
sign = lambda x: -1 if x < 0 else 1на 15% быстрее . То же самое с sign = lambda x: x and (-1 if x < 0 else 1).
Матин Улхак
26

Поскольку cmpбыл удален , вы можете получить ту же функциональность с

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

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

Она работает float, intи даже Fraction. В случае float, уведомление sign(float("nan"))равно нулю.

Python не требует, чтобы сравнения возвращали логическое значение, и поэтому приведение сравнений к bool () защищает от допустимой, но необычной реализации:

def sign(a):
    return bool(a > 0) - bool(a < 0)
AllenT
источник
13

Только правильный ответ, соответствующий определению Википедии

Определение в Википедии гласит:

определение знака

Следовательно,

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

Что для всех намерений и целей может быть упрощено до:

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

Это определение функции выполняется быстро и дает гарантированные правильные результаты для 0, 0.0, -0.0, -4 и 5 (см. Комментарии к другим неправильным ответам).

Обратите внимание, что ноль (0) не является ни положительным, ни отрицательным .

Серж Строобандт
источник
1
Этот ответ показывает, насколько лаконичным, но мощным может быть Python.
НельсонГон
1
Quibble: код не реализует определение WP, он заменяет среднее предложение на предложение по умолчанию в конце. Хотя это необходимо для обработки не вещественных чисел, таких как nan, оно ошибочно показано как непосредственно следующее за оператором WP («Следовательно»).
Юрген Штробель
1
@ JürgenStrobel Я точно знаю, что вы имеете в виду, и я также давно обдумывал этот вопрос. Я расширил ответ теперь для правильного формализма, сохраняя упрощенную версию для большинства случаев использования.
Серж
10

Numpy имеет функцию знака, а также дает вам бонус к другим функциям. Так:

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

Только будьте осторожны, что результатом будет numpy.float64:

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

Для таких вещей, как json, это важно, поскольку json не знает, как сериализовать типы numpy.float64. В этом случае вы можете сделать:

float(np.sign(y))

чтобы получить регулярное плавание.

Лука
источник
10

Попробуйте запустить это, где х любое число

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

Приведение к bool () обрабатывает возможность того, что оператор сравнения не возвращает логическое значение.

Эрик Сонг
источник
Хорошая идея, но я думаю, что вы имеете в виду: int_sign = int (x> 0) - int (x <0)
yucer
Я имею в виду: int_sign = lambda x: (x> 0) - (x <0)
yucer
1
@yucer нет, он действительно имел в виду приведение к bool (который в любом случае является подклассом int), из-за теоретической возможности которого он дал ссылку на объяснение.
Уолтер Тросс
Единственным недостатком этой конструкции является то, что аргумент появляется дважды, что хорошо, только если это одна переменная
Уолтер Тросс
5

Да, правильная sign()функция должна быть, по крайней мере, в математическом модуле - как в numpy. Потому что это часто нужно для математического кода.

Но math.copysign()также полезно самостоятельно.

cmp()и obj.__cmp__()... как правило, имеют большое значение независимо друг от друга. Не только для математического кода. Рассмотрите сравнение / сортировку кортежей, объектов даты, ...

Аргументы dev на http://bugs.python.org/issue1640 относительно упущения math.sign()являются странными, потому что:

  • Там нет отдельного -NaN
  • sign(nan) == nan без беспокойства (как exp(nan))
  • sign(-0.0) == sign(0.0) == 0 без беспокойства
  • sign(-inf) == -1 без беспокойства

- как это в NumPy

KXR
источник
4

В Python 2 cmp()возвращает целое число: не требуется, чтобы результат был -1, 0 или 1, поэтому sign(x)он не совпадает с cmp(x,0).

В Python 3 cmp()была удалена в пользу богатого сравнения. Для cmp(), Python 3 предлагает это :

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

что хорошо для cmp (), но опять же не может использоваться для sign (), потому что операторам сравнения не нужно возвращать логические значения .

Чтобы справиться с этой возможностью, результаты сравнения должны быть приведены к логическим значениям:

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

Это работает для любого, typeкоторый полностью упорядочен (включая специальные значения как NaNили бесконечности).

AllenT
источник
0

Вам не нужно, вы можете просто использовать:

If not number == 0:
    sig = number/abs(number)
else:
    sig = 0
inetphantom
источник
4
Следует отметить, что для проверки, на какой стороне 0 находится переменная , x / abs(x)требуется немного больше времени, чем просто цепочка if/else, или, если уж на то пошло, используется слизистый, но все же удовлетворительный, return (x > 0) - (x < 0)для вычитания boolзначений и возвратаint
1
Python лечит Trueи , Falseкак 1и 0вы можете абсолютно сделать это и получить либо 1, 0или -1. def sign(x): return (x > 0) - (x < 0)не вернет bool, он вернет int- если вы пройдете, 0вы 0вернетесь
0

Это просто не так.

Лучший способ это исправить:

sign = lambda x: bool(x > 0) - bool(x < 0)
Мишель де Рюитер
источник
-8

Причина, по которой «знак» не включен, заключается в том, что если бы мы включили все полезные однострочные символы в список встроенных функций, Python больше не был бы легким и практичным для работы с ним. Если вы используете эту функцию так часто, то почему бы вам не сделать это самостоятельно? Это не так сложно или даже утомительно.

Антуан П.
источник
6
Ну, я бы купил это только если abs()не учтено. sign()и abs()часто используются вместе, sign()является наиболее полезным из двух (IMO), и ни один из них не является труднодоступным или трудоемким для реализации (хотя он подвержен ошибкам, посмотрите, как этот ответ ошибается: stackoverflow.com/questions/1986152/… )
Davide
1
Дело в том, что численный результат sign()сам по себе редко полезен. В большинстве случаев вы выбираете разные пути кода в зависимости от того, является ли переменная положительной или отрицательной, и в этом случае более удобно читать условие в явном виде.
Антуан П.
3
abs () используется гораздо чаще, чем знак (). И я указал вам на трекер NumPy, который показывает, насколько сложно реализовать sign (). Каким должен быть знак (-3 + 4j)? При этом abs (-3 + 4j) составляет 5,0. Вы делаете утверждения, что sign () и abs () часто видны вместе. Стандартная библиотека C не имеет функции 'sign', так откуда вы берете свои данные?
Эндрю Далке