Jaki jest najbardziej pythonowy sposób sprawdzenia, czy obiekt jest liczbą?

114

Biorąc pod uwagę dowolny obiekt Pythona, jaki jest najlepszy sposób określenia, czy jest to liczba? Tutaj isjest zdefiniowany jako acts like a number in certain circumstances.

Na przykład, powiedzmy, że piszesz klasę wektorową. Jeśli masz inny wektor, chcesz znaleźć iloczyn skalarny. Jeśli masz skalar, chcesz przeskalować cały wektor.

Sprawdzanie, czy coś jest int, float, long, booljest irytujące i nie obejmuje zdefiniowanych przez użytkownika obiektów, które mogą działać jak numery. Ale __mul__na przykład sprawdzenie, czy nie jest wystarczająco dobre, ponieważ klasa wektora, którą właśnie opisałem, zdefiniowałaby __mul__, ale nie byłaby to liczba, której chcę.

Claudiu
źródło

Odpowiedzi:

135

Użyj Numberz numbersmodułu do testowania isinstance(n, Number)(dostępne od 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

Jest to oczywiście przeciwieństwo pisania na klawiaturze. Jeśli jesteś bardziej zaniepokojony jak obiekt działa raczej niż to, co jest , wykonywać operacje jak masz numer i używać wyjątków powiedzieć inaczej.

Steven Rumbalski
źródło
3
Robi mądrą rzeczą, zamiast rzeczy kaczki korzystne jest, gdy jesteś mnożenie wektora przez X. W tym przypadku chcesz robić różne rzeczy na podstawie tego, co X jest . (Może działać jako coś, co się mnoży, ale wynik może być bezsensowny.)
Evgeni Sergeev
3
ta odpowiedź mówi, że Prawda to liczba… która prawdopodobnie nie zawsze jest tym, czego chcesz. Dla wykluczenia booleanów (pomyśl o walidacji np.), Powiedziałbymisinstance(value, Number) and type(value) != bool
Yo Ludke
32

Chcesz sprawdzić, czy jakiś obiekt

w pewnych okolicznościach zachowuje się jak liczba

Jeśli używasz Pythona 2.5 lub starszego, jedynym prawdziwym sposobem jest sprawdzenie niektórych z tych „pewnych okoliczności” i zobaczenie.

W wersji 2.6 lub nowszej można używać isinstancez liczbami Numer - abstrakcyjna klasa bazowa (ABC), która istnieje dokładnie w tym celu (w collectionsmodule istnieje o wiele więcej ABC dla różnych form kolekcji / kontenerów, ponownie zaczynając od 2.6; oraz również tylko w tych wydaniach możesz łatwo dodać własne abstrakcyjne klasy bazowe, jeśli zajdzie taka potrzeba).

Bacha do 2.5 i wcześniejszych, „można dodać 0i nie jest iterowalne” może być w niektórych przypadkach dobrą definicją. Ale naprawdę musisz zadać sobie pytanie, co to jest, że pytasz, że to, co chcesz uznać za „liczbę”, musi zdecydowanie być w stanie zrobić , a czego absolutnie nie może zrobić - i sprawdzić.

Może to być również potrzebne w wersji 2.6 lub nowszej, być może w celu tworzenia własnych rejestracji w celu dodania typów, na których Ci zależy, a które nie zostały jeszcze zarejestrowane numbers.Numbers- jeśli chcesz wykluczyć niektóre typy, które twierdzą, że są numerami, ale ty po prostu nie mogę sobie z tym poradzić, to wymaga jeszcze większej ostrożności, ponieważ ABC nie ma unregistermetody [[na przykład możesz stworzyć własne ABC WeirdNumi zarejestrować tam wszystkie takie dziwne dla ciebie typy, a następnie najpierw sprawdzić, czy isinstancewyskoczysz, zanim przejdziesz dalej sprawdzanie isinstancenormalności, numbers.Numberaby kontynuować pomyślnie.

BTW, jeśli i kiedy chcesz sprawdzić, czy xmożesz lub nie możesz coś zrobić, generalnie musisz spróbować czegoś takiego:

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

Obecność __add__per se nie mówi nic użytecznego, ponieważ np. Wszystkie sekwencje mają je w celu połączenia z innymi sekwencjami. To sprawdzenie jest równoważne definicji „liczba jest czymś takim, że sekwencja takich rzeczy jest prawidłowym pojedynczym argumentem funkcji wbudowanej sum”. Całkowicie dziwne typy (np. Takie, które podnoszą „zły” wyjątek po zsumowaniu do 0, takie jak, powiedzmy, a ZeroDivisionErrorlub ValueError& c) będą propagować wyjątek, ale to jest w porządku, daj użytkownikowi jak najszybciej wiedzieć, że takie szalone typy są po prostu nie do przyjęcia w dobrym firma;-); ale „wektor”, który można podsumować jako skalar (standardowa biblioteka Pythona go nie ma, ale oczywiście są popularne jako rozszerzenia innych firm) również dałby tutaj zły wynik, więc (np.opcja „nie może być iterowalna” (np. sprawdź, czy iter(x)podbija TypeError, lub na obecność specjalnej metody __iter__- jeśli jesteś w 2.5 lub wcześniej i potrzebujesz własnych kontroli).

Krótkie spojrzenie na takie komplikacje może wystarczyć, aby zmotywować Cię do polegania na abstrakcyjnych klasach bazowych, gdy tylko jest to możliwe ... ;-).

Alex Martelli
źródło
Ale w module liczb jest ABC dla liczby. Tak twierdzi dokumentacja: „Moduł liczb (PEP 3141) definiuje hierarchię numerycznych abstrakcyjnych klas bazowych, które stopniowo definiują więcej operacji”.
Steven Rumbalski
17

To dobry przykład, w którym wyjątki naprawdę świecą. Po prostu zrób to, co zrobiłbyś z typami liczbowymi i wyłap TypeErrorwszystkie inne.

Ale oczywiście sprawdza tylko, czy operacja działa , a nie czy ma sens ! Jedynym realnym rozwiązaniem jest to, aby nigdy nie mieszać typów i zawsze dokładnie wiedzieć, do jakiej typeklasy należą twoje wartości.

Jochen Ritzel
źródło
1
+1 dla Duck Typing: nie ma znaczenia, jakiego typu są moje dane, tylko czy mogę z nimi robić, co chcę.
systempuntoout
12
Było to tradycyjne podejście, ale ABC zostały wprowadzone w dużej mierze po to, aby uciec od zwykłego pisania na klawiaturze i przesunąć pewien dystans w stronę świata, w którym isinstancemoże być faktycznie przydatna w wielu przypadkach (== "sprawdź to ma sens", jak również formalne zastosowanie operacji). Trudna zmiana dla osób od dawna korzystających wyłącznie z języka Python, ale bardzo ważny subtelny trend w filozofii Pythona, którego zignorowanie byłoby poważnym błędem.
Alex Martelli
@Alex: Prawda i kocham typeklasy (głównie collections.Sequencei przyjaciół). Ale afaik, nie ma takich klas dla liczb, wektorów ani żadnych innych obiektów matematycznych.
Jochen Ritzel
1
Nie ma nic przeciwko pisaniu na klawiaturze. To właśnie bym zrobił. Ale istnieje abstrakcyjna klasa bazowa dla liczb: liczby.
Steven Rumbalski
4

Pomnóż obiekt przez zero. Dowolna liczba razy zero daje zero. Każdy inny wynik oznacza, że ​​obiekt nie jest liczbą (łącznie z wyjątkami)

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

W ten sposób użycie isNumber da następujący wynik:

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

Wynik:

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)

Prawdopodobnie istnieją na świecie obiekty nieliczbowe, które __mul__po pomnożeniu przez zero definiują zwracanie zera, ale jest to skrajny wyjątek. To rozwiązanie powinno obejmować cały normalny i rozsądny kod, który generujesz / szyfrujesz.

przykład 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()))

wynik:

False == isNumber([0 1])
złośnica
źródło
5
True * 0 == 0
endolit
4
Twoja funkcja błędnie powie, że wartości logiczne to liczby
endolit
1
@endolith, wartości logiczne działają dokładnie jak liczby. Prawda zawsze == 1 i Fałsz zawsze == 0. Dokładnie o to pytający pytał: „Tutaj” jest ”zdefiniowane jako„ działa jak liczba w pewnych okolicznościach ”.
shrewmouse
1
@endolith, Właściwie wartości logiczne to liczby. Boolean pochodzi od, intwięc moja funkcja poprawnie powie, że wartości logiczne to liczby.
złośnica
1
@NicolasAbril, przekonwertuj 0 * x == 0 na bool wewnątrz isNumber.
shrewmouse
3

Aby przeformułować swoje pytanie, próbujesz określić, czy coś jest zbiorem czy pojedynczą wartością. Próba porównania, czy coś jest wektorem, czy liczbą, jest porównaniem jabłek z pomarańczami - mogę mieć wektor ciągów lub liczb i mogę mieć pojedynczy ciąg lub pojedynczą liczbę. Interesuje Cię, ile ich masz (1 lub więcej) , a nie jaki typ faktycznie posiadasz.

moim rozwiązaniem tego problemu jest sprawdzenie, czy dane wejściowe są pojedynczą wartością, czy kolekcją, sprawdzając obecność __len__. Na przykład:

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]

Lub, jeśli chodzi o podejście do pisania kaczego, możesz najpierw wypróbować iterację 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]

Ostatecznie łatwiej jest sprawdzić, czy coś jest podobne do wektora, niż sprawdzić, czy coś jest podobne do skalarnego. Jeśli przechodzą przez Ciebie wartości innego typu (np. Łańcuch, liczba itp.), Logika twojego programu może wymagać trochę pracy - w jaki sposób w pierwszej kolejności próbowałeś pomnożyć ciąg przez wektor numeryczny?

Gordon Bean
źródło
3

Podsumowanie / ocena istniejących metod:

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

(Przyszedłem tutaj przez to pytanie )

Kod

#!/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
źródło
DO ZROBIENIA dla siebie:, float('nan'), 'nan', '123.45', '42', '42a', '0x8', '0xa'dodajmath.isnan
Martin Thoma
2

Prawdopodobnie lepiej zrobić to na odwrót: sprawdzasz, czy to wektor. Jeśli tak, robisz iloczyn skalarny, a we wszystkich innych przypadkach próbujesz mnożyć przez skalar.

Sprawdzenie wektora jest łatwe, ponieważ powinien on należeć do typu Twojej klasy wektorów (lub być z niego dziedziczony). Możesz także spróbować najpierw wykonać iloczyn skalarny, a jeśli to się nie powiedzie (= tak naprawdę nie był to wektor), wtedy powróć do mnożenia przez skalar.

sth
źródło
1

Po prostu dodać. Być może możemy użyć kombinacji isinstance i isdigit w następujący sposób, aby sprawdzić, czy wartość jest liczbą (int, float itp.)

if isinstance (num1, int) or isinstance (num1, float) lub num1.isdigit ():

shadab.tughlaq
źródło
0

Dla hipotetycznej klasy wektorów:

Załóżmy, że vjest to wektor i mnożymy go przez x. Jeśli pomnożenie każdego składnika vprzez przez ma sens x, prawdopodobnie mieliśmy na myśli to, więc spróbuj najpierw. Jeśli nie, może możemy kropkować? W przeciwnym razie jest to błąd typu.

EDYCJA - poniższy kod nie działa, ponieważ 2*[0]==[0,0]zamiast podnosić TypeError. Zostawiam to, ponieważ zostało skomentowane.

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
źródło
jeśli xjest wektorem, to [comp * x for comp in self]da iloczyn zewnętrzny xan v. To jest tensor rangi 2, a nie skalar.
aaronasterling
zmień „nie jest skalarem” na „nie jest wektorem”. przynajmniej nie w oryginalnej przestrzeni wektorowej.
aaronasterling
Heh, właściwie oboje się mylimy. Jesteś przy założeniu, że comp*xbędą skalowane xprzez comp, byłem przy założeniu, że będzie podnieść TypeError. Niestety, w rzeczywistości połączy się xze sobą comprazy. Ups.
Katriel
meh. jeśli xjest wektorem, to powinien mieć __rmul__metodę method ( __rmul__ = __mul__), która comp * xpowinna być skalowana xw ten sam sposób, w jaki x * compjest to najwyraźniej zamierzone.
aaronasterling
0

Podobny problem miałem przy implementacji pewnego rodzaju klasy wektorowej. Jednym ze sposobów sprawdzenia liczby jest po prostu przekonwertowanie na jeden, czyli użycie

float(x)

Powinno to odrzucić przypadki, w których x nie może zostać zamienione na liczbę; ale może również odrzucić inne rodzaje struktur podobnych do liczb, które mogą być prawidłowe, na przykład liczby zespolone.

Ant6n
źródło
0

Jeśli chcesz wywołać różne metody w zależności od typu argumentów, spójrz na multipledispatch.

Na przykład, powiedzmy, że piszesz klasę wektorową. Jeśli masz inny wektor, chcesz znaleźć iloczyn skalarny. Jeśli masz skalar, chcesz przeskalować cały wektor.

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]

Niestety (o ile mi wiadomo) nie możemy pisać, @dispatch(Vector)ponieważ wciąż definiujemy typ Vector, więc nazwa typu nie jest jeszcze zdefiniowana. Zamiast tego używam typu podstawowego list, który pozwala nawet znaleźć iloczyn skalarny a Vectori a list.

AJNeufeld
źródło
0

Krótki i prosty sposób:

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

Wynik :

True

Jeśli obiekt jest ciągiem znaków, zostanie zwrócony „False”:

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

Wynik :

False
Shekhar
źródło
0

Masz element danych, powiedz, rec_dayże po zapisaniu do pliku będzie to float. Ale podczas przetwarzania programu może to być albo float, intalbo strtyp ( strjest używany podczas inicjowania nowego rekordu i zawiera fikcyjną wartość flagi).

Następnie możesz sprawdzić, czy masz numer z tym

                type(rec_day) != str 

Skonstruowałem program w Pythonie w ten sposób i po prostu umieściłem `` poprawkę konserwacyjną '', używając tego jako kontroli numerycznej. Czy to sposób w Pythonie? Najprawdopodobniej nie, odkąd programowałem w języku COBOL.

CopyPasteIt
źródło
-1

Możesz użyć funkcji isdigit ().

>>> x = "01234"
>>> a.isdigit()
True
>>> y = "1234abcd"
>>> y.isdigit()
False
rsy
źródło
„01234” nie jest liczbą, to ciąg znaków.
Alexey,