Was ist die pythonischste Methode, um zu überprüfen, ob ein Objekt eine Zahl ist?

114

Wie kann bei einem beliebigen Python-Objekt am besten festgestellt werden, ob es sich um eine Zahl handelt? Hier isist definiert als acts like a number in certain circumstances.

Angenommen, Sie schreiben eine Vektorklasse. Wenn Sie einen anderen Vektor angeben, möchten Sie das Punktprodukt finden. Wenn Sie einen Skalar erhalten, möchten Sie den gesamten Vektor skalieren.

Prüfen , ob etwas ist int, float, long, boolärgerlich und deckt keine benutzerdefinierten Objekte , die wie Zahlen handeln könnte. Das Überprüfen auf __mul__zum Beispiel ist jedoch nicht gut genug, da die soeben beschriebene Vektorklasse definieren würde __mul__, aber es wäre nicht die Art von Zahl, die ich möchte.

Claudiu
quelle

Antworten:

135

Verwenden Sie Numberaus dem numbersModul Test isinstance(n, Number)(verfügbar seit 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

Dies widerspricht natürlich der Ententypisierung. Wenn Sie sich mehr Gedanken darüber machen, wie sich ein Objekt verhält, als was es ist , führen Sie Ihre Operationen so aus, als hätten Sie eine Nummer, und verwenden Sie Ausnahmen, um Ihnen etwas anderes mitzuteilen.

Steven Rumbalski
quelle
3
Wenn Sie einen Vektor mit X multiplizieren, wird es vorgezogen, das Kluge anstelle des Entens zu tun. In diesem Fall möchten Sie verschiedene Dinge tun, je nachdem, was X ist . (Es könnte als etwas wirken, das sich vervielfacht, aber das Ergebnis könnte unsinnig sein.)
Evgeni Sergeev
3
Diese Antwort würde sagen, True ist eine Zahl. Das ist wahrscheinlich nicht immer das, was Sie wollen. Um Boolesche Werte auszuschließen (denken Sie an Validierung, z. B.), würde ich sagenisinstance(value, Number) and type(value) != bool
Yo Ludke
32

Sie möchten überprüfen, ob ein Objekt vorhanden ist

verhält sich unter bestimmten Umständen wie eine Zahl

Wenn Sie Python 2.5 oder älter verwenden, besteht die einzige Möglichkeit darin, einige dieser "bestimmten Umstände" zu überprüfen und zu überprüfen.

In 2.6 oder besser können Sie isinstancemit numbers.Number verwenden - eine abstrakte Basisklasse (ABC), die genau für diesen Zweck existiert (es gibt viel mehr ABCs in dercollections Modul für verschiedene Arten von Sammlungen / Containern, wiederum beginnend mit 2.6; und, Auch nur in diesen Releases können Sie bei Bedarf problemlos Ihre eigenen abstrakten Basisklassen hinzufügen.

Bach bis 2.5 und früher, "kann hinzugefügt werden 0und ist nicht iterierbar", könnte in einigen Fällen eine gute Definition sein. Aber, die Sie wirklich brauchen , sich zu fragen, was es ist , dass Sie fragen, was Sie „eine Reihe“ betrachten wollen definitiv in der Lage sein müssen , zu tun , und was es muss absolut sein nicht in der Lage zu tun - und überprüfen.

Dies kann auch in Version 2.6 oder höher erforderlich sein, um möglicherweise eigene Registrierungen vorzunehmen, um Typen hinzuzufügen, für die Sie sich interessieren und die noch nicht registriert sind numbers.Numbers- wenn Sie einige Typen ausschließen möchten, die behaupten, sie seien Zahlen, aber Sie kann einfach nicht damit umgehen, das ist noch sorgfältiger, da ABCs keine unregisterMethode haben [[zum Beispiel könnten Sie Ihr eigenes ABC erstellen WeirdNumund dort all diese für Sie seltsamen Typen registrieren, und dann zuerst prüfen, ob isinstancesie gerettet werden, bevor Sie fortfahren um nach isinstancedem Normalen numbers.Numberzu suchen , um erfolgreich fortzufahren.

Übrigens, wenn Sie prüfen müssen, ob etwas möglich xist oder nicht, müssen Sie im Allgemeinen Folgendes ausprobieren:

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

Das Vorhandensein von __add__per se sagt Ihnen nichts Nützliches, da z. B. alle Sequenzen es zum Zwecke der Verkettung mit anderen Sequenzen haben. Diese Prüfung entspricht beispielsweise der Definition "Eine Zahl ist so, dass eine Folge solcher Dinge ein gültiges Einzelargument für die eingebaute Funktion ist sum". Völlig seltsame Typen (z. B. solche, die die "falsche" Ausnahme auslösen, wenn sie auf 0 summiert werden, wie z. B. a ZeroDivisionErroroder "nicht iterierbar sein") (z. B. überprüfen, ob dies ausgelöst wird , oder auf das Vorhandensein einer speziellen Methode - wenn Sie in 2.5 oder früher sind und daher Ihre eigenen Schecks benötigen).ValueError & c) verbreiten eine Ausnahme, aber das ist in Ordnung. Lassen Sie den Benutzer so schnell wie möglich wissen, dass solche verrückten Typen im Guten einfach nicht akzeptabel sind Unternehmen;-); Aber ein "Vektor", der zu einem Skalar summierbar ist (Pythons Standardbibliothek hat keinen, aber natürlich sind sie als Erweiterungen von Drittanbietern beliebt), würde hier auch das falsche Ergebnis liefern, also (ziter(x)TypeError__iter__

Ein kurzer Blick auf solche Komplikationen kann ausreichen, um Sie zu motivieren, sich nach Möglichkeit auf abstrakte Basisklassen zu verlassen ... ;-).

Alex Martelli
quelle
Es gibt jedoch ein ABC für Nummer im Zahlenmodul. Das behaupten die Dokumente: "Das Zahlenmodul (PEP 3141) definiert eine Hierarchie numerischer abstrakter Basisklassen, die nach und nach mehr Operationen definieren."
Steven Rumbalski
17

Dies ist ein gutes Beispiel, wo Ausnahmen wirklich glänzen. Tun Sie einfach, was Sie mit den numerischen Typen tun würden, und fangen Sie das TypeErrorvon allem anderen ab.

Dies prüft aber natürlich nur, ob eine Operation funktioniert , nicht ob sie sinnvoll ist ! Die einzige wirkliche Lösung dafür besteht darin, niemals Typen zu mischen und immer genau zu wissen, zu welcher Typklasse Ihre Werte gehören.

Jochen Ritzel
quelle
1
+1 für Duck Typing: Es spielt keine Rolle, um welchen Typ es sich bei meinen Daten handelt, nur ob ich damit machen kann, was ich will.
Systempuntoout
12
Dies war der traditionelle Ansatz, aber ABCs wurden zum großen Teil eingeführt, um sich von der reinen Ententypisierung zu lösen und eine gewisse Distanz zu einer Welt zu schaffen, in der isinstancees in vielen Fällen tatsächlich nützlich sein kann (== "check it sinnvoll" sowie formale Anwendbarkeit von Operationen). Schwierige Verschiebung für langjährige Nur-Python-Leute, aber ein sehr wichtiger subtiler Trend in Pythons Philosophie, dass es ein schwerwiegender Fehler wäre, ihn zu ignorieren.
Alex Martelli
@ Alex: Stimmt und ich liebe Typklassen (meistens collections.Sequenceund Freunde). Aber afaik, es gibt keine solchen Klassen für Zahlen, Vektoren oder andere mathematische Objekte.
Jochen Ritzel
1
Nichts gegen das Tippen von Enten. Das würde ich tun. Es gibt jedoch eine abstrakte Basisklasse für Zahlen: numbers.Number.
Steven Rumbalski
4

Multiplizieren Sie das Objekt mit Null. Jede Zahl mal Null ist Null. Jedes andere Ergebnis bedeutet, dass das Objekt keine Zahl ist (einschließlich Ausnahmen).

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

Wenn Sie also isNumber verwenden, erhalten Sie die folgende Ausgabe:

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

Ausgabe:

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)

Es gibt wahrscheinlich einige Nicht-Zahlen-Objekte auf der Welt, die definieren __mul__, dass bei Multiplikation mit Null Null zurückgegeben wird, aber das ist eine extreme Ausnahme. Diese Lösung sollte den gesamten normalen und vernünftigen Code abdecken , den Sie generieren / erfassen.

numpy.array Beispiel:

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

Ausgabe:

False == isNumber([0 1])
Spitzmaus
quelle
5
True * 0 == 0
Endolith
4
Ihre Funktion wird fälschlicherweise sagen, dass Boolesche Zahlen Zahlen sind
Endolith
1
@endolith, Boolesche Werte verhalten sich genau wie Zahlen. Richtig immer == 1 und Falsch immer == 0. Dies ist genau die Frage des Fragestellers: "Hier ist 'ist' definiert als 'verhält sich unter bestimmten Umständen wie eine Zahl'."
Spitzmaus
1
@endolith, Eigentlich sind Boolesche Zahlen. Der Boolesche intWert leitet sich von ab, sodass meine Funktion korrekt sagt, dass Boolesche Werte Zahlen sind.
Spitzmaus
1
@NicolasAbril, konvertiere 0 * x == 0 in einen Bool in isNumber.
Spitzmaus
3

Um Ihre Frage neu zu formulieren, versuchen Sie festzustellen, ob es sich bei etwas um eine Sammlung oder um einen einzelnen Wert handelt. Der Versuch zu vergleichen, ob etwas ein Vektor oder eine Zahl ist, vergleicht Äpfel mit Orangen - ich kann einen Vektor von Zeichenfolgen oder Zahlen haben, und ich kann eine einzelne Zeichenfolge oder eine einzelne Zahl haben. Sie interessieren sich dafür, wie viele Sie haben (1 oder mehr) , nicht welchen Typ Sie tatsächlich haben.

Meine Lösung für dieses Problem besteht darin, zu überprüfen, ob die Eingabe ein einzelner Wert oder eine Sammlung ist, indem ich das Vorhandensein von überprüfe __len__. Beispielsweise:

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]

Für den Ansatz der Ententypisierung können Sie auch versuchen, zuerst zu iterieren 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]

Letztendlich ist es einfacher zu testen, ob etwas vektorartig ist, als zu testen, ob etwas skalarartig ist. Wenn Werte unterschiedlichen Typs (z. B. Zeichenfolge, Zahl usw.) durchkommen, muss die Logik Ihres Programms möglicherweise etwas bearbeitet werden. Wie haben Sie dann überhaupt versucht, eine Zeichenfolge mit einem numerischen Vektor zu multiplizieren?

Gordon Bean
quelle
3

Um vorhandene Methoden zusammenzufassen / zu bewerten:

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

(Ich bin mit dieser Frage hierher gekommen )

Code

#!/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
quelle
TODO für mich : float('nan'), 'nan', '123.45', '42', '42a', '0x8', '0xa', hinzufügenmath.isnan
Martin Thoma
2

Wahrscheinlich ist es besser, es einfach umgekehrt zu machen: Sie überprüfen, ob es sich um einen Vektor handelt. Wenn dies der Fall ist, erstellen Sie ein Punktprodukt und in allen anderen Fällen versuchen Sie eine skalare Multiplikation.

Das Überprüfen des Vektors ist einfach, da er von Ihrem Vektorklassentyp sein sollte (oder von ihm geerbt wurde). Sie können auch einfach versuchen, zuerst ein Punktprodukt zu erstellen. Wenn dies fehlschlägt (= es war nicht wirklich ein Vektor), können Sie auf die Skalarmultiplikation zurückgreifen.

etw
quelle
1

Nur um etwas hinzuzufügen. Vielleicht können wir eine Kombination aus isinstance und isdigit wie folgt verwenden, um herauszufinden, ob ein Wert eine Zahl ist (int, float usw.)

wenn isinstance (num1, int) oder isinstance (num1, float) oder num1.isdigit ():

shadab.tughlaq
quelle
0

Für die hypothetische Vektorklasse:

Angenommen, es vist ein Vektor, und wir multiplizieren ihn mit x. Wenn es sinnvoll ist, jede Komponente von vmit zu multiplizieren x, haben wir das wahrscheinlich so gemeint, also versuchen Sie es zuerst. Wenn nicht, können wir vielleicht punktieren? Ansonsten ist es ein Tippfehler.

BEARBEITEN - der folgende Code funktioniert nicht, weil 2*[0]==[0,0]statt a TypeError. Ich lasse es, weil es kommentiert wurde.

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
quelle
wenn xein Vektor dann [comp * x for comp in self]nachgeben das äußere Produkt von xein v. Dies ist ein Tensor vom Rang 2, kein Skalar.
Aaronasterling
Ändern Sie "kein Skalar" in "kein Vektor". Zumindest nicht im ursprünglichen Vektorraum.
Aaronasterling
Heh, eigentlich liegen wir beide falsch. Sie gehen davon aus, dass comp*xskaliert xdurch comp, war ich davon aus, dass es eine Typeerror erhöhen würde. Leider wird es tatsächlich mal xmit sich selbst verketten comp. Hoppla.
Katriel
meh. Wenn xes sich um einen Vektor handelt, sollte er eine __rmul__Methode ( __rmul__ = __mul__) haben, comp * xdie xauf die gleiche Weise skaliert , wie x * compes anscheinend beabsichtigt ist.
Aaronasterling
0

Ich hatte ein ähnliches Problem bei der Implementierung einer Art Vektorklasse. Eine Möglichkeit, nach einer Zahl zu suchen, besteht darin, sie einfach in eine zu konvertieren, dh mit

float(x)

Dies sollte Fälle ablehnen, in denen x nicht in eine Zahl konvertiert werden kann. kann aber auch andere Arten von zahlenähnlichen Strukturen ablehnen, die gültig sein könnten, beispielsweise komplexe Zahlen.

Ant6n
quelle
0

Wenn Sie je nach Argumenttyp (en) unterschiedliche Methoden aufrufen möchten, prüfen Sie multipledispatch.

Angenommen, Sie schreiben eine Vektorklasse. Wenn Sie einen anderen Vektor angeben, möchten Sie das Punktprodukt finden. Wenn Sie einen Skalar erhalten, möchten Sie den gesamten Vektor skalieren.

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]

Leider können wir (meines Wissens) nicht schreiben, @dispatch(Vector)da wir den Typ noch definieren Vector, sodass der Typname noch nicht definiert ist. Stattdessen verwende ich den Basistyp list, mit dem Sie sogar das Punktprodukt von a Vectorund a finden können list.

AJNeufeld
quelle
0

Kurzer und einfacher Weg:

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

Ausgabe :

True

Wenn das Objekt eine Zeichenfolge ist, wird 'False' zurückgegeben:

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

Ausgabe :

False
Shekhar
quelle
0

Sie haben ein Datenelement, sagen Sie, rec_daydass beim Schreiben in eine Datei ein float. Aber während der Programmbearbeitung kann es entweder float, intoder strTyp (derstr verwendet wird , wenn ein neuer Datensatz zu initialisieren und enthält einen Dummy - Flag - Wert).

Sie können dann überprüfen, ob Sie eine Nummer mit dieser haben

                type(rec_day) != str 

Ich habe ein Python-Programm auf diese Weise strukturiert und einfach einen 'Wartungs-Patch' eingefügt, der dies als numerische Prüfung verwendet. Ist es der pythonische Weg? Höchstwahrscheinlich nicht, seit ich in COBOL programmiert habe.

CopyPasteIt
quelle
-1

Sie können die Funktion isdigit () verwenden.

>>> x = "01234"
>>> a.isdigit()
True
>>> y = "1234abcd"
>>> y.isdigit()
False
rsy
quelle
"01234" ist keine Zahl, sondern eine Zeichenkette.
Alexey