Pregunta ¿Cómo puedo verificar si una cadena es un número (float)?


¿Cuál es la mejor manera de verificar si una cadena se puede representar como un número en Python?

La función que tengo actualmente es:

def is_number(s):
    try:
        float(s)
        return True
    except ValueError:
        return False

Lo cual, no solo es feo y lento, parece torpe. Sin embargo, no he encontrado un método mejor porque llamar float en la función principal es aún peor.


1247
2017-12-09 20:03


origen


Respuestas:


Lo cual, no solo es feo y lento

Yo disputaría ambos.

Un regex u otro análisis de cadenas sería más feo y lento.

No estoy seguro de que mucho podría ser más rápido que el anterior. Llama a la función y regresa. Try / Catch no introduce mucha sobrecarga porque la excepción más común se detecta sin una búsqueda exhaustiva de los fotogramas de pila.

El problema es que cualquier función de conversión numérica tiene dos tipos de resultados

  • Un número, si el número es válido
  • Un código de estado (por ejemplo, vía errno) o una excepción para mostrar que no se pudo analizar ningún número válido.

C (como un ejemplo) piratea esto de varias maneras. Python lo establece de forma clara y explícita.

Creo que tu código para hacer esto es perfecto.


564
2017-12-09 20:30



En caso de que esté buscando números enteros de análisis (positivos, sin signo) en lugar de flotantes, puede usar isdigit() función para objetos de cuerda.

>>> a = "03523"
>>> a.isdigit()
True
>>> b = "963spam"
>>> b.isdigit()
False

Métodos de cadena - isdigit()

También hay algo en las cadenas Unicode, con el que no estoy demasiado familiarizado Unicode - Es decimal / decimal


1334
2017-12-09 20:15



Hay una excepción que quizás desee tener en cuenta: la cadena 'NaN'

Si quieres que is_number devuelva FALSE para 'NaN', este código no funcionará ya que Python lo convierte a su representación de un número que no es un número (habla sobre problemas de identidad):

>>> float('NaN')
nan

De lo contrario, en realidad debería agradecerte la parte del código que ahora uso extensivamente. :)

GRAMO.


64
2017-09-01 14:06



TL; DR La mejor solución es s.replace('.','',1).isdigit()

Hice algunos puntos de referencia comparando los diferentes enfoques

def is_number_tryexcept(s):
    """ Returns True is string is a number. """
    try:
        float(s)
        return True
    except ValueError:
        return False

import re    
def is_number_regex(s):
    """ Returns True is string is a number. """
    if re.match("^\d+?\.\d+?$", s) is None:
        return s.isdigit()
    return True


def is_number_repl_isdigit(s):
    """ Returns True is string is a number. """
    return s.replace('.','',1).isdigit()

Si la cadena no es un número, el bloque except es bastante lento. Pero, lo que es más importante, el método try-except es el único enfoque que maneja correctamente las notaciones científicas.

funcs = [
          is_number_tryexcept, 
          is_number_regex,
          is_number_repl_isdigit
          ]

a_float = '.1234'

print('Float notation ".1234" is not supported by:')
for f in funcs:
    if not f(a_float):
        print('\t -', f.__name__)

La notación flotante ".1234" no es compatible con:
- is_number_regex

scientific1 = '1.000000e+50'
scientific2 = '1e50'


print('Scientific notation "1.000000e+50" is not supported by:')
for f in funcs:
    if not f(scientific1):
        print('\t -', f.__name__)




print('Scientific notation "1e50" is not supported by:')
for f in funcs:
    if not f(scientific2):
        print('\t -', f.__name__)

La notación científica "1.000000e + 50" no es compatible con:
- is_number_regex
- is_number_repl_isdigit
La notación científica "1e50" no es compatible con:
- is_number_regex
- is_number_repl_isdigit

EDITAR: Los resultados de referencia

import timeit

test_cases = ['1.12345', '1.12.345', 'abc12345', '12345']
times_n = {f.__name__:[] for f in funcs}

for t in test_cases:
    for f in funcs:
        f = f.__name__
        times_n[f].append(min(timeit.Timer('%s(t)' %f, 
                      'from __main__ import %s, t' %f)
                              .repeat(repeat=3, number=1000000)))

donde se probaron las siguientes funciones

from re import match as re_match
from re import compile as re_compile

def is_number_tryexcept(s):
    """ Returns True is string is a number. """
    try:
        float(s)
        return True
    except ValueError:
        return False

def is_number_regex(s):
    """ Returns True is string is a number. """
    if re_match("^\d+?\.\d+?$", s) is None:
        return s.isdigit()
    return True


comp = re_compile("^\d+?\.\d+?$")    

def compiled_regex(s):
    """ Returns True is string is a number. """
    if comp.match(s) is None:
        return s.isdigit()
    return True


def is_number_repl_isdigit(s):
    """ Returns True is string is a number. """
    return s.replace('.','',1).isdigit()

enter image description here


64
2018-05-13 19:28



Qué tal esto:

'3.14'.replace('.','',1).isdigit()

que volverá verdadero solo si hay uno o no '.' en la cadena de dígitos.

'3.14.5'.replace('.','',1).isdigit()

devolverá falso

editar: acabo de ver otro comentario ... agregando un .replace(badstuff,'',maxnum_badstuff) para otros casos se puede hacer. si está pasando sal y no condimentos arbitrarios (ref:xkcd # 974) Esto hará bien: P


52
2018-05-25 22:22



Actualizado después de que Alfe señaló que no es necesario verificar el flotador por separado ya que maneja ambos complejos:

def is_number(s):
    try:
        complex(s) # for int, long, float and complex
    except ValueError:
        return False

    return True

Anteriormente dicho: En algunos casos excepcionales, es posible que también deba verificar los números complejos (por ejemplo, 1 + 2i), que no pueden representarse mediante un flotador:

def is_number(s):
    try:
        float(s) # for int, long and float
    except ValueError:
        try:
            complex(s) # for complex
        except ValueError:
            return False

    return True

38
2017-12-11 04:56



Lo cual, no solo es feo y lento, parece torpe.

Puede tomar algún tiempo acostumbrarse, pero esta es la manera pitónica de hacerlo. Como ya se ha señalado, las alternativas son peores. Pero hay otra ventaja de hacer las cosas de esta manera: polimorfismo.

La idea central detrás de la tipificación de patos es que "si camina y habla como un pato, entonces es un pato". ¿Qué sucede si decide que necesita una subclase de cadena para poder cambiar la forma en que determina si algo se puede convertir en una flotación? ¿O qué pasa si decides probar algún otro objeto por completo? Puede hacer estas cosas sin tener que cambiar el código anterior.

Otros idiomas resuelven estos problemas mediante el uso de interfaces. Guardaré el análisis de qué solución es mejor para otro hilo. El punto, sin embargo, es que Python está decididamente en el lado del tipaje de tipeo de la ecuación, y es probable que tengas que acostumbrarte a la sintaxis así si planeas hacer mucha programación en Python (pero eso no significa te tiene que gustar, por supuesto).

Otra cosa que quizás desee tener en cuenta: Python es bastante rápido al lanzar y atrapar excepciones en comparación con muchos otros lenguajes (30 veces más rápido que .Net, por ejemplo). Demonios, el lenguaje en sí mismo incluso arroja excepciones para comunicar condiciones de programa normales y no excepcionales (cada vez que utiliza un bucle for). Por lo tanto, no me preocuparía demasiado por los aspectos de rendimiento de este código hasta que note un problema importante.


37
2017-09-08 08:42



por int utilizar esta:

>>> "1221323".isdigit()
True

Pero para float necesitamos algunos trucos ;-). Cada número de flotador tiene un punto ...

>>> "12.34".isdigit()
False
>>> "12.34".replace('.','',1).isdigit()
True
>>> "12.3.4".replace('.','',1).isdigit()
False

También para números negativos solo agrega lstrip():

>>> '-12'.lstrip('-')
'12'

Y ahora tenemos una forma universal:

>>> '-12.34'.lstrip('-').replace('.','',1).isdigit()
True
>>> '.-234'.lstrip('-').replace('.','',1).isdigit()
False

18
2018-02-18 01:35