อะไรคือวิธี pythonic ที่สุดในการตรวจสอบว่าวัตถุเป็นตัวเลขหรือไม่?


115

เมื่อพิจารณาจากวัตถุหลามโดยพลการวิธีใดที่ดีที่สุดในการตรวจสอบว่าเป็นตัวเลขหรือไม่ ที่นี่isถูกกำหนดให้เป็นacts like a number in certain circumstances.

ตัวอย่างเช่นสมมติว่าคุณกำลังเขียนคลาสเวกเตอร์ หากให้เวกเตอร์อื่นคุณจะต้องค้นหาผลิตภัณฑ์ดอท หากกำหนดสเกลาร์คุณต้องปรับขนาดเวกเตอร์ทั้งหมด

ตรวจสอบว่ามีอะไรบางอย่างint, float, long, boolเป็นที่น่ารำคาญและไม่ครอบคลุมวัตถุที่ผู้ใช้กำหนดที่อาจจะทำหน้าที่เหมือนตัวเลข แต่__mul__ตัวอย่างเช่นการตรวจสอบยังไม่ดีพอเพราะคลาสเวกเตอร์ที่ฉันเพิ่งอธิบายไปจะกำหนด__mul__แต่มันจะไม่ใช่ตัวเลขที่ฉันต้องการ

คำตอบ:


136

ใช้Numberจากnumbersโมดูลเพื่อทดสอบisinstance(n, Number)(ใช้ได้ตั้งแต่ 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

แน่นอนว่าตรงกันข้ามกับการพิมพ์เป็ด หากคุณมีความกังวลมากขึ้นเกี่ยวกับวัตถุทำหน้าที่มากกว่าสิ่งที่มันเป็นดำเนินการดำเนินงานของคุณเช่นถ้าคุณมีหมายเลขและการใช้ข้อยกเว้นที่จะบอกคุณมิฉะนั้น


3
การทำสิ่งที่ชาญฉลาดแทนที่จะเป็นสิ่งที่เป็ดเป็นที่ต้องการเมื่อคุณคูณเวกเตอร์ด้วย X ในกรณีนี้คุณต้องการทำสิ่งที่แตกต่างโดยพิจารณาจาก X คืออะไร (อาจทำหน้าที่เป็นสิ่งที่ทวีคูณ แต่ผลลัพธ์อาจไร้สาระ)
Evgeni Sergeev

3
คำตอบนี้จะบอกว่า True คือตัวเลข .. ซึ่งอาจไม่ใช่สิ่งที่คุณต้องการเสมอไป สำหรับ exlcuding booleans (คิดว่าการตรวจสอบ fe) ฉันจะบอกว่าisinstance(value, Number) and type(value) != bool
Yo Ludke

32

คุณต้องการตรวจสอบว่าวัตถุบางอย่าง

ทำหน้าที่เหมือนตัวเลขในบางสถานการณ์

หากคุณใช้ Python 2.5 หรือเก่ากว่าวิธีเดียวที่แท้จริงคือตรวจสอบ "สถานการณ์บางอย่าง" เหล่านั้นและดู

ใน 2.6 หรือดีกว่าคุณสามารถใช้isinstanceกับตัวเลขได้ Number - คลาสฐานนามธรรม (ABC) ที่มีอยู่เพื่อจุดประสงค์นี้ (ABCs อื่น ๆ อีกมากมายมีอยู่ในcollectionsโมดูลสำหรับคอลเลกชัน / คอนเทนเนอร์รูปแบบต่างๆอีกครั้งโดยเริ่มต้นด้วย 2.6 และ นอกจากนี้ในรุ่นเหล่านั้นเท่านั้นคุณสามารถเพิ่มคลาสพื้นฐานนามธรรมของคุณเองได้อย่างง่ายดายหากต้องการ)

Bach เป็น 2.5 และก่อนหน้า "สามารถเพิ่ม0และไม่สามารถทำซ้ำได้" อาจเป็นคำจำกัดความที่ดีในบางกรณี แต่คุณจริงๆต้องถามตัวเองว่าอะไรคือสิ่งที่คุณถามว่าสิ่งที่คุณต้องการที่จะต้องพิจารณา "จำนวน" ต้องแน่นอนจะสามารถที่จะทำและสิ่งที่มันแน่นอนจะต้องไม่สามารถที่จะทำ - และการตรวจสอบ

สิ่งนี้อาจจำเป็นใน 2.6 หรือใหม่กว่าบางทีเพื่อจุดประสงค์ในการลงทะเบียนของคุณเองเพื่อเพิ่มประเภทที่คุณสนใจซึ่งยังไม่ได้ลงทะเบียนnumbers.Numbers- หากคุณต้องการยกเว้นบางประเภทที่อ้างว่าเป็นตัวเลข แต่เป็นคุณ ไม่สามารถจัดการได้ซึ่งต้องดูแลมากกว่านี้เนื่องจาก ABCs ไม่มีunregisterวิธีการ [[ตัวอย่างเช่นคุณสามารถสร้าง ABC ของคุณเองWeirdNumและลงทะเบียนที่นั่นทุกประเภทที่แปลกสำหรับคุณจากนั้นตรวจสอบก่อนเพื่อisinstanceให้ประกันตัวก่อนดำเนินการต่อ เพื่อตรวจสอบisinstanceความปกติnumbers.Numberเพื่อดำเนินการต่อให้สำเร็จ

BTW หากและเมื่อคุณต้องการตรวจสอบว่าxสามารถทำได้หรือไม่สามารถทำได้โดยทั่วไปคุณต้องลองทำสิ่งต่างๆเช่น:

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

การปรากฏตัวของ__add__per se บอกคุณว่าไม่มีประโยชน์ใด ๆ เนื่องจากเช่นลำดับทั้งหมดมีเพื่อจุดประสงค์ในการเชื่อมต่อกับลำดับอื่น ๆ การตรวจสอบนี้เทียบเท่ากับคำจำกัดความ "a number คือสิ่งที่ลำดับของสิ่งนั้นเป็นอาร์กิวเมนต์เดียวที่ถูกต้องสำหรับฟังก์ชัน builtin sum" ตัวอย่างเช่น ประเภทที่แปลกโดยสิ้นเชิง (เช่นประเภทที่เพิ่มข้อยกเว้น "ผิด" เมื่อรวมเป็น 0 เช่นพูด a ZeroDivisionErrorหรือ"ไม่อนุญาตให้ทำซ้ำ" (เช่นตรวจสอบที่เพิ่มขึ้นหรือสำหรับการมีอยู่ของวิธีพิเศษ- หากคุณอยู่ใน 2.5 หรือก่อนหน้านี้และต้องการเช็คของคุณเอง)ValueError & c) จะเผยแพร่ข้อยกเว้น แต่ไม่เป็นไรแจ้งให้ผู้ใช้ทราบโดยเร็วว่าประเภทที่บ้าคลั่งเช่นนั้นไม่สามารถยอมรับได้ในทางที่ดี บริษัท;-); แต่ "เวกเตอร์" ที่สามารถสรุปได้เป็นสเกลาร์ (ไลบรารีมาตรฐานของ Python ไม่มี แต่แน่นอนว่าเป็นที่นิยมในฐานะส่วนขยายของบุคคลที่สาม) ก็จะให้ผลลัพธ์ที่ไม่ถูกต้องเช่นกัน (เช่นiter(x)TypeError__iter__

การสรุปสั้น ๆ เกี่ยวกับภาวะแทรกซ้อนดังกล่าวอาจเพียงพอที่จะกระตุ้นให้คุณพึ่งพาคลาสฐานนามธรรมแทนเมื่อใดก็ตามที่เป็นไปได้ ... ;-)


แต่มี ABC สำหรับตัวเลขในโมดูลตัวเลข นั่นคือสิ่งที่เอกสารอ้างว่า: "โมดูลตัวเลข (PEP 3141) กำหนดลำดับชั้นของคลาสฐานนามธรรมที่เป็นตัวเลขซึ่งกำหนดการดำเนินการเพิ่มเติมอย่างต่อเนื่อง"
Steven Rumbalski

17

นี่เป็นตัวอย่างที่ดีที่ข้อยกเว้นส่องแสงอย่างแท้จริง เพียงแค่ทำสิ่งที่คุณจะทำกับประเภทตัวเลขและจับTypeErrorจากสิ่งอื่น ๆ

แต่เห็นได้ชัดว่าสิ่งนี้จะตรวจสอบว่าการดำเนินการใช้งานได้หรือไม่ไม่ใช่ว่าสมเหตุสมผลหรือไม่ ! ทางออกเดียวที่แท้จริงคืออย่าผสมประเภทและรู้เสมอว่าค่าของคุณอยู่ในประเภทใด


1
+1 สำหรับ Duck Typing: ไม่สำคัญว่าข้อมูลของฉันจะเป็นประเภทใดเพียงแค่ว่าฉันสามารถทำสิ่งที่ต้องการได้หรือไม่
systempuntoout

12
นี่เป็นแนวทางดั้งเดิม แต่มีการนำ ABC มาใช้ในส่วนที่ดีในการหลีกเลี่ยงการพิมพ์เป็ดอย่างแท้จริงและเปลี่ยนระยะทางไปยังโลกที่isinstanceสามารถใช้ประโยชน์ได้จริงในหลาย ๆ กรณี (== "ตรวจสอบความสมเหตุสมผล" รวมถึงการบังคับใช้อย่างเป็นทางการ ของการดำเนินงาน) การเปลี่ยนแปลงที่ยากลำบากสำหรับคนที่ใช้ Python เป็นเวลานาน แต่เป็นแนวโน้มที่ลึกซึ้งที่สำคัญมากในปรัชญาของ Python ว่ามันจะเป็นข้อผิดพลาดร้ายแรงที่จะละเลย
Alex Martelli

@ อเล็กซ์: จริงและฉันชอบแว่นตาพิมพ์ (ส่วนใหญ่collections.Sequenceและเพื่อน) แต่ afaik ไม่มีคลาสสำหรับตัวเลขเวกเตอร์หรือวัตถุทางคณิตศาสตร์อื่น ๆ
Jochen Ritzel

1
ไม่มีอะไรต่อต้านการพิมพ์เป็ด มันคือสิ่งที่ฉันจะทำ แต่มีคลาสพื้นฐานที่เป็นนามธรรมสำหรับตัวเลข: numbers.Number
Steven Rumbalski

4

คูณวัตถุด้วยศูนย์ ตัวเลขใด ๆ ที่ศูนย์เป็นศูนย์ ผลลัพธ์อื่นใดหมายความว่าวัตถุไม่ใช่ตัวเลข (รวมถึงข้อยกเว้น)

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

การใช้ isNumber จึงจะให้ผลลัพธ์ดังต่อไปนี้:

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

เอาท์พุต:

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)

อาจมีวัตถุที่ไม่ใช่ตัวเลขบางตัวในโลกที่กำหนด__mul__ให้ส่งคืนศูนย์เมื่อคูณด้วยศูนย์ แต่นั่นเป็นข้อยกเว้นอย่างยิ่ง โซลูชันนี้ควรครอบคลุมรหัสปกติและมีเหตุผลทั้งหมดที่คุณสร้าง / สนับสนุน

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

เอาต์พุต:

False == isNumber([0 1])

5
True * 0 == 0
endolith

4
ฟังก์ชันของคุณจะบอกอย่างไม่ถูกต้องว่าบูลีนเป็นตัวเลข
endolith

1
@endolith บูลีนทำหน้าที่เหมือนตัวเลข True always == 1 และ False always == 0 นี่คือสิ่งที่ผู้ถามถามว่า "Here" is "ถูกกำหนดให้เป็น" การกระทำเหมือนตัวเลขในบางสถานการณ์ ""
หนู

1
@endolith จริงๆแล้วบูลีนคือตัวเลข บูลีนมาจากintดังนั้นฟังก์ชันของฉันจะบอกได้อย่างถูกต้องว่าบูลีนเป็นตัวเลข
หนู

1
@NicolasAbril แปลง 0 * x == 0 เป็นบูลภายใน isNumber
shrewmouse

3

ในการเรียบเรียงคำถามของคุณใหม่คุณกำลังพยายามตรวจสอบว่าบางสิ่งเป็นคอลเล็กชันหรือค่าเดียว พยายามเปรียบเทียบว่าบางอย่างเป็นเวกเตอร์หรือตัวเลขกำลังเปรียบเทียบแอปเปิ้ลกับส้มฉันสามารถมีเวกเตอร์ของสตริงหรือตัวเลขและฉันสามารถมีสตริงเดียวหรือตัวเลขเดียวได้ คุณสนใจจำนวนที่คุณมี (1 หรือมากกว่า)ไม่ใช่ประเภทที่คุณมีอยู่จริง

ทางออกของฉันสำหรับปัญหานี้คือการตรวจสอบว่าอินพุตเป็นค่าเดียวหรือคอลเลกชันโดยการตรวจสอบการมีอยู่ของ__len__. ตัวอย่างเช่น:

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]

หรือสำหรับวิธีการพิมพ์เป็ดคุณสามารถลองทำซ้ำได้fooก่อน:

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]

ท้ายที่สุดแล้วมันง่ายกว่าที่จะทดสอบว่าบางสิ่งบางอย่างมีลักษณะเหมือนเวกเตอร์หรือไม่มากกว่าการทดสอบว่าบางสิ่งมีลักษณะเหมือนสเกลาร์หรือไม่ หากคุณมีค่าประเภทอื่น (เช่นสตริงตัวเลข ฯลฯ ) เข้ามาตรรกะของโปรแกรมของคุณอาจต้องใช้งาน - คุณพยายามคูณสตริงด้วยเวกเตอร์ตัวเลขในตอนแรกได้อย่างไร?


3

สรุป / ประเมินวิธีการที่มีอยู่:

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

(ฉันมาที่นี่ด้วยคำถามนี้ )

รหัส

#!/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))

สิ่งที่ต้องทำเพื่อตัวเอง: float('nan'), 'nan', '123.45', '42', '42a', '0x8', '0xa'เพิ่มmath.isnan
Martin Thoma

2

อาจจะดีกว่าถ้าทำแบบอื่น: คุณตรวจสอบว่าเป็นเวกเตอร์หรือไม่ ถ้าเป็นเช่นนั้นคุณต้องทำผลิตภัณฑ์ดอทและในกรณีอื่น ๆ ทั้งหมดคุณพยายามคูณสเกลาร์

การตรวจสอบเวกเตอร์นั้นทำได้ง่ายเนื่องจากควรเป็นประเภทคลาสเวกเตอร์ของคุณ (หรือสืบทอดมาจากเวกเตอร์) คุณสามารถลองทำ dot-product ก่อนและถ้ามันล้มเหลว (= มันไม่ใช่เวกเตอร์จริงๆ) ให้ถอยกลับไปที่การคูณสเกลาร์


1

เพียงเพื่อเพิ่ม บางทีเราอาจใช้การรวมกันของ isinstance และ isdigit ดังต่อไปนี้เพื่อค้นหาว่าค่าเป็นตัวเลขหรือไม่ (int, float, ฯลฯ )

ถ้า isinstance (num1, int) หรือ isinstance (num1, float) หรือ num1.isdigit ():


0

สำหรับคลาสเวกเตอร์สมมุติฐาน:

สมมติว่าเป็นเวกเตอร์และเราจะคูณโดยv xถ้ามันสมเหตุสมผลที่จะคูณแต่ละองค์ประกอบของvด้วยxเราก็น่าจะหมายถึงอย่างนั้นดังนั้นลองทำก่อน ถ้าไม่เราอาจจะจุด? มิฉะนั้นจะเป็นข้อผิดพลาดประเภท

แก้ไข - โค้ดด้านล่างใช้ไม่ได้เพราะ2*[0]==[0,0]แทนที่จะเพิ่มไฟล์TypeError. ฉันปล่อยมันไว้เพราะมีการแสดงความคิดเห็น

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 )

ถ้าxเป็นเวกเตอร์แล้ว[comp * x for comp in self]จะให้ผลผลิตผลิตภัณฑ์ด้านนอกของ นี่คืออันดับ 2 เทนเซอร์ไม่ใช่สเกลาร์ xv
aaronasterling

เปลี่ยน "ไม่ใช่สเกลาร์" เป็น "ไม่ใช่เวกเตอร์" อย่างน้อยก็ไม่อยู่ในปริภูมิเวกเตอร์ดั้งเดิม
aaronasterling

เฮ้จริงๆแล้วเราผิดทั้งคู่ คุณกำลังสมมติว่าcomp*xจะปรับขนาดxโดยcompฉันสมมติว่ามันจะเพิ่ม TypeError น่าเสียดายที่มันจะเชื่อมต่อxกับcompเวลาของตัวเอง อ๊ะ.
Katriel

ฉัน. ถ้าxเป็นเวกเตอร์ก็ควรมี__rmul__method ( __rmul__ = __mul__) เพื่อที่comp * xจะปรับขนาดxในลักษณะเดียวกับที่ x * compตั้งใจไว้
aaronasterling

0

ฉันมีปัญหาคล้ายกันเมื่อใช้คลาสเวกเตอร์ประเภทหนึ่ง วิธีหนึ่งในการตรวจสอบตัวเลขคือการแปลงเป็นหมายเลขหนึ่งนั่นคือการใช้

float(x)

สิ่งนี้ควรปฏิเสธกรณีที่ x ไม่สามารถแปลงเป็นตัวเลขได้ แต่ยังอาจปฏิเสธโครงสร้างที่คล้ายตัวเลขประเภทอื่น ๆ ที่สามารถใช้ได้เช่นจำนวนเชิงซ้อน


0

หากคุณต้องการที่จะเรียกวิธีการที่แตกต่างกันขึ้นอยู่กับชนิดอาร์กิวเมนต์ (s), multipledispatchดูเป็น

ตัวอย่างเช่นสมมติว่าคุณกำลังเขียนคลาสเวกเตอร์ หากให้เวกเตอร์อื่นคุณจะต้องค้นหาผลิตภัณฑ์ดอท หากกำหนดสเกลาร์คุณต้องปรับขนาดเวกเตอร์ทั้งหมด

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]

น่าเสียดายที่ (สำหรับความรู้ของฉัน) เราไม่สามารถเขียนได้@dispatch(Vector)เนื่องจากเรายังคงกำหนดประเภทVectorดังนั้นจึงยังไม่ได้กำหนดชื่อประเภทนั้น แต่ฉันใช้ประเภทฐานlistซึ่งช่วยให้คุณสามารถหาผลิตภัณฑ์จุดของ a Vectorและlist.


0

วิธีที่สั้นและง่าย:

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

เอาท์พุต:

True

หากออบเจ็กต์เป็นสตริงระบบจะส่งคืน "False":

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

เอาท์พุต:

False

0

คุณมีรายการข้อมูลบอกrec_dayว่าเมื่อเขียนลงไฟล์จะเป็นไฟล์float. แต่ในระหว่างการประมวลผลโปรแกรมมันสามารถเป็นได้ทั้งfloat, intหรือstrประเภท (คนstrจะใช้เมื่อมีการเริ่มต้นบันทึกใหม่และมีค่าธงจำลอง)

จากนั้นคุณสามารถตรวจสอบว่าคุณมีหมายเลขนี้หรือไม่

                type(rec_day) != str 

ฉันได้จัดโครงสร้างโปรแกรม python ด้วยวิธีนี้และใส่ 'โปรแกรมแก้ไขการบำรุงรักษา' โดยใช้สิ่งนี้เป็นการตรวจสอบตัวเลข มันเป็นวิธี Pythonic หรือไม่? ไม่น่าจะเป็นไปได้มากที่สุดตั้งแต่ฉันเคยเขียนโปรแกรมในภาษาโคบอล


-1

คุณสามารถใช้ฟังก์ชัน isdigit ()

>>> x = "01234"
>>> a.isdigit()
True
>>> y = "1234abcd"
>>> y.isdigit()
False

"01234" ไม่ใช่ตัวเลข แต่เป็นสตริงอักขระ
Alexey
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.