Чому Python не має функції знаку?


241

Я не можу зрозуміти, чому Python не має signфункції. У нього є absвбудований (який я вважаю signсестрою), але ні sign.

У python 2.6 є навіть copysignфункція (з математики ), але ніякої ознаки. Навіщо турбуватися писати а, copysign(x,y)коли ви могли просто написати a, signа потім отримати copysignбезпосередньо з abs(x) * sign(y)? Останнє було б набагато зрозуміліше: x зі знаком y, тоді як при copysign ви повинні пам'ятати, чи це x зі знаком y або y зі знаком x!

Очевидно, sign(x)це не дає нічого іншого cmp(x,0), але було б набагато читабельніше, що це теж (і для такої читабельної мови, як пітон, це було б великим плюсом).

Якби я був дизайнером пітонів, я би був іншим способом: не cmpвбудований, а a sign. Коли вам потрібно cmp(x,y), ви можете просто зробити sign(x-y)(або, ще краще, для нечислових речей, просто x> y - звичайно, це повинно було вимагати sortedприйняття булевого, а не порівняльного числа). Це також буде більш ясно: позитивним , коли x>y( в той час як з cmpви повинні пам'ятати конвенції плюсовій , коли перший є великим , але це може бути навпаки). Звичайно, це cmpмає сенс самостійно з інших причин (наприклад, при сортуванні нечислових речей або якщо ви хочете, щоб сорт був стабільним, що неможливо використовувати просто булевим)

Отже, питання: чому дизайнери (і) Python вирішили залишити signфункцію поза мовою? Чому чорт турбує, copysignа не його батьків sign?

Я щось пропускаю?

EDIT - після коментаря Пітера Хансена. Досить справедливо, що ви його не використовували, але не сказали, для чого використовуєте python. За 7 років, що я використовую пітон, мені це потрібно було незліченну кількість разів, і останнє - солома, що зламала верблюду спину!

Так, ви можете передавати cmp навколо, але 90% разів, які мені потрібно було пройти, були в lambda x,y: cmp(score(x),score(y))такій ідіомі, як це працювало б зі знаком просто чудово.

Нарешті, я сподіваюся, ви погоджуєтесь, що це signбуло б корисніше copysign, тому, навіть якщо я купував ваше уявлення, навіщо займатись визначенням математики, а не знаком? Як може copysign бути настільки корисним, ніж підписувати?


33
@dmazzoni: чи не вдасться цей аргумент для всіх питань на цьому сайті? просто закрийте stackoverflow і задайте кожне запитання відповідній темі розробника або списку розсилки користувачів!
Девід

43
Правильне місце для запитання - це будь-яке місце, де на нього, швидше за все, відповіді. Таким чином, stackoverflow - це належне місце.
Стефано Борині

23
-1: @Davide: "Чому" і "чому ні" на запитання тут не можна відповісти. Оскільки більшість принципів розробки Python тут не відповідають на запитання, ви рідко (якщо і колись) збираєтесь отримати відповідь на питання "чому" чи "чому ні". Крім того, у вас немає проблеми вирішити. Вам здається, що у вас є зухвалість. Якщо у вас є проблема ("Як мені подолати відсутність знаку в цьому прикладі ..."), це розумно. "Чому б і ні" не є розумним для цього місця проведення.
S.Lott

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, оскільки я показав, що це лише підмножина одного функціоналу.


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) як заміна знаку (x) виявляється невдалим, оскільки він повертає 1 для x = 0, тоді як знак (0) оцінюватиме до 0.
PaulMcG

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)! Він не вважає ознакою b. Наприклад, якщо a=b=-1перший код поверне 1, тоді як другий поверне -1
Йоганнес Дендерсі,

14
Побачивши визначення заміни помилкового знака (), помилковий еквівалент множення зі знаком (а), помилкове пояснення мотивації копісигналу та правильну заміну "cmp (x, 0)" вже згадується у запитанні - є не так багато інформації, і мені незрозуміло, чому це "прийнята" відповідь з такою кількістю голосів.?
kxr

59

"copysign" визначено IEEE 754 і є частиною специфікації C99. Ось чому це в Python. Функція не може бути повністю реалізована знаком abs (x) * (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 () більш корисною функцією, ніж знак ().

Щодо конкретних причин, чому знакова панель IEEE (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 () є більш ефективною функцією, ніж функція тризначного знака ().

Ти написав:

Якби я був дизайнером пітону, я був би навпаки: не вбудований cmp (), а знак ()

Це означає, що ви не знаєте, що cmp () використовується для речей, крім цифр. cmp ("Це", "Це") неможливо реалізувати за допомогою функції sign ().

Редагувати, щоб зіставити мої додаткові відповіді в іншому місці :

Ви базуєте свої виправдання на тому, як abs () та sign () часто бачать разом. Оскільки стандартна бібліотека С не містить функції «знак (x)» будь-якого роду, я не знаю, як ви виправдовуєте свої погляди. Там abs (int) і fabs (подвійний) і fabsf (float) і fabsl (long), але жодної згадки про знак. Існують "copysign ()" та "signbit ()", але вони стосуються лише номерів IEEE 754.

Що із складними числами, що означатиме (-3 + 4j) повернення в Python, якби це було реалізовано? abs (-3 + 4j) повернути 5,0. Це наочний приклад того, як abs () можна використовувати в місцях, де знак () не має сенсу.

Припустимо, знак (x) був доданий до Python як доповнення до abs (x). Якщо 'x' - це примірник визначеного користувачем класу, який реалізує метод __abs __ (self), то abs (x) буде викликати x .__ abs __ (). Щоб правильно працювати, обробляти abs (x) так само, тоді Python повинен буде отримати знак (х) слот.

Це надмірно для відносно непотрібної функції. Крім того, чому знак (x) повинен існувати, а негативне (x) і непозитивне (x) не існувати? Мій фрагмент з реалізації математичного модуля Python показує, як copybit (x, y) може бути використаний для реалізації негативу (), що простий знак (x) не може зробити.

Python повинен підтримувати кращу підтримку математичної функції IEEE 754 / C99. Це додало б функцію вивіски (x), яка б робила те, що ви хочете у випадку плавців. Він би не працював для цілих чисел чи складних чисел, тим більше рядків, і не було б імені, яке ви шукаєте.

Ви запитуєте "чому", а відповідь - "знак (х) не корисний". Ви стверджуєте, що це корисно. Однак ваші коментарі показують, що ви не знаєте достатньо, щоб можна було зробити таке твердження, а значить, вам доведеться показати переконливі докази його потреби. Сказати, що NumPy реалізує це недостатньо переконливо. Вам потрібно буде показати випадки, як існуючий код буде покращений за допомогою функції знаків.

І що це поза сферою StackOverflow. Замість цього перейдіть до одного зі списків Python.


5
Ну, я не знаю, якщо це зробить тебе щасливим, але у Python 3 немає ні cmp()ані sign():-)
Антуан П.

4
написання функції хорошого знака (), яка б працювала належним чином з IEEE 754, не є тривіальною. Це було б гарним моментом включити його до мови, а не залишати його, хоч я і не уточнив це питання у питанні
Девіде

2
Ваш коментар про те, як "якщо ви хочете, щоб сорт був стабільним", означає, що ви також не знаєте, наскільки стабільний сорт працює. Ваша заява про те, що copysign та знак є рівнозначними, свідчать про те, що ви мало знали про математику IEEE 754 перед цим повідомленням. Чи повинен Python реалізувати всі основні математичні функції 754 в ядрі? Що робити для компіляторів, що не належать до C99? Платформи не 754? "неонегативний" і "неопозитивний" також є корисними функціями. Чи повинен також включати 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]

1
чому? Саме питання визнає, що cmp(x, 0)це рівнозначно signі lambda x: cmp(x, 0)є більш читабельним, ніж те, що ви пропонуєте.
ToolmakerSteve

1
Дійсно, я помилявся. Я припускав, що "cmp" вказано для повернення -1,0, + 1, але я бачу, що специфікація цього не гарантує.
ToolmakerSteve

Гарний. Відповідає на розпочате запитання: python int або float до -1, 0, 1?
scharfmn

1
Чи є якась перевага використовувати списки замість -1 if x < 0 else 1?
Mateen Ulhaq

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)

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
Ця відповідь ілюструє, наскільки складним, але потужним може бути пітон.
НельсонГон

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

Спробуйте запустити це, де x - будь-яке число

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), через теоретичну можливість якої він дав посилання на пояснення.
Вальтер Трос

Єдиним недоліком цієї конструкції є те, що аргумент з’являється двічі, що добре, лише якщо це одна змінна
Walter Tross

5

Так правильно sign() функція повинна бути принаймні в математичному модулі - як в нуме. Тому що часто потрібен він для математично орієнтованого коду.

Але math.copysign()також корисний і самостійно.

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

Аргументи розробника на веб-сайті http://bugs.python.org/issue1640 щодо пропуску " math.sign()незвичайні", оскільки:

  • Окремого немає -NaN
  • sign(nan) == nan без турботи (як exp(nan))
  • sign(-0.0) == sign(0.0) == 0 без турботи
  • sign(-inf) == -1 без турботи

- як у нумері


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 (), але знову не може бути використаний для знака (), оскільки операторам порівняння не потрібно повертати булі .

Щоб вирішити цю можливість, результати порівняння повинні бути примушені до булевих:

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

Це працює для будь- typeяких цілком упорядкованих (включаючи спеціальні значення, такі як NaNабо нескінченності).


0

Вам це не потрібно, ви можете просто використовувати:

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

4
Це вказує на те, що x / abs(x)потрібно трохи довше, ніж просто ланцюжок, if/elseщоб перевірити, на якій стороні 0 return (x > 0) - (x < 0)boolint

1
Python лікує Trueі , Falseяк 1і 0ви можете абсолютно зробити це і отримати або 1, 0або -1. def sign(x): return (x > 0) - (x < 0)не поверне а bool, він поверне int- якщо ви пройдете, 0ви отримаєте 0назад


-8

Причина "знак" не включена в тому, що якби ми включили кожен корисний однокласник до списку вбудованих функцій, Python більше не був би легким та практичним для роботи. Якщо ви користуєтеся цією функцією так часто, то чому б вам не зробити її самостійно? Це не так, як це віддалено важко або навіть нудно зробити це.


6
Ну, я б купував це лише у тому випадку, якщо також abs()залишився. sign()і abs()вони часто використовуються разом, sign()є найбільш корисним з двох (ІМО), і жоден не є важко або виснажливим для впровадження (навіть якщо він схильний до помилок, дивіться, як ця відповідь помилиться : stackoverflow.com/questions/1986152/… )
Девід

1
Вся справа в тому, що числовий результат sign()сам по собі рідко корисний. Що ви робите більшу частину часу, це проходити різні шляхи коду, залежно від того, чи є змінна позитивною чи негативною, і в такому випадку легше читати умову.
Антуан П.

3
abs () використовується набагато частіше, ніж знак (). І я вказав на трекер NumPy, який показує, як важкий знак () може бути реалізований. Яким повинен бути знак (-3 + 4j)? Тоді як abs (-3 + 4j) - 5,0. Ви робите твердження, що знак () і abs () часто бачите разом. Стандартна бібліотека C не має функції "знаку", тож де ви отримуєте свої дані?
Ендрю Далке
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.