Question Comment puis-je vérifier si une chaîne est un nombre (float)?


Quel est le meilleur moyen de vérifier si une chaîne peut être représentée comme un nombre en Python?

La fonction que j'ai actuellement est:

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

Qui, non seulement est laide et lente, semble maladroit. Cependant, je n'ai pas trouvé une meilleure méthode car appeler float dans la fonction principale est encore pire.


1247
2017-12-09 20:03


origine


Réponses:


Lequel, non seulement est laid et lent

Je me disputerais les deux.

Une regex ou une autre analyse de chaîne serait plus laide et plus lente.

Je ne suis pas sûr que quelque chose pourrait être plus rapide que ce qui précède. Il appelle la fonction et retourne. Try / Catch n'introduit pas beaucoup de surcharge car l'exception la plus courante est interceptée sans une recherche approfondie des cadres de pile.

Le problème est que toute fonction de conversion numérique a deux types de résultats

  • Un nombre, si le nombre est valide
  • Un code d'état (par exemple, via errno) ou une exception pour montrer qu'aucun numéro valide n'a pu être analysé.

C (à titre d'exemple) hacks autour de ce un certain nombre de façons. Python l'énonce clairement et explicitement.

Je pense que votre code pour le faire est parfait.


564
2017-12-09 20:30



Si vous cherchez à analyser des entiers (positifs, non signés) au lieu de floats, vous pouvez utiliser le isdigit() fonction pour les objets chaîne.

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

Méthodes de chaîne - isdigit()

Il y a aussi quelque chose sur les chaînes Unicode, que je ne connais pas trop Unicode - est décimal / décimal


1334
2017-12-09 20:15



Il y a une exception que vous voudrez peut-être prendre en compte: la chaîne 'NaN'

Si vous voulez que is_number retourne FALSE pour 'NaN' ce code ne fonctionnera pas car Python le convertira en sa représentation d'un nombre qui n'est pas un nombre (parlez des problèmes d'identité):

>>> float('NaN')
nan

Sinon, je devrais vous remercier pour le morceau de code que j'utilise maintenant beaucoup. :)

G.


64
2017-09-01 14:06



TL; DR La meilleure solution est s.replace('.','',1).isdigit()

J'en ai fait repères comparer les différentes approches

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 chaîne n'est pas un nombre, l'exception-block est assez lent. Mais plus important encore, la méthode try-except est la seule approche qui gère correctement les notations scientifiques.

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 notation flottante ".1234" n'est pas supportée par:
- 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 notation scientifique "1.000000e + 50" n'est pas supportée par:
- is_number_regex
- is_number_repl_isdigit
La notation scientifique "1e50" n'est pas supportée par:
- is_number_regex
- is_number_repl_isdigit

EDIT: Les résultats de référence

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

où les fonctions suivantes ont été testées

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



que dis-tu de ça:

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

qui ne sera vrai que s'il y en a un ou pas '.' dans la chaîne de chiffres.

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

retournera false

edit: juste vu un autre commentaire ... ajouter un .replace(badstuff,'',maxnum_badstuff) pour d'autres cas, cela peut être fait. si vous passez du sel et non des condiments arbitraires (ref:xkcd # 974) ça ira bien: P


52
2018-05-25 22:22



Mis à jour après que Alfe a souligné que vous n'avez pas besoin de vérifier séparément le flotteur comme des poignées complexes à la fois:

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

    return True

Précédemment dit: Dans certains cas rares, vous pourriez également avoir besoin de vérifier les nombres complexes (par exemple 1 + 2i), qui ne peuvent pas être représentés par un flottant:

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



Qui, non seulement est laide et lente, semble maladroit.

Cela peut prendre un peu de temps pour s'y habituer, mais c'est la façon pythonique de le faire. Comme cela a déjà été souligné, les alternatives sont pires. Mais il y a un autre avantage à faire les choses de cette façon: le polymorphisme.

L'idée centrale du typage du canard est que "s'il marche et parle comme un canard, alors c'est un canard". Que se passe-t-il si vous décidez que vous avez besoin de sous-classer une chaîne pour pouvoir modifier la façon dont vous déterminez si quelque chose peut être converti en un flottant? Ou si vous décidez de tester un autre objet entièrement? Vous pouvez faire ces choses sans avoir à changer le code ci-dessus.

D'autres langues résolvent ces problèmes en utilisant des interfaces. Je vais enregistrer l'analyse de la solution qui est la meilleure pour un autre thread. Le point, cependant, est que python est décidément du côté du typage du canard, et vous devrez probablement vous habituer à la syntaxe si vous envisagez de faire beaucoup de programmation en Python (mais cela ne veut pas dire vous devez l'aimer bien sûr).

Une autre chose que vous pourriez vouloir prendre en considération: Python est assez rapide en jetant et attrapant des exceptions par rapport à beaucoup d'autres langues (30x plus rapide que .Net par exemple). Heck, le langage lui-même lance même des exceptions pour communiquer des conditions de programme normales non exceptionnelles (chaque fois que vous utilisez une boucle for). Ainsi, je ne m'inquiéterais pas trop des aspects de performance de ce code jusqu'à ce que vous notiez un problème significatif.


37
2017-09-08 08:42



Pour int utilisez ceci:

>>> "1221323".isdigit()
True

Mais pour float nous avons besoin de quelques trucs ;-). Chaque nombre flottant a un point ...

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

Aussi pour les nombres négatifs il suffit d'ajouter lstrip():

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

Et maintenant nous obtenons un moyen universel:

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

18
2018-02-18 01:35