Qual è il modo più pitonico per verificare se un oggetto è un numero?


114

Dato un oggetto Python arbitrario, qual è il modo migliore per determinare se si tratta di un numero? Qui isè definito come acts like a number in certain circumstances.

Ad esempio, supponi di scrivere una classe vettoriale. Se viene fornito un altro vettore, si desidera trovare il prodotto scalare. Se viene fornito uno scalare, si desidera scalare l'intero vettore.

Controllare se qualcosa è int, float, long, boolè fastidioso e non copre gli oggetti definiti dall'utente che potrebbero agire come i numeri. Ma il controllo __mul__, ad esempio, non è abbastanza buono perché la classe vettoriale che ho appena descritto definirebbe __mul__, ma non sarebbe il tipo di numero che desidero.

Risposte:


135

Utilizzare Numberdal numbersmodulo per testare isinstance(n, Number)(disponibile dalla 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

Questo è, ovviamente, contrario alla dattilografia. Se sei più preoccupato di come si comporta un oggetto piuttosto che di ciò che è , esegui le tue operazioni come se avessi un numero e usa le eccezioni per dirti il ​​contrario.


3
Fare la cosa intelligente, piuttosto che la cosa anatra, è preferito quando si sta moltiplicando un vettore da X. In questo caso si vuole fare cose diverse in base a ciò che X è . (Potrebbe agire come qualcosa che si moltiplica, ma il risultato potrebbe essere senza senso.)
Evgeni Sergeev

3
questa risposta darebbe dire che True è un numero .. che probabilmente non è sempre quello che vuoi. Per escludere i booleani (si pensi alla convalida), direiisinstance(value, Number) and type(value) != bool
Yo Ludke

32

Vuoi controllare se qualche oggetto

agisce come un numero in determinate circostanze

Se stai usando Python 2.5 o precedente, l'unico vero modo è controllare alcune di quelle "determinate circostanze" e vedere.

In 2.6 o superiore, puoi usare isinstancecon numbers.Number - una classe base astratta (ABC) che esiste esattamente per questo scopo (esistono molti più ABC nelcollections modulo per varie forme di collezioni / contenitori, sempre a partire da 2.6; e, inoltre, solo in queste versioni, puoi facilmente aggiungere le tue classi base astratte se necessario).

Bach a 2.5 e precedenti, "può essere aggiunto 0e non è iterabile" potrebbe essere una buona definizione in alcuni casi. Ma devi davvero chiederti cosa stai chiedendo che ciò che vuoi considerare "un numero" deve essere sicuramente in grado di fare e cosa deve essere assolutamente incapace di fare - e controllare.

Ciò potrebbe essere necessario anche nella versione 2.6 o successiva, forse allo scopo di effettuare le tue registrazioni per aggiungere tipi a cui tieni che non sono già stati registrati numbers.Numbers- se desideri escludere alcuni tipi che affermano di essere numeri ma tu semplicemente non riesco a gestirlo, questo richiede ancora più attenzione, poiché gli ABC non hannounregister metodo [[per esempio potresti creare il tuo ABC WeirdNume registrare lì tutti questi tipi strani per te, quindi prima controlla per isinstancesalvarli prima di procedere al controllo isinstancedel normale numbers.Numberper continuare con successo.

A proposito, se e quando è necessario controllare se x puoi o non puoi fare qualcosa, in genere devi provare qualcosa del tipo:

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

La presenza di __add__per sé non dice nulla di utile, poiché ad esempio tutte le sequenze lo hanno allo scopo di concatenarsi con altre sequenze. Questo controllo è equivalente alla definizione "un numero è qualcosa tale che una sequenza di tali cose è un singolo argomento valido per la funzione incorporata sum", per esempio. Tipi totalmente strani (ad esempio quelli che sollevano l'eccezione "sbagliata" quando sommata a 0, come, ad esempio, a ZeroDivisionErroroValueError & c) propagheranno l'eccezione, ma va bene, fai sapere all'utente al più presto che questi tipi pazzi non sono accettabili in bene azienda;-); ma, un "vettore" sommabile a uno scalare (la libreria standard di Python non ne ha uno, ma ovviamente sono popolari come estensioni di terze parti) darebbe anche il risultato sbagliato qui, quindi (ad es.quello "non può essere iterabile" (ad esempio, controlla che iter(x)solleva TypeError, o per la presenza di un metodo speciale __iter__- se sei in 2.5 o precedente e quindi hai bisogno dei tuoi controlli).

Una breve occhiata a tali complicazioni può essere sufficiente per motivarti a fare affidamento invece su classi base astratte ogniqualvolta sia fattibile ... ;-).


Ma c'è un ABC per Numero nel modulo dei numeri. Questo è ciò che affermano i documenti: "Il modulo dei numeri (PEP 3141) definisce una gerarchia di classi base astratte numeriche che definiscono progressivamente più operazioni".
Steven Rumbalski

17

Questo è un buon esempio in cui le eccezioni brillano davvero. Fai semplicemente quello che faresti con i tipi numerici e prendi il TypeErrorda tutto il resto.

Ma ovviamente, questo controlla solo se un'operazione funziona , non se ha senso ! L'unica vera soluzione per questo è non mescolare mai i tipi e sapere sempre esattamente a quale classe di tipi appartengono i tuoi valori.


1
+1 per Duck Typing: non importa di che tipo siano i miei dati, solo se posso o meno farne ciò che voglio.
systempuntoout

12
Questo era l'approccio tradizionale, ma gli ABC sono stati introdotti in buona parte per allontanarsi dalla semplice digitazione a papera e spostare una certa distanza verso un mondo in cui isinstancepuò essere effettivamente utile in molti casi (== "controlla che abbia senso" oltre all'applicabilità formale delle operazioni). Cambiamento difficile per le persone di lunga data che utilizzano solo Python, ma una sottile tendenza molto importante nella filosofia di Python che sarebbe un grave errore ignorare.
Alex Martelli

@ Alex: Vero e adoro i typeclasses (soprattutto collections.Sequencee gli amici). Ma in fin dei conti, non esistono classi del genere per numeri, vettori o altri oggetti matematici.
Jochen Ritzel

1
Niente contro la dattilografia. È quello che farei io. Ma esiste una classe base astratta per i numeri: numbers.Number.
Steven Rumbalski

4

Moltiplica l'oggetto per zero. Qualsiasi numero per zero è zero. Qualsiasi altro risultato significa che l'oggetto non è un numero (eccezioni incluse)

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

L'uso di isNumber in questo modo darà il seguente output:

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

Produzione:

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)

Probabilmente ci sono alcuni oggetti non numerici nel mondo che definiscono __mul__di restituire zero quando moltiplicati per zero, ma questa è un'eccezione estrema. Questa soluzione dovrebbe coprire tutto il codice normale e sano che generi / incontri.

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

produzione:

False == isNumber([0 1])

5
True * 0 == 0
endolith

4
La tua funzione dirà erroneamente che i booleani sono numeri
endolith

1
@endolith, i booleani si comportano esattamente come i numeri. Vero sempre == 1 e Falso sempre == 0. Questo è esattamente ciò che l'interrogante ha chiesto, "Qui 'è' è definito come 'agisce come un numero in determinate circostanze'."
Shrewmouse

1
@endolith, In realtà, i booleani sono numeri. Boolean deriva da intquindi la mia funzione dirà correttamente che i booleani sono numeri.
Shrewmouse

1
@NicolasAbril, converte 0 * x == 0 in un bool all'interno di isNumber.
toporagno

3

Per riformulare la tua domanda, stai cercando di determinare se qualcosa è una raccolta o un singolo valore. Cercare di confrontare se qualcosa è un vettore o un numero sta confrontando le mele con le arance: posso avere un vettore di stringhe o numeri e posso avere una singola stringa o un singolo numero. Sei interessato a quanti ne hai (1 o più) , non al tipo che hai effettivamente.

la mia soluzione per questo problema è verificare se l'input è un valore singolo o una collezione verificando la presenza di __len__. Per esempio:

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]

Oppure, per l'approccio della digitazione a papera, puoi provare a ripetere fooprima:

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]

In definitiva, è più facile verificare se qualcosa è simile a un vettore che verificare se qualcosa è simile a uno scalare. Se hai valori di tipo diverso (ad es. Stringa, numerico, ecc.), La logica del tuo programma potrebbe richiedere un po 'di lavoro: come hai finito per cercare di moltiplicare una stringa per un vettore numerico?


3

Per riassumere / valutare i metodi esistenti:

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

(Sono venuto qui per questa domanda )

Codice

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

TODO per me:, float('nan'), 'nan', '123.45', '42', '42a', '0x8', '0xa'aggiungimath.isnan
Martin Thoma

2

Probabilmente è meglio farlo al contrario: controlla se è un vettore. Se lo è, fai un prodotto scalare e in tutti gli altri casi provi la moltiplicazione scalare.

Controllare il vettore è facile, poiché dovrebbe essere del tuo tipo di classe vettoriale (o ereditato da esso). Potresti anche provare prima a fare un prodotto scalare, e se fallisce (= non era realmente un vettore), poi ripiegare sulla moltiplicazione scalare.


1

Solo per aggiungere. Forse possiamo usare una combinazione di isinstance e isdigit come segue per scoprire se un valore è un numero (int, float, ecc.)

se isinstance (num1, int) o isinstance (num1, float) o num1.isdigit ():


0

Per l'ipotetica classe vettoriale:

Supponiamo vsia un vettore e lo stiamo moltiplicando per x. Se ha senso moltiplicare ogni componente di vper x, probabilmente lo intendevamo, quindi prova prima. In caso contrario, forse possiamo puntare? Altrimenti è un errore di tipo.

EDIT : il codice seguente non funziona, perché 2*[0]==[0,0]invece di sollevare un file TypeError. Lo lascio perché è stato commentato.

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 )

se xè un vettore allora [comp * x for comp in self]produrrà il prodotto esterno di xun v. Questo è un tensore di rango 2, non uno scalare.
aaronasterling

cambia "non uno scalare" in "non un vettore". almeno non nello spazio vettoriale originale.
aaronasterling

Eh, in realtà ci sbagliamo entrambi. Stai assumendo che comp*xscalerà xda comp, mi è stato presupposto che avrebbe sollevato un TypeError. Sfortunatamente, si concatenerà effettivamente xcon se stesso compvolte. Ops.
Katriel

meh. se xè un vettore, dovrebbe avere un __rmul__metodo ( __rmul__ = __mul__) in modo che comp * xdovrebbe scalare xnello stesso modo in cui x * compè apparentemente inteso.
aaronasterling

0

Ho avuto un problema simile, durante l'implementazione di una sorta di classe vettoriale. Un modo per verificare la presenza di un numero è semplicemente convertirlo in uno, ovvero utilizzando

float(x)

Questo dovrebbe rifiutare i casi in cui x non può essere convertito in un numero; ma può anche rifiutare altri tipi di strutture simili ai numeri che potrebbero essere valide, ad esempio i numeri complessi.


0

Se vuoi chiamare metodi diversi a seconda del tipo di argomento, guarda in multipledispatch.

Ad esempio, supponi di scrivere una classe vettoriale. Se viene fornito un altro vettore, si desidera trovare il prodotto scalare. Se viene fornito uno scalare, si desidera scalare l'intero vettore.

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]

Sfortunatamente, (a mia conoscenza) non possiamo scrivere @dispatch(Vector)poiché stiamo ancora definendo il tipo Vector, quindi il nome del tipo non è ancora definito. Invece, sto usando il tipo di base list, che ti consente di trovare anche il prodotto scalare di a Vectore a list.


0

Modo breve e semplice:

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

Produzione :

True

Se l'oggetto è una stringa, verrà restituito "False":

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

Produzione :

False

0

Hai un elemento dati, dì rec_dayche quando scritto su un file sarà un file float. Ma durante l'elaborazione del programma può essere sia float, into stril tipo (ilstr viene utilizzato quando si inizializza un nuovo record e contiene un valore di flag fittizio).

Puoi quindi verificare se hai un numero con questo

                type(rec_day) != str 

Ho strutturato un programma Python in questo modo e ho appena inserito la "patch di manutenzione" usando questo come controllo numerico. È il modo pitonico? Molto probabilmente no da quando programmavo in COBOL.


-1

Potresti usare la funzione isdigit ().

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

"01234" non è un numero, è una stringa di caratteri.
Alexey
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.