Apa cara paling pythonic untuk memeriksa apakah suatu objek adalah angka?

114

Mengingat objek python yang berubah-ubah, apa cara terbaik untuk menentukan apakah itu angka? Siniis didefinisikan sebagai acts like a number in certain circumstances.

Misalnya, Anda menulis kelas vektor. Jika diberi vektor lain, Anda ingin mencari perkalian titik. Jika diberi skalar, Anda ingin menskalakan seluruh vektor.

Memeriksa apakah ada sesuatu yang int, float, long, boolmenjengkelkan dan tidak mencakup ditetapkan pengguna objek yang mungkin bertindak seperti nomor. Tetapi, memeriksa __mul__, misalnya, tidak cukup baik karena kelas vektor yang baru saja saya jelaskan akan __mul__ditentukan, tetapi itu bukan jenis angka yang saya inginkan.

Claudiu
sumber

Jawaban:

135

Gunakan Numberdari numbersmodul untuk menguji isinstance(n, Number)(tersedia sejak 2.6).

>>> from numbers import Number
... from decimal import Decimal
... from fractions import Fraction
... for n in [2, 2.0, Decimal('2.0'), complex(2, 0), Fraction(2, 1), '2']:
...     print(f'{n!r:>14} {isinstance(n, Number)}')
              2 True
            2.0 True
 Decimal('2.0') True
         (2+0j) True
 Fraction(2, 1) True
            '2' False

Ini tentu saja bertentangan dengan bebek mengetik. Jika Anda lebih prihatin tentang bagaimana sebuah objek bertindak daripada apa yang , melakukan operasi Anda sebagai jika Anda memiliki nomor dan menggunakan pengecualian untuk memberitahu Anda sebaliknya.

Steven Rumbalski
sumber
3
Melakukan hal yang cerdas, daripada hal bebek, lebih disukai saat Anda mengalikan vektor dengan X. Dalam hal ini Anda ingin melakukan hal yang berbeda berdasarkan X itu . (Ini mungkin bertindak sebagai sesuatu yang berlipat ganda, tetapi hasilnya mungkin tidak masuk akal.)
Evgeni Sergeev
3
jawaban ini memberi akan mengatakan Benar adalah angka .. yang mungkin tidak selalu seperti yang Anda inginkan. Untuk mengecualikan boolean (pikirkan validasi fe), saya akan mengatakanisinstance(value, Number) and type(value) != bool
Yo Ludke
32

Anda ingin memeriksa apakah ada objek

bertindak seperti angka dalam keadaan tertentu

Jika Anda menggunakan Python 2.5 atau yang lebih lama, satu-satunya cara yang nyata adalah dengan memeriksa beberapa "keadaan tertentu" dan melihat.

Di 2.6 atau lebih baik, Anda dapat menggunakan isinstancedengan bilangan.Nomor - kelas dasar abstrak (ABC) yang ada persis untuk tujuan ini (lebih banyak ABC ada dicollections modul untuk berbagai bentuk koleksi / kontainer, sekali lagi dimulai dengan 2.6; dan, juga hanya dalam rilis tersebut, Anda dapat dengan mudah menambahkan kelas dasar abstrak Anda sendiri jika perlu).

Bach ke 2.5 dan sebelumnya, "dapat ditambahkan ke 0dan tidak dapat diulang" bisa menjadi definisi yang baik dalam beberapa kasus. Tapi, Anda benar-benar perlu bertanya pada diri sendiri, apa yang Anda tanyakan sehingga apa yang ingin Anda anggap sebagai "angka" pasti bisa dilakukan , dan apa yang pasti tidak bisa dilakukan bisa dilakukan - dan periksa.

Ini mungkin juga diperlukan di 2.6 atau yang lebih baru, mungkin untuk tujuan membuat pendaftaran Anda sendiri untuk menambahkan jenis yang Anda pedulikan yang belum didaftarkan numbers.Numbers- jika Anda ingin mengecualikan beberapa jenis yang mengklaim bahwa mereka adalah nomor tetapi Anda hanya tidak bisa menangani, itu bahkan lebih berhati-hati, karena ABC tidak memiliki unregistermetode [[misalnya Anda dapat membuat ABC Anda sendiri WeirdNumdan mendaftarkan di sana semua jenis yang aneh untuk Anda, kemudian periksa terlebih dahulu untuk isinstancemenebusnya sebelum Anda melanjutkan untuk memeriksa isinstancenormal numbers.Numberuntuk melanjutkan dengan sukses.

BTW, jika dan ketika Anda perlu memeriksa apakah xbisa atau tidak bisa melakukan sesuatu, Anda biasanya harus mencoba sesuatu seperti:

try: 0 + x
except TypeError: canadd=False
else: canadd=True

Kehadiran __add__per se memberi tahu Anda tidak ada yang berguna, karena misalnya semua urutan memilikinya untuk tujuan penggabungan dengan urutan lain. Pemeriksaan ini setara dengan definisi "bilangan adalah sesuatu yang urutan dari hal-hal seperti itu adalah argumen tunggal yang valid untuk fungsi bawaan sum", misalnya. Jenis yang benar-benar aneh (misalnya, yang memunculkan pengecualian "salah" saat dijumlahkan menjadi 0, seperti, katakanlah, a ZeroDivisionErroratauValueError & c) akan menyebarkan pengecualian, tetapi tidak apa-apa, beri tahu pengguna secepatnya bahwa jenis gila seperti itu tidak dapat diterima dalam keadaan baik perusahaan;-); tetapi, "vektor" yang dapat diringkas ke skalar (pustaka standar Python tidak memilikinya, tetapi tentu saja mereka populer sebagai ekstensi pihak ketiga) juga akan memberikan hasil yang salah di sini, jadi (mis.yang "tidak diizinkan untuk berulang" (misalnya, periksa iter(x)kenaikan gaji TypeError, atau untuk keberadaan metode khusus __iter__- jika Anda berada di 2.5 atau lebih awal dan karenanya perlu pemeriksaan Anda sendiri).

Sekilas tentang kerumitan seperti itu mungkin cukup untuk memotivasi Anda untuk mengandalkan kelas dasar abstrak kapan pun memungkinkan ... ;-).

Alex Martelli
sumber
Tetapi ada ABC untuk Angka di modul angka. Itulah yang diklaim oleh dokumen: "Modul angka (PEP 3141) mendefinisikan hierarki kelas dasar abstrak numerik yang secara progresif menentukan lebih banyak operasi."
Steven Rumbalski
17

Ini adalah contoh yang baik di mana pengecualian benar-benar menonjol. Lakukan saja apa yang akan Anda lakukan dengan tipe numerik dan tangkap TypeErrordari yang lainnya.

Tapi jelas, ini hanya memeriksa apakah suatu operasi berfungsi , bukan apakah masuk akal ! Satu-satunya solusi nyata untuk itu adalah jangan pernah mencampur jenis dan selalu tahu persis kelas tipe apa nilai Anda.

Jochen Ritzel
sumber
1
1 untuk Pengetikan Bebek: tidak masalah jenis data saya, apakah saya dapat melakukan apa yang saya inginkan dengannya atau tidak.
systempuntoout
12
Ini adalah pendekatan tradisional, tetapi ABC telah diperkenalkan dengan cara yang baik untuk menjauh dari pengetikan bebek murni dan bergeser agak jauh menuju dunia di mana isinstancesebenarnya dapat berguna dalam banyak kasus (== "periksa masuk akal" serta penerapan formal operasi). Pergeseran sulit untuk orang-orang lama yang hanya menggunakan Python, tetapi tren halus yang sangat penting dalam filosofi Python yang akan menjadi kesalahan serius untuk diabaikan.
Alex Martelli
@ Alex: Benar dan saya suka kelas tipe (kebanyakan collections.Sequencedan teman-teman). Tapi afaik, tidak ada kelas seperti itu untuk bilangan, vektor, atau objek matematika lainnya.
Jochen Ritzel
1
Tidak ada yang melarang mengetik bebek. Itu yang akan saya lakukan. Tetapi ada kelas dasar abstrak untuk bilangan: bilangan.Nomor.
Steven Rumbalski
4

Kalikan objek dengan nol. Setiap angka dikalikan nol adalah nol. Hasil lainnya berarti objek tersebut bukan angka (termasuk pengecualian)

def isNumber(x):
    try:
        return bool(0 == x*0)
    except:
        return False

Dengan demikian, penggunaan isNumber akan menghasilkan keluaran sebagai berikut:

class A: pass 

def foo(): return 1

for x in [1,1.4, A(), range(10), foo, foo()]:
    answer = isNumber(x)
    print('{answer} == isNumber({x})'.format(**locals()))

Keluaran:

True == isNumber(1)
True == isNumber(1.4)
False == isNumber(<__main__.A instance at 0x7ff52c15d878>)
False == isNumber([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
False == isNumber(<function foo at 0x7ff52c121488>)
True == isNumber(1)

Mungkin ada beberapa objek non-angka di dunia yang mendefinisikan __mul__kembali nol ketika dikalikan dengan nol tetapi itu adalah pengecualian yang ekstrim. Solusi ini harus mencakup semua kode normal dan waras yang Anda buat / encouter.

contoh numpy.array:

import numpy as np

def isNumber(x):
    try:
        return bool(x*0 == 0)
    except:
        return False

x = np.array([0,1])

answer = isNumber(x)
print('{answer} == isNumber({x})'.format(**locals()))

keluaran:

False == isNumber([0 1])
lihai
sumber
5
True * 0 == 0
endolith
4
Fungsi Anda salah akan mengatakan bahwa boolean adalah angka
endolit
1
@endolith, Boolean bertindak persis seperti angka. Benar selalu == 1 dan Salah selalu == 0. Ini persis seperti yang ditanyakan oleh penanya, "Ini 'adalah' didefinisikan sebagai 'bertindak seperti angka dalam keadaan tertentu'."
shrewmouse
1
@endolith, Sebenarnya, Boolean adalah angka. Boolean berasal dari intjadi fungsi saya akan dengan benar mengatakan bahwa boolean adalah angka.
shrewmouse
1
@NicolasAbril, ubah 0 * x == 0 menjadi bool di dalam isNumber.
shrewmouse
3

Untuk menyusun ulang pertanyaan Anda, Anda mencoba untuk menentukan apakah sesuatu adalah kumpulan atau nilai tunggal. Mencoba membandingkan apakah sesuatu itu vektor atau angka membandingkan apel dengan jeruk - Saya dapat memiliki vektor string atau angka, dan saya dapat memiliki satu string atau satu nomor. Anda tertarik pada berapa banyak yang Anda miliki (1 atau lebih) , bukan jenis yang sebenarnya Anda miliki.

solusi saya untuk masalah ini adalah memeriksa apakah masukan adalah nilai tunggal atau kumpulan dengan memeriksa keberadaan __len__. Sebagai contoh:

def do_mult(foo, a_vector):
    if hasattr(foo, '__len__'):
        return sum([a*b for a,b in zip(foo, a_vector)])
    else:
        return [foo*b for b in a_vector]

Atau, untuk pendekatan mengetik bebek, Anda dapat mencoba mengulanginya footerlebih dahulu:

def do_mult(foo, a_vector):
    try:
        return sum([a*b for a,b in zip(foo, a_vector)])
    except TypeError:
        return [foo*b for b in a_vector]

Pada akhirnya, lebih mudah untuk menguji apakah sesuatu itu mirip vektor daripada menguji apakah sesuatu itu seperti skalar. Jika Anda memiliki nilai dari jenis yang berbeda (yaitu string, numerik, dll.) Yang masuk, maka logika program Anda mungkin memerlukan beberapa pekerjaan - bagaimana Anda akhirnya mencoba mengalikan string dengan vektor numerik di tempat pertama?

Gordon Bean
sumber
3

Untuk meringkas / mengevaluasi metode yang ada:

Candidate    | type                      | delnan | mat | shrewmouse | ant6n
-------------------------------------------------------------------------
0            | <type 'int'>              |      1 |   1 |          1 |     1
0.0          | <type 'float'>            |      1 |   1 |          1 |     1
0j           | <type 'complex'>          |      1 |   1 |          1 |     0
Decimal('0') | <class 'decimal.Decimal'> |      1 |   0 |          1 |     1
True         | <type 'bool'>             |      1 |   1 |          1 |     1
False        | <type 'bool'>             |      1 |   1 |          1 |     1
''           | <type 'str'>              |      0 |   0 |          0 |     0
None         | <type 'NoneType'>         |      0 |   0 |          0 |     0
'0'          | <type 'str'>              |      0 |   0 |          0 |     1
'1'          | <type 'str'>              |      0 |   0 |          0 |     1
[]           | <type 'list'>             |      0 |   0 |          0 |     0
[1]          | <type 'list'>             |      0 |   0 |          0 |     0
[1, 2]       | <type 'list'>             |      0 |   0 |          0 |     0
(1,)         | <type 'tuple'>            |      0 |   0 |          0 |     0
(1, 2)       | <type 'tuple'>            |      0 |   0 |          0 |     0

(Saya datang ke sini dengan pertanyaan ini )

Kode

#!/usr/bin/env python

"""Check if a variable is a number."""

import decimal


def delnan_is_number(candidate):
    import numbers
    return isinstance(candidate, numbers.Number)


def mat_is_number(candidate):
    return isinstance(candidate, (int, long, float, complex))


def shrewmouse_is_number(candidate):
    try:
        return 0 == candidate * 0
    except:
        return False


def ant6n_is_number(candidate):
    try:
        float(candidate)
        return True
    except:
        return False

# Test
candidates = (0, 0.0, 0j, decimal.Decimal(0),
              True, False, '', None, '0', '1', [], [1], [1, 2], (1, ), (1, 2))

methods = [delnan_is_number, mat_is_number, shrewmouse_is_number, ant6n_is_number]

print("Candidate    | type                      | delnan | mat | shrewmouse | ant6n")
print("-------------------------------------------------------------------------")
for candidate in candidates:
    results = [m(candidate) for m in methods]
    print("{:<12} | {:<25} | {:>6} | {:>3} | {:>10} | {:>5}"
          .format(repr(candidate), type(candidate), *results))
Martin Thoma
sumber
TODO untuk saya sendiri float('nan'), 'nan', '123.45', '42', '42a', '0x8', '0xa'math.isnan
:,
2

Mungkin lebih baik melakukannya dengan cara lain: Anda memeriksa apakah itu vektor. Jika ya, Anda melakukan perkalian titik dan dalam semua kasus lain Anda mencoba perkalian skalar.

Memeriksa vektor itu mudah, karena itu harus dari jenis kelas vektor Anda (atau diwarisi darinya). Anda juga dapat mencoba melakukan perkalian titik terlebih dahulu, dan jika gagal (= ini bukan benar-benar vektor), maka kembali ke perkalian skalar.

sth
sumber
1

Hanya untuk menambah. Mungkin kita bisa menggunakan kombinasi isinstance dan isdigit sebagai berikut untuk mengetahui apakah suatu nilai adalah angka (int, float, dll)

if isinstance (num1, int) atau isinstance (num1, float) atau num1.isdigit ():

shadab.tughlaq
sumber
0

Untuk kelas vektor hipotetis:

Misalkan vadalah vektor, dan kita mengalikannya dengan x. Jika masuk akal untuk mengalikan setiap komponen vdengan x, kami mungkin bermaksud begitu, jadi coba dulu. Jika tidak, mungkin kita bisa memberi titik? Jika tidak, itu adalah kesalahan tipe.

EDIT - kode di bawah ini tidak berfungsi, karena 2*[0]==[0,0]alih-alih menaikkan file TypeError. Saya membiarkannya karena sudah dikomentari.

def __mul__( self, x ):
    try:
        return [ comp * x for comp in self ]
    except TypeError:
        return [ x * y for x, y in itertools.zip_longest( self, x, fillvalue = 0 )
Katriel
sumber
jika xadalah vektor maka [comp * x for comp in self]akan menghasilkan produk luar dari xsebuah v. Ini adalah tensor peringkat 2, bukan skalar.
aaronasterling
ubah "bukan skalar" menjadi "bukan vektor". setidaknya tidak dalam ruang vektor asli.
aaronasterling
Heh, sebenarnya kita berdua salah. Anda mengasumsikan bahwa comp*xakan skala xoleh comp, saya berasumsi bahwa itu akan menaikkan TypeError. Sayangnya, ini sebenarnya akan digabungkan xdengan compwaktu itu sendiri . Ups.
Katriel
meh. jika xadalah vektor maka ia harus memiliki __rmul__metode ( __rmul__ = __mul__) sehingga comp * xharus menskalakan xdengan cara yang sama seperti yang x * compdimaksudkan.
aaronasterling
0

Saya memiliki masalah serupa, saat menerapkan semacam kelas vektor. Salah satu cara untuk memeriksa suatu nomor adalah dengan mengubahnya menjadi satu, yaitu dengan menggunakan

float(x)

Ini harus menolak kasus di mana x tidak dapat diubah menjadi angka; tetapi mungkin juga menolak jenis struktur mirip angka lain yang mungkin valid, misalnya bilangan kompleks.

Ant6n
sumber
0

Jika Anda ingin memanggil metode yang berbeda bergantung pada tipe argumen, lihat ke multipledispatch.

Misalnya, Anda menulis kelas vektor. Jika diberi vektor lain, Anda ingin mencari perkalian titik. Jika diberi skalar, Anda ingin menskalakan seluruh vektor.

from multipledispatch import dispatch

class Vector(list):

    @dispatch(object)
    def __mul__(self, scalar):
        return Vector( x*scalar for x in self)

    @dispatch(list)
    def __mul__(self, other):
        return sum(x*y for x,y in zip(self, other))


>>> Vector([1,2,3]) * Vector([2,4,5])   # Vector time Vector is dot product
25
>>> Vector([1,2,3]) * 2                 # Vector times scalar is scaling
[2, 4, 6]

Sayangnya, (setahu saya) kita tidak bisa menulis @dispatch(Vector)karena kita masih mendefinisikan tipenya Vector, sehingga nama tipe itu belum ditentukan. Sebaliknya, saya menggunakan tipe dasar list, yang memungkinkan Anda bahkan menemukan perkalian titik dari a Vectordan a list.

AJNeufeld
sumber
0

Cara singkat dan sederhana:

obj = 12345
print(isinstance(obj,int))

Keluaran:

True

Jika objeknya adalah string, 'False' akan dikembalikan:

obj = 'some string'
print(isinstance(obj,int))

Keluaran:

False
Shekhar
sumber
0

Anda memiliki item data, katakanlah rec_daybahwa ketika ditulis ke file akan menjadi float. Tetapi selama pemrosesan program, ini bisa berupa float, intatau strtype ( strdigunakan saat menginisialisasi record baru dan berisi nilai flag dummy).

Anda kemudian dapat memeriksa untuk melihat apakah Anda memiliki nomor dengan ini

                type(rec_day) != str 

Saya telah menyusun program python dengan cara ini dan hanya memasukkan 'patch pemeliharaan' menggunakan ini sebagai pemeriksaan numerik. Apakah itu cara Pythonic? Kemungkinan besar tidak karena saya dulu memprogram di COBOL.

CopyPasteIt
sumber
-1

Anda bisa menggunakan fungsi isdigit ().

>>> x = "01234"
>>> a.isdigit()
True
>>> y = "1234abcd"
>>> y.isdigit()
False
rsy
sumber
"01234" bukan angka, ini adalah string karakter.
Alexey