¿Cuál es la forma más pitónica de comprobar si un objeto es un número?

114

Dado un objeto de Python arbitrario, ¿cuál es la mejor manera de determinar si es un número? Aquí isse define como acts like a number in certain circumstances.

Por ejemplo, digamos que está escribiendo una clase de vector. Si se le da otro vector, desea encontrar el producto escalar. Si se le da un escalar, desea escalar todo el vector.

Comprobación de si algo es int, float, long, booles molesto y no cubre los objetos definidos por el usuario que podrían actuar como números. Pero buscar __mul__, por ejemplo, no es lo suficientemente bueno porque la clase de vector que acabo de describir definiría __mul__, pero no sería el tipo de número que quiero.

Claudiu
fuente

Respuestas:

135

Úselo Numberdesde el numbersmódulo para probar isinstance(n, Number)(disponible desde 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

Esto, por supuesto, es contrario a la escritura de pato. Si está más preocupado por cómo actúa un objeto que por lo que es , realice sus operaciones como si tuviera un número y use excepciones para indicarle lo contrario.

Steven Rumbalski
fuente
3
Haciendo lo más inteligente, en lugar de la cosa pato, se prefiere cuando se está multiplicando un vector por X. En este caso usted quiere hacer cosas diferentes en base a lo que X es . (Puede actuar como algo que se multiplica, pero el resultado puede no tener sentido.)
Evgeni Sergeev
3
esta respuesta da diría que Verdadero es un número ... que probablemente no siempre sea lo que desea. Para excluir booleanos (piense en validación fe), yo diríaisinstance(value, Number) and type(value) != bool
Yo Ludke
32

Quieres comprobar si algún objeto

actúa como un número en determinadas circunstancias

Si está utilizando Python 2.5 o anterior, la única forma real es verificar algunas de esas "ciertas circunstancias" y ver.

En 2.6 o superior, puede usar isinstancecon números . Número : una clase base abstracta (ABC) que existe exactamente para este propósito (existen muchos más ABC en el collectionsmódulo para varias formas de colecciones / contenedores, nuevamente comenzando con 2.6; y, Además, solo en esas versiones, puede agregar fácilmente sus propias clases base abstractas si es necesario).

Bach a 2.5 y anteriores, "se puede agregar 0y no es iterable" podría ser una buena definición en algunos casos. Pero, realmente debe preguntarse qué es lo que está preguntando que lo que quiere considerar "un número" definitivamente debe poder hacer , y qué debe ser absolutamente incapaz de hacer, y verificar.

Esto también puede ser necesario en 2.6 o posterior, quizás con el propósito de hacer sus propios registros para agregar los tipos que le interesan y en los que aún no se han registrado numbers.Numbers, si desea excluir algunos tipos que afirman que son números, pero usted simplemente no puedo manejar, eso requiere aún más cuidado, ya que los ABC no tienen ningún unregistermétodo [[por ejemplo, puede hacer su propio ABC WeirdNumy registrar allí todos los tipos extraños para usted, luego primero verifique isinstanceque estén disponibles antes de continuar a la comprobación isinstancede lo normal numbers.Numberpara continuar con éxito.

Por cierto, siempre que necesite verificar si xpuede o no puede hacer algo, generalmente debe probar algo como:

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

La presencia de __add__per se no le dice nada útil, ya que, por ejemplo, todas las secuencias lo tienen con el propósito de concatenación con otras secuencias. Esta comprobación es equivalente a la definición "un número es algo tal que una secuencia de tales cosas es un único argumento válido para la función incorporada sum", por ejemplo. Los tipos totalmente extraños (por ejemplo, los que generan la excepción "incorrecta" cuando se suman a 0, como, por ejemplo, a ZeroDivisionErroro ValueError& c) propagarán la excepción, pero está bien, deje que el usuario sepa lo antes posible que esos tipos locos simplemente no son aceptables en el bien empresa;-); pero, un "vector" que se puede sumar a un escalar (la biblioteca estándar de Python no tiene uno, pero por supuesto son populares como extensiones de terceros) también daría un resultado incorrecto aquí, así que (por ejemplo,el "no permitido que sea iterable" (por ejemplo, verificar que iter(x)aumente TypeError, o la presencia de un método especial __iter__, si está en 2.5 o antes y, por lo tanto, necesita sus propias verificaciones).

Un breve vistazo a tales complicaciones puede ser suficiente para motivarte a confiar en clases base abstractas siempre que sea posible ... ;-).

Alex Martelli
fuente
Pero hay un ABC para Número en el módulo de números. Eso es lo que afirman los documentos: "El módulo de números (PEP 3141) define una jerarquía de clases base abstractas numéricas que definen progresivamente más operaciones".
Steven Rumbalski
17

Este es un buen ejemplo en el que las excepciones realmente brillan. Simplemente haga lo que haría con los tipos numéricos y obtenga el TypeErrorde todo lo demás.

Pero obviamente, esto solo verifica si una operación funciona , ¡no si tiene sentido ! La única solución real para eso es nunca mezclar tipos y siempre saber exactamente a qué clase de tipos pertenecen sus valores.

Jochen Ritzel
fuente
1
+1 para Duck Typing: no importa de qué tipo sean mis datos, solo si puedo o no hacer lo que quiera con ellos.
systemmpuntoout el
12
Este era el enfoque tradicional, pero los ABC se han introducido en buena parte para alejarse de la mecanografía de pato puro y cambiar cierta distancia hacia un mundo donde isinstancepuede ser realmente útil en muchos casos (== "comprobar que tiene sentido", así como la aplicabilidad formal de operaciones). Cambio difícil para las personas que solo usan Python desde hace mucho tiempo, pero una tendencia sutil muy importante en la filosofía de Python que sería un error grave ignorar.
Alex Martelli
@Alex: Es cierto y me encantan las clases de tipos (sobre todo collections.Sequencey amigos). Pero afaik, no existen tales clases para números, vectores o cualquier otro objeto matemático.
Jochen Ritzel
1
Nada en contra de la escritura de pato. Es lo que haría yo. Pero hay una clase base abstracta para números: números.
Steven Rumbalski
4

Multiplica el objeto por cero. Cualquier número multiplicado por cero es cero. Cualquier otro resultado significa que el objeto no es un número (incluidas las excepciones)

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

El uso de isNumber de esta manera dará el siguiente resultado:

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

Salida:

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)

Probablemente hay algunos objetos no numéricos en el mundo que definen __mul__devolver cero cuando se multiplica por cero, pero esa es una excepción extrema. Esta solución debe cubrir todo lo normal y sano. código que genere / encuentre.

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

salida:

False == isNumber([0 1])
musaraña
fuente
5
True * 0 == 0
endolito
4
Su función será incorrectamente dicen que son números booleanos
endolito
1
@endolith, los booleanos actúan exactamente como los números. Verdadero siempre == 1 y Falso siempre == 0. Esto es exactamente lo que preguntó el interlocutor, "Aquí 'es' se define como 'actúa como un número en determinadas circunstancias'".
musaraña
1
@endolith, en realidad, los booleanos son números. Boolean se deriva de intpor lo que mi función dirá correctamente que los booleanos son números.
musaraña
1
@NicolasAbril, convierte 0 * x == 0 a un bool dentro de isNumber.
musaraña
3

Para reformular su pregunta, está tratando de determinar si algo es una colección o un valor único. Tratar de comparar si algo es un vector o un número es comparar manzanas con naranjas: puedo tener un vector de cadenas o números, y puedo tener una sola cadena o un solo número. Le interesa cuántos tiene (1 o más) , no qué tipo tiene realmente.

mi solución para este problema es verificar si la entrada es un valor único o una colección al verificar la presencia de __len__. Por ejemplo:

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]

O, para el enfoque de tipificación de pato, puede intentar iterar fooprimero:

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]

En última instancia, es más fácil probar si algo es similar a un vector que probar si algo es similar a un escalar. Si tiene valores de diferente tipo (es decir, cadena, numérico, etc.), entonces la lógica de su programa puede necesitar algo de trabajo. ¿Cómo terminó tratando de multiplicar una cadena por un vector numérico en primer lugar?

Gordon Bean
fuente
3

Para resumir / evaluar los métodos existentes:

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

(Vine aquí por esta pregunta )

Código

#!/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))
Martín Thoma
fuente
TODO para mí:, float('nan'), 'nan', '123.45', '42', '42a', '0x8', '0xa'agregarmath.isnan
Martin Thoma
2

Probablemente sea mejor hacerlo al revés: comprueba si es un vector. Si es así, realiza un producto escalar y en todos los demás casos intenta la multiplicación escalar.

Verificar el vector es fácil, ya que debe ser de su tipo de clase de vector (o heredado de él). También puede intentar primero hacer un producto punto, y si eso falla (= no era realmente un vector), luego recurrir a la multiplicación escalar.

algo
fuente
1

Solo para agregar. Quizás podamos usar una combinación de isinstance e isdigit de la siguiente manera para encontrar si un valor es un número (int, float, etc.)

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

shadab.tughlaq
fuente
0

Para la clase de vector hipotética:

Supongamos que ves un vector y lo multiplicamos por x. Si tiene sentido multiplicar cada componente de vpor x, probablemente lo decimos en serio, así que inténtalo primero. Si no es así, ¿quizás podamos puntear? De lo contrario, es un error de tipo.

EDITAR : el siguiente código no funciona, porque en 2*[0]==[0,0]lugar de generar un TypeError. Lo dejo porque fue comentado.

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
fuente
si xes un vector, entonces se [comp * x for comp in self]obtendrá el producto externo de xan v. Este es un tensor de rango 2, no un escalar.
aaronasterling
cambie "no un escalar" a "no un vector". al menos no en el espacio vectorial original.
aaronasterling
Je, en realidad los dos estamos equivocados. Estás asumiendo que comp*xva a escalar xpor comp, estaba asumiendo que plantearía un TypeError. Desafortunadamente, en realidad se concatenará xconsigo mismo compveces. ¡Ups!
Katriel
meh. si xes un vector, entonces debería tener un __rmul__método ( __rmul__ = __mul__) de modo que comp * xdebería escalar xde la misma manera que x * compaparentemente se pretende.
aaronasterling
0

Tuve un problema similar al implementar una especie de clase vectorial. Una forma de verificar un número es convertirlo a uno, es decir, usando

float(x)

Esto debería rechazar los casos en los que x no se puede convertir en un número; pero también puede rechazar otros tipos de estructuras de tipo numérico que podrían ser válidas, por ejemplo, números complejos.

Antón
fuente
0

Si desea llamar a diferentes métodos según el tipo de argumento, consulte multipledispatch.

Por ejemplo, digamos que está escribiendo una clase de vector. Si se le da otro vector, desea encontrar el producto escalar. Si se le da un escalar, desea escalar todo el vector.

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]

Desafortunadamente, (que yo sepa) no podemos escribir @dispatch(Vector)porque todavía estamos definiendo el tipo Vector, por lo que el nombre del tipo aún no está definido. En su lugar, estoy usando el tipo base list, que le permite incluso encontrar el producto escalar de a Vectory a list.

AJNeufeld
fuente
0

Manera corta y sencilla:

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

Salida:

True

Si el objeto es una cadena, se devolverá 'Falso':

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

Salida:

False
Shekhar
fuente
0

Tiene un elemento de datos, digamos rec_dayque cuando se escribe en un archivo será un float. Pero durante el procesamiento del programa puede ser float, into strtipo ( strse usa al inicializar un nuevo registro y contiene un valor de indicador ficticio).

Luego puede verificar si tiene un número con este

                type(rec_day) != str 

Estructuré un programa de Python de esta manera y solo puse un 'parche de mantenimiento' usando esto como una verificación numérica. ¿Es la forma pitónica? Lo más probable es que no, ya que solía programar en COBOL.

CopyPasteIt
fuente
-1

Puede utilizar la función isdigit ().

>>> x = "01234"
>>> a.isdigit()
True
>>> y = "1234abcd"
>>> y.isdigit()
False
rsy
fuente
"01234" no es un número, es una cadena de caracteres.
Alexey