Mengapa Python tidak memiliki fungsi tanda?

241

Saya tidak mengerti mengapa Python tidak memiliki signfungsi. Itu memilikiabs builtin (yang saya anggap signsaudara perempuan), tetapi tidaksign .

Dalam python 2.6 bahkan ada copysignfungsi (dalam matematika ), tetapi tidak ada tanda. Mengapa repot - repot menulis copysign(x,y)ketika Anda hanya bisa menulis signdan kemudian mendapatkancopysign langsung dariabs(x) * sign(y) ? Yang terakhir akan jauh lebih jelas: x dengan tanda y, sedangkan dengan copysign Anda harus ingat apakah x dengan tanda y atau y dengan tanda x!

Jelas sekali sign(x) tidak memberikan apa pun selaincmp(x,0) , tetapi akan jauh lebih mudah dibaca bahwa ini juga (dan untuk bahasa yang sangat mudah dibaca seperti python, ini akan menjadi nilai tambah yang besar).

Jika saya seorang desainer python, saya akan menjadi sebaliknya: tidak ada cmpbuiltin, tapi a sign. Ketika Anda membutuhkan cmp(x,y), Anda bisa melakukan sign(x-y)(atau, bahkan lebih baik untuk hal-hal non-numerik, hanya x> y - tentu saja ini seharusnya mengharuskan sortedmenerima boolean daripada pembanding integer). Ini juga akan lebih jelas: positif ketika x>y(sedangkan dengan cmpAnda harus ingat konvensi positif ketika pertama adalah lebih besar , tapi bisa jalan di sekitar lainnya). Tentu sajacmp masuk akal sendiri karena alasan lain (misalnya ketika mengurutkan hal-hal non-numerik, atau jika Anda ingin jenis menjadi stabil, yang tidak mungkin digunakan hanya dengan boolean)

Jadi, pertanyaannya adalah: mengapa perancang Python memutuskan untuk meninggalkan signfungsi ini dari bahasa? Kenapa sih repot-repot dengan copysigndan bukan induknya sign?

Apakah saya melewatkan sesuatu?

EDIT - setelah Peter Hansen berkomentar. Cukup adil sehingga Anda tidak menggunakannya, tetapi Anda tidak mengatakan untuk apa Anda menggunakan python. Dalam 7 tahun saya menggunakan python, saya membutuhkannya berkali-kali, dan yang terakhir adalah sedotan yang mematahkan punggung unta!

Ya, Anda dapat melewati cmp sekitar, tetapi 90% dari waktu yang saya butuhkan untuk lulus itu dalam ungkapan seperti lambda x,y: cmp(score(x),score(y))itu akan bekerja dengan tanda baik-baik saja.

Akhirnya, saya harap Anda setuju bahwa signakan lebih bermanfaat daripada copysign, jadi bahkan jika saya membeli pandangan Anda, mengapa repot-repot mendefinisikannya dalam matematika, alih-alih tanda? Bagaimana copysign lebih berguna daripada tanda?

Davide
sumber
33
@dmazzoni: tidakkah argumen ini cocok untuk semua pertanyaan di situs ini? tutup saja stackoverflow dan ajukan setiap pertanyaan ke dev topik yang relevan atau milis pengguna!
Davide
43
Tempat yang tepat untuk pertanyaan adalah tempat mana pun yang kemungkinan akan dijawab. Dengan demikian, stackoverflow adalah tempat yang tepat.
Stefano Borini
23
-1: @Davide: pertanyaan "Mengapa" dan "Mengapa tidak" umumnya tidak dapat dijawab di sini. Karena sebagian besar kepala sekolah pengembangan Python tidak menjawab pertanyaan di sini, Anda jarang (jika pernah) akan mendapatkan jawaban untuk pertanyaan "mengapa" atau "mengapa tidak". Selanjutnya, Anda tidak memiliki masalah untuk dipecahkan. Anda terdengar seperti kata-kata kasar. Jika Anda memiliki masalah ("Bagaimana saya mengatasi kekurangan tanda dalam contoh ini ...") itu masuk akal. "Kenapa tidak" tidak masuk akal untuk venue ini.
S.Lott
31
Pertanyaannya mungkin agak emosional, tapi saya pikir itu bukan pertanyaan yang buruk. Saya yakin banyak orang telah mencari fungsi tanda bawaan, jadi bisa jadi penasaran mengapa tidak ada.
FogleBird
17
Ini adalah pertanyaan yang sangat obyektif: "Mengapa" Python tidak memiliki fitur yang diberikan adalah permintaan yang sah tentang sejarah desain bahasa yang dapat dijawab dengan menghubungkan ke diskusi yang sesuai dari python-dev atau forum lain (kadang-kadang posting blog) di mana Python pengembang inti kebetulan hash topik keluar. Setelah mencoba Google untuk bit sejarah di python-dev sendiri sebelumnya, saya bisa mengerti mengapa pendatang baru ke bahasa mungkin menemui jalan buntu dan datang bertanya di sini dengan harapan orang Python yang lebih berpengalaman menjawab!
Brandon Rhodes

Jawaban:

228

EDIT:

Memang ada tambalan yang termasuk sign()dalam matematika , tetapi itu tidak diterima, karena mereka tidak setuju tentang apa yang harus dikembalikan dalam semua kasus tepi (+/- 0, +/- nan, dll)

Jadi mereka memutuskan untuk mengimplementasikan hanya copysign, yang (walaupun lebih banyak verbose) dapat digunakan untuk mendelegasikan kepada pengguna akhir perilaku yang diinginkan untuk kasus tepi - yang kadang-kadang mungkin memerlukan panggilan untukcmp(x,0) .


Saya tidak tahu mengapa itu bukan bawaan, tapi saya punya beberapa pemikiran.

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

Yang paling penting, copysignadalah superset sign! Memanggil copysigndengan x = 1 sama dengan signfungsi. Jadi Anda bisa menggunakan copysigndan melupakannya .

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

Jika Anda bosan melewati dua argumen penuh, Anda bisa menerapkan signcara ini, dan itu masih akan kompatibel dengan hal-hal IEEE yang disebutkan oleh orang lain:

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

Kedua, biasanya ketika Anda menginginkan tanda sesuatu, Anda akhirnya mengalikannya dengan nilai lain. Dan tentu saja itu pada dasarnya apacopysign terjadi.

Jadi, alih-alih:

s = sign(a)
b = b * s

Anda bisa melakukannya:

b = copysign(b, a)

Dan ya, saya terkejut Anda telah menggunakan Python selama 7 tahun dan berpikir cmpbisa dengan mudah dihapus dan diganti sign! Pernahkah Anda menerapkan kelas dengan __cmp__metode? Pernahkah Anda meneleponcmp dan menetapkan fungsi pembanding khusus?

Singkatnya, saya mendapati diri saya menginginkan signfungsi juga, tetapi copysigndengan argumen pertama menjadi 1 akan berfungsi dengan baik. Saya tidak setuju itu signakan lebih berguna daripada copysign, karena saya telah menunjukkan bahwa itu hanya sebagian dari fungsi yang sama.

FogleBird
sumber
35
Menggunakan [int(copysign(1, zero)) for zero in (0, 0.0, -0.0)]memberi [1, 1, -1]. Itu seharusnya [0, 0, 0]sesuai dengan en.wikipedia.org/wiki/Sign_function
user238424
12
@Andrew - Perintah panggilan @ user238424 benar. copysign(a,b)mengembalikan a dengan tanda b - b adalah input yang bervariasi, a adalah nilai untuk dinormalisasi dengan tanda b. Dalam hal ini, komentator menggambarkan bahwa copysign (1, x) sebagai pengganti tanda (x) gagal, karena mengembalikan 1 untuk x = 0, sedangkan tanda (0) akan dievaluasi menjadi 0.
PaulMcG
7
Mengapung menahan "tanda" terpisah dari "nilai"; -0.0 adalah angka negatif, bahkan jika itu tampaknya kesalahan implementasi. Cukup menggunakan cmp()akan memberikan hasil yang diinginkan, mungkin untuk hampir setiap kasus siapa pun akan peduli: [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 * stidak setara dengan b = copysign(b, a)! Tidak mempertimbangkan tanda b. Misalnya jika a=b=-1kode pertama akan mengembalikan 1 sedangkan yang kedua mengembalikan -1
Johannes Jendersie
14
Melihat tanda palsu () definisi penggantian, ekuivalen palsu untuk perkalian dengan tanda (a), penjelasan salah untuk motivasi copysign, dan penggantian yang benar "cmp (x, 0)" telah disebutkan dalam pertanyaan - ada tidak banyak info dan tidak jelas bagi saya mengapa ini adalah jawaban "diterima" dengan begitu banyak suara.?
kxr
59

"copysign" didefinisikan oleh IEEE 754, dan bagian dari spesifikasi C99. Itu sebabnya itu di Python. Fungsi tidak dapat diimplementasikan secara penuh oleh abs (x) * tanda (y) karena bagaimana itu seharusnya menangani nilai-nilai 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
>>> 

Itu membuat copysign () fungsi yang lebih berguna daripada tanda ().

Mengenai alasan spesifik mengapa IEEE's signbit (x) tidak tersedia dalam standar Python, saya tidak tahu. Saya bisa membuat asumsi, tapi itu hanya menebak.

Modul matematika itu sendiri menggunakan copysign (1, x) sebagai cara untuk memeriksa apakah x negatif atau non-negatif. Untuk sebagian besar kasus, berurusan dengan fungsi matematika yang tampaknya lebih berguna daripada memiliki tanda (x) yang mengembalikan 1, 0, atau -1 karena ada satu kasus yang kurang untuk dipertimbangkan. Misalnya, berikut ini dari modul matematika 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);

Di sana Anda dapat dengan jelas melihat bahwa copysign () adalah fungsi yang lebih efektif daripada fungsi tiga-tanda ().

Kau menulis:

Jika saya seorang desainer python, saya akan menjadi sebaliknya: tidak ada cmp () bawaan, tapi sebuah tanda ()

Itu berarti Anda tidak tahu bahwa cmp () digunakan untuk hal-hal selain angka. cmp ("Ini", "Itu") tidak dapat diimplementasikan dengan fungsi tanda ().

Edit untuk menyusun jawaban tambahan saya di tempat lain :

Anda mendasarkan pembenaran Anda pada seberapa abs () dan tanda () sering terlihat bersama. Karena pustaka standar C tidak mengandung fungsi 'tanda (x)' dalam bentuk apa pun, saya tidak tahu bagaimana Anda membenarkan pandangan Anda. Ada abs (int) dan fabs (double) dan fabsf (float) dan fabsl (panjang) tetapi tidak menyebutkan tanda. Ada "copysign ()" dan "signbit ()" tetapi itu hanya berlaku untuk nomor IEEE 754.

Dengan bilangan kompleks, tanda apa yang akan (-3 + 4j) kembalikan dalam Python, apakah harus diimplementasikan? abs (-3 + 4j) mengembalikan 5.0. Itu adalah contoh yang jelas tentang bagaimana abs () dapat digunakan di tempat-tempat di mana tanda () tidak masuk akal.

Misalkan tanda (x) ditambahkan ke Python, sebagai pelengkap untuk abs (x). Jika 'x' adalah turunan dari kelas yang ditentukan pengguna yang mengimplementasikan metode __ab __ (self) maka abs (x) akan memanggil x .__ abs __ (). Agar dapat bekerja dengan benar, untuk menangani abs (x) dengan cara yang sama maka Python harus mendapatkan slot tanda (x).

Ini berlebihan untuk fungsi yang relatif tidak dibutuhkan. Selain itu, mengapa tanda (x) ada dan tidak negatif (x) dan tidak positif (x) tidak ada? Cuplikan saya dari implementasi modul matematika Python menunjukkan bagaimana copybit (x, y) dapat digunakan untuk mengimplementasikan nonnegative (), yang tidak dapat dilakukan oleh tanda sederhana (x).

Python seharusnya mendukung memiliki dukungan yang lebih baik untuk fungsi matematika IEEE 754 / C99. Itu akan menambahkan fungsi signbit (x), yang akan melakukan apa yang Anda inginkan dalam kasus floats. Ini tidak akan berfungsi untuk bilangan bulat atau bilangan kompleks, apalagi string, dan tidak akan memiliki nama yang Anda cari.

Anda bertanya "mengapa", dan jawabannya adalah "tanda (x) tidak berguna." Anda menyatakan bahwa itu berguna. Namun komentar Anda menunjukkan bahwa Anda tidak cukup tahu untuk dapat membuat pernyataan itu, yang berarti Anda harus menunjukkan bukti yang meyakinkan tentang kebutuhannya. Mengatakan bahwa NumPy mengimplementasikannya tidak cukup meyakinkan. Anda perlu menunjukkan kasus bagaimana kode yang ada akan ditingkatkan dengan fungsi tanda.

Dan itu di luar lingkup StackOverflow. Bawa saja ke salah satu daftar Python.

Andrew Dalke
sumber
5
Yah, aku tidak jika itu akan membuat Anda bahagia, tetapi Python 3 memiliki tidak cmp()atau sign():-)
Antoine P.
4
menulis fungsi tanda yang baik () yang akan berfungsi dengan baik dengan IEEE 754 bukanlah hal sepele. Ini akan menjadi poin yang baik untuk memasukkannya ke dalam bahasa, daripada meninggalkannya, meskipun saya tidak menguraikan hal ini dalam pertanyaan
Davide
2
Komentar Anda tentang bagaimana "jika Anda ingin jenis ini stabil" berarti Anda juga tidak tahu cara kerja jenis stabil. Pernyataan Anda yang menyalin dan menandatangani sama dengan menunjukkan bahwa Anda tidak tahu banyak tentang IEEE 754 matematika sebelum posting ini. Haruskah Python mengimplementasikan semua fungsi matematika 754 di inti? Apa yang harus dilakukan untuk kompiler non-C99? Platform non-754? "isnonnegative" dan "isnonpositive" juga merupakan fungsi yang berguna. Haruskah Python juga memasukkan itu? abs (x) menunjukkan x .__ abs __ (), jadi haruskah tanda (x) tunduk pada x .__ tanda __ ()? Ada sedikit permintaan atau kebutuhan untuk itu, jadi mengapa itu harus menjadi inti?
Andrew Dalke
2
math.copysign (1, float ("- nan")) mengembalikan 1.0 bukannya -1.0 ketika saya mencobanya di 2.7
dansalmo
34

Satu liner lain untuk tanda ()

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

Jika Anda ingin mengembalikan 0 untuk x = 0:

sign = lambda x: x and (1, -1)[x<0]
dansalmo
sumber
1
Mengapa? Pertanyaan itu sendiri mengakui yang cmp(x, 0)setara dengan sign, dan lambda x: cmp(x, 0)lebih mudah dibaca daripada apa yang Anda sarankan.
ToolmakerSteve
1
Memang saya salah. Saya berasumsi bahwa 'cmp' ditentukan untuk mengembalikan -1,0, +1, tetapi saya melihat bahwa spek tidak menjamin itu.
ToolmakerSteve
Cantik. Menjawab pertanyaan yang dimulai: python int atau float ke -1, 0, 1?
scharfmn
1
Apakah ada keuntungan menggunakan daftar bukan -1 if x < 0 else 1?
Mateen Ulhaq
6
sign = lambda x: -1 if x < 0 else 1adalah 15% lebih cepat . Sama dengan sign = lambda x: x and (-1 if x < 0 else 1).
Mateen Ulhaq
26

Karena cmptelah dihapus , Anda bisa mendapatkan fungsionalitas yang sama dengannya

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

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

Ini bekerja untuk float, intdan bahkan Fraction. Dalam kasusfloat , perhatikansign(float("nan")) adalah nol.

Python tidak mengharuskan perbandingan mengembalikan boolean, dan karenanya memaksa perbandingan ke bool () melindungi terhadap penerapan yang diijinkan, tetapi tidak umum:

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

Hanya jawaban yang benar yang sesuai dengan definisi Wikipedia

The definisi di Wikipedia berbunyi:

definisi tanda

Karenanya,

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

Yang untuk semua maksud dan tujuan dapat disederhanakan untuk:

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

Definisi fungsi ini mengeksekusi cepat dan menghasilkan hasil yang benar dijamin untuk 0, 0,0, -0,0, -4 dan 5 (lihat komentar untuk jawaban yang salah lainnya).

Perhatikan bahwa nol (0) tidak positif atau negatif .

Serge Stroobandt
sumber
1
Jawaban ini menggambarkan bagaimana python ringkas namun kuat bisa.
NelsonGon
1
Quibble: Kode tidak mengimplementasikan definisi WP, ia menggantikan klausa tengah dengan klausa default di akhir. Meskipun ini diperlukan untuk menangani nomor non-nyata seperti nan itu salah ditampilkan sebagai langsung mengikuti pernyataan WP ('Karenanya').
Jürgen Strobel
1
@ JürgenStrobel Saya tahu persis apa yang Anda maksud dan saya juga telah lama merenungkan masalah ini. Saya memberikan jawabannya sekarang untuk formalisme yang benar, sambil mempertahankan versi yang disederhanakan untuk sebagian besar kasus penggunaan.
Serge Stroobandt
10

numpy memiliki fungsi tanda, dan memberi Anda bonus fungsi lainnya. Begitu:

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

Berhati-hatilah karena hasilnya numpy.float64:

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

Untuk hal-hal seperti json, ini penting, karena json tidak tahu cara membuat serial jenis numpy.float64. Dalam hal ini, Anda dapat melakukan:

float(np.sign(y))

untuk mendapatkan pelampung reguler.

Luca
sumber
10

Coba jalankan ini, di mana x adalah angka apa pun

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

Paksaan ke bool () menangani kemungkinan bahwa operator pembanding tidak mengembalikan boolean.

Eric Song
sumber
Ide bagus, tapi saya pikir maksud Anda: int_sign = int (x> 0) - int (x <0)
yucer
Maksud saya: int_sign = lambda x: (x> 0) - (x <0)
yucer
1
@yucer no, dia benar-benar bermaksud membuat para pemain menjadi bool (yang merupakan subclass dari int), karena kemungkinan teoretis yang dia berikan tautannya ke penjelasan.
Walter Tross
Satu-satunya downside dari konstruksi ini adalah bahwa argumen muncul dua kali, yang baik-baik saja jika itu adalah variabel tunggal
Walter Tross
5

Ya sign()fungsi yang benar harus setidaknya dalam modul matematika - seperti yang ada di numpy. Karena kita sering membutuhkannya untuk kode berorientasi matematika.

Tetapi math.copysign()juga bermanfaat secara mandiri.

cmp() dan obj.__cmp__() ... secara umum memiliki kepentingan tinggi secara mandiri. Bukan hanya untuk kode yang berorientasi matematika. Pertimbangkan membandingkan / menyortir tupel, objek tanggal, ...

Argumen dev di http://bugs.python.org/issue1640 tentang penghilangan math.sign()ganjil, karena:

  • Tidak ada yang terpisah -NaN
  • sign(nan) == nan tanpa khawatir (seperti exp(nan))
  • sign(-0.0) == sign(0.0) == 0 tanpa khawatir
  • sign(-inf) == -1 tanpa khawatir

- Seperti di numpy

kxr
sumber
4

Dalam Python 2, cmp()mengembalikan bilangan bulat: tidak ada persyaratan bahwa hasilnya adalah -1, 0, atau 1, jadi sign(x)tidak sama dengan cmp(x,0).

Dalam Python 3, cmp()telah dihapus demi perbandingan kaya. Sebab cmp(), Python 3 menyarankan ini :

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

yang bagus untuk cmp (), tetapi sekali lagi tidak dapat digunakan untuk tanda () karena operator pembanding tidak perlu mengembalikan boolean .

Untuk menghadapi kemungkinan ini, hasil perbandingan harus dipaksa untuk boolean:

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

Ini berfungsi untuk apa pun typeyang benar-benar dipesan (termasuk nilai-nilai khusus suka NaNatau tak terbatas).

AllenT
sumber
0

Anda tidak memerlukannya, Anda cukup menggunakan:

If not number == 0:
    sig = number/abs(number)
else:
    sig = 0
inetphantom
sumber
4
Perlu ditunjukkan bahwa x / abs(x)dibutuhkan sedikit lebih lama dari sekadar rantai if/elseuntuk memeriksa sisi 0 variabel mana yang aktif, atau dalam hal ini menggunakan slimy-belum-memuaskan return (x > 0) - (x < 0)untuk mengurangi boolnilai dan mengembalikanint
1
Python memperlakukan Truedan Falsesebagai 1dan 0, Anda benar-benar dapat melakukan hal ini dan mendapatkan baik 1, 0atau -1. def sign(x): return (x > 0) - (x < 0)tidak akan mengembalikan bool, itu akan mengembalikan int- jika Anda lulus 0Anda akan 0kembali
0

Hanya saja tidak.

Cara terbaik untuk memperbaikinya adalah:

sign = lambda x: bool(x > 0) - bool(x < 0)
Michel de Ruiter
sumber
-8

Alasan "tanda" tidak disertakan adalah bahwa jika kami menyertakan setiap satu-liner yang berguna dalam daftar fungsi bawaan, Python tidak akan mudah dan praktis untuk digunakan. Jika Anda sering menggunakan fungsi ini maka mengapa Anda tidak memfaktorkannya sendiri? Ini tidak seperti itu sulit atau bahkan membosankan untuk melakukannya.

Antoine P.
sumber
6
Yah, saya akan membeli ini hanya jika abs()ditinggalkan juga. sign()dan abs()sering digunakan bersama, sign()adalah yang paling berguna dari keduanya (IMO), dan tidak ada yang terlalu sulit atau membosankan untuk diimplementasikan (meskipun rentan kesalahan, lihat bagaimana jawaban ini salah: stackoverflow.com/questions/1986152/… )
Davide
1
Masalahnya adalah bahwa hasil numerik sign()itu sendiri jarang berguna. Yang paling sering Anda lakukan adalah mengambil jalur kode yang berbeda berdasarkan apakah suatu variabel positif atau negatif, dan dalam hal itu lebih mudah dibaca untuk menulis kondisi secara eksplisit.
Antoine P.
3
abs () digunakan lebih sering daripada tanda (). Dan saya mengarahkan Anda ke pelacak NumPy yang menunjukkan seberapa sulit tanda () dapat diterapkan. Apa yang harus masuk (-3 + 4j)? Sementara abs (-3 + 4j) adalah 5.0. Anda membuat pernyataan bahwa tanda () dan abs () sering terlihat bersama. Pustaka standar C tidak memiliki fungsi 'tanda', jadi di mana Anda mendapatkan data?
Andrew Dalke