Vérifier si une chaîne peut être convertie en float en Python

182

J'ai du code Python qui parcourt une liste de chaînes et les convertit en nombres entiers ou en nombres à virgule flottante si possible. Faire cela pour les entiers est assez facile

if element.isdigit():
  newelement = int(element)

Les nombres à virgule flottante sont plus difficiles. À l'heure actuelle, j'utilise partition('.')pour diviser la chaîne et vérifier que l'un ou les deux côtés sont des chiffres.

partition = element.partition('.')
if (partition[0].isdigit() and partition[1] == '.' and partition[2].isdigit()) 
    or (partition[0] == '' and partition[1] == '.' and partition[2].isdigit()) 
    or (partition[0].isdigit() and partition[1] == '.' and partition[2] == ''):
  newelement = float(element)

Cela fonctionne, mais évidemment la déclaration if pour cela est un peu un ours. L'autre solution que j'ai envisagée est de simplement envelopper la conversion dans un bloc try / catch et de voir si elle réussit, comme décrit dans cette question .

Quelqu'un a-t-il d'autres idées? Opinions sur les mérites relatifs de la partition et des approches try / catch?

Chris Upchurch
la source

Réponses:

305

J'utiliserais juste ..

try:
    float(element)
except ValueError:
    print "Not a float"

.. c'est simple et ça marche

Une autre option serait une expression régulière:

import re
if re.match(r'^-?\d+(?:\.\d+)?$', element) is None:
    print "Not float"
dbr
la source
3
@ S.Lott: La plupart des chaînes auxquelles cela s'applique se révéleront être des entiers ou des flottants.
Chris Upchurch
10
Votre regex n'est pas optimale. "^ \ d + \. \ d + $" échouera une correspondance à la même vitesse que ci-dessus, mais réussira plus rapidement. De plus, une méthode plus correcte serait: "^ [+ -]? \ D (>? \. \ D +)? $" Cependant, cela ne correspond toujours pas à des nombres comme: + 1.0e-10
John Gietzen
86
Sauf que vous avez oublié de nommer votre fonction "will_it_float".
démonté le
3
La deuxième option n'attrapera pas l'expression nan et exponentielle - comme 2e3.
Patrick B.
4
Je pense que l'expression régulière n'analyse pas les nombres négatifs.
Carlos le
191

Méthode Python pour vérifier le flottant:

def isfloat(value):
  try:
    float(value)
    return True
  except ValueError:
    return False

Ne vous laissez pas mordre par les gobelins qui se cachent dans le bateau flottant! FAITES UN TEST D'UNITÉ!

Ce qui est et n'est pas un flotteur peut vous surprendre:

Command to parse                        Is it a float?  Comment
--------------------------------------  --------------- ------------
print(isfloat(""))                      False
print(isfloat("1234567"))               True 
print(isfloat("NaN"))                   True            nan is also float
print(isfloat("NaNananana BATMAN"))     False
print(isfloat("123.456"))               True
print(isfloat("123.E4"))                True
print(isfloat(".1"))                    True
print(isfloat("1,234"))                 False
print(isfloat("NULL"))                  False           case insensitive
print(isfloat(",1"))                    False           
print(isfloat("123.EE4"))               False           
print(isfloat("6.523537535629999e-07")) True
print(isfloat("6e777777"))              True            This is same as Inf
print(isfloat("-iNF"))                  True
print(isfloat("1.797693e+308"))         True
print(isfloat("infinity"))              True
print(isfloat("infinity and BEYOND"))   False
print(isfloat("12.34.56"))              False           Two dots not allowed.
print(isfloat("#56"))                   False
print(isfloat("56%"))                   False
print(isfloat("0E0"))                   True
print(isfloat("x86E0"))                 False
print(isfloat("86-5"))                  False
print(isfloat("True"))                  False           Boolean is not a float.   
print(isfloat(True))                    True            Boolean is a float
print(isfloat("+1e1^5"))                False
print(isfloat("+1e1"))                  True
print(isfloat("+1e1.3"))                False
print(isfloat("+1.3P1"))                False
print(isfloat("-+1"))                   False
print(isfloat("(1)"))                   False           brackets not interpreted
Eric Leschinski
la source
6
Très bonne réponse. Il suffit d'ajouter 2 autres où float = True: isfloat(" 1.23 ")et isfloat(" \n \t 1.23 \n\t\n"). Utile dans les demandes Web; pas besoin de couper d'abord les espaces blancs.
BareNakedCoder
22
'1.43'.replace('.','',1).isdigit()

qui ne reviendra trueque s'il y en a un ou pas de '.' dans la chaîne de chiffres.

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

reviendra false

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

reviendra false

TulasiReddy
la source
3
Pas optimal mais en fait assez intelligent. Ne gère pas +/- et les exposants.
Mad Physicist
Des années de retard, mais c'est une bonne méthode. A travaillé pour moi en utilisant ce qui suit dans une base de données pandas:[i for i in df[i].apply(lambda x: str(x).replace('.','').isdigit()).any()]
Mark Moretto
1
@MarkMoretto Vous allez être sous le choc lorsque vous apprenez l'existence de nombres négatifs
David Heffernan
Meilleur one-liner pour mon scénario, où j'ai juste besoin de vérifier les flottants ou les nombres positifs. J'aime.
MJohnyJ le
8

TL; DR :

  • Si votre entrée est principalement constituée de chaînes qui peuvent être converties en flottants, la try: except:méthode est la meilleure méthode native Python.
  • Si votre entrée est principalement constituée de chaînes qui ne peuvent pas être converties en flottants, les expressions régulières ou la méthode de partition seront meilleures.
  • Si vous n'êtes 1) pas sûr de votre entrée ou si vous avez besoin de plus de vitesse et 2) cela ne vous dérange pas et que vous pouvez installer une extension C tierce, fastnumbers fonctionne très bien.

Il existe une autre méthode disponible via un module tiers appelé fastnumbers (divulgation, je suis l'auteur); il fournit une fonction appelée isfloat . J'ai pris l'exemple le plus simple décrit par Jacob Gabrielson dans cette réponse , mais j'ai ajouté la fastnumbers.isfloatméthode. Je dois également noter que l'exemple de Jacob ne rendait pas justice à l'option regex parce que la plupart du temps dans cet exemple était passé dans des recherches globales à cause de l'opérateur point ... J'ai modifié cette fonction pour donner une comparaison plus juste try: except:.


def is_float_try(str):
    try:
        float(str)
        return True
    except ValueError:
        return False

import re
_float_regexp = re.compile(r"^[-+]?(?:\b[0-9]+(?:\.[0-9]*)?|\.[0-9]+\b)(?:[eE][-+]?[0-9]+\b)?$").match
def is_float_re(str):
    return True if _float_regexp(str) else False

def is_float_partition(element):
    partition=element.partition('.')
    if (partition[0].isdigit() and partition[1]=='.' and partition[2].isdigit()) or (partition[0]=='' and partition[1]=='.' and partition[2].isdigit()) or (partition[0].isdigit() and partition[1]=='.' and partition[2]==''):
        return True
    else:
        return False

from fastnumbers import isfloat


if __name__ == '__main__':
    import unittest
    import timeit

    class ConvertTests(unittest.TestCase):

        def test_re_perf(self):
            print
            print 're sad:', timeit.Timer('ttest.is_float_re("12.2x")', "import ttest").timeit()
            print 're happy:', timeit.Timer('ttest.is_float_re("12.2")', "import ttest").timeit()

        def test_try_perf(self):
            print
            print 'try sad:', timeit.Timer('ttest.is_float_try("12.2x")', "import ttest").timeit()
            print 'try happy:', timeit.Timer('ttest.is_float_try("12.2")', "import ttest").timeit()

        def test_fn_perf(self):
            print
            print 'fn sad:', timeit.Timer('ttest.isfloat("12.2x")', "import ttest").timeit()
            print 'fn happy:', timeit.Timer('ttest.isfloat("12.2")', "import ttest").timeit()


        def test_part_perf(self):
            print
            print 'part sad:', timeit.Timer('ttest.is_float_partition("12.2x")', "import ttest").timeit()
            print 'part happy:', timeit.Timer('ttest.is_float_partition("12.2")', "import ttest").timeit()

    unittest.main()

Sur ma machine, la sortie est:

fn sad: 0.220988988876
fn happy: 0.212214946747
.
part sad: 1.2219619751
part happy: 0.754667043686
.
re sad: 1.50515985489
re happy: 1.01107215881
.
try sad: 2.40243887901
try happy: 0.425730228424
.
----------------------------------------------------------------------
Ran 4 tests in 7.761s

OK

Comme vous pouvez le voir, regex n'est en fait pas aussi mauvais qu'il le paraissait à l'origine, et si vous avez un réel besoin de vitesse, la fastnumbersméthode est plutôt bonne.

SethMMorton
la source
la vérification rapide des nombres fonctionne si bien si vous avez une majorité de chaînes qui ne peuvent pas être converties en flotteurs, accélère vraiment les choses, merci
ragardner
5

Si vous vous souciez des performances (et je ne vous suggère pas de le faire), l'approche basée sur les essais est clairement gagnante (par rapport à votre approche basée sur les partitions ou à l'approche regexp), tant que vous ne vous attendez pas à beaucoup de chaînes non valides, auquel cas il est potentiellement plus lent (probablement en raison du coût de la gestion des exceptions).

Encore une fois, je ne suggère pas que vous vous souciez de la performance, je vous donne simplement les données au cas où vous le feriez 10 milliards de fois par seconde, ou quelque chose du genre. De plus, le code basé sur la partition ne gère pas au moins une chaîne valide.

$ ./floatstr.py
F..
partition triste: 3.1102449894
partition heureuse: 2.09208488464
..
re triste: 7.76906108856
re heureux: 7.09421992302
..
essayez triste: 12.1525540352
essayez heureux: 1.44165301323
.
=================================================== =====================
ÉCHEC: test_partition (__main __. ConvertTests)
-------------------------------------------------- --------------------
Traceback (dernier appel le plus récent):
  Fichier "./floatstr.py", ligne 48, dans test_partition
    self.failUnless (is_float_partition ("20e2"))
AssertionError

-------------------------------------------------- --------------------
Ran 8 tests en 33.670s

FAILED (échecs = 1)

Voici le code (Python 2.6, expression régulière tirée de la réponse de John Gietzen ):

def is_float_try(str):
    try:
        float(str)
        return True
    except ValueError:
        return False

import re
_float_regexp = re.compile(r"^[-+]?(?:\b[0-9]+(?:\.[0-9]*)?|\.[0-9]+\b)(?:[eE][-+]?[0-9]+\b)?$")
def is_float_re(str):
    return re.match(_float_regexp, str)


def is_float_partition(element):
    partition=element.partition('.')
    if (partition[0].isdigit() and partition[1]=='.' and partition[2].isdigit()) or (partition[0]=='' and partition[1]=='.' and pa\
rtition[2].isdigit()) or (partition[0].isdigit() and partition[1]=='.' and partition[2]==''):
        return True

if __name__ == '__main__':
    import unittest
    import timeit

    class ConvertTests(unittest.TestCase):
        def test_re(self):
            self.failUnless(is_float_re("20e2"))

        def test_try(self):
            self.failUnless(is_float_try("20e2"))

        def test_re_perf(self):
            print
            print 're sad:', timeit.Timer('floatstr.is_float_re("12.2x")', "import floatstr").timeit()
            print 're happy:', timeit.Timer('floatstr.is_float_re("12.2")', "import floatstr").timeit()

        def test_try_perf(self):
            print
            print 'try sad:', timeit.Timer('floatstr.is_float_try("12.2x")', "import floatstr").timeit()
            print 'try happy:', timeit.Timer('floatstr.is_float_try("12.2")', "import floatstr").timeit()

        def test_partition_perf(self):
            print
            print 'partition sad:', timeit.Timer('floatstr.is_float_partition("12.2x")', "import floatstr").timeit()
            print 'partition happy:', timeit.Timer('floatstr.is_float_partition("12.2")', "import floatstr").timeit()

        def test_partition(self):
            self.failUnless(is_float_partition("20e2"))

        def test_partition2(self):
            self.failUnless(is_float_partition(".2"))

        def test_partition3(self):
            self.failIf(is_float_partition("1234x.2"))

    unittest.main()
Jacob Gabrielson
la source
4

Juste pour la variété, voici une autre méthode pour le faire.

>>> all([i.isnumeric() for i in '1.2'.split('.',1)])
True
>>> all([i.isnumeric() for i in '2'.split('.',1)])
True
>>> all([i.isnumeric() for i in '2.f'.split('.',1)])
False

Edit: Je suis sûr que cela ne tiendra pas à tous les cas de flottement, surtout quand il y a un exposant. Pour résoudre cela, cela ressemble à ceci. Cela renverra True seulement val est un float et False pour int mais est probablement moins performant que regex.

>>> def isfloat(val):
...     return all([ [any([i.isnumeric(), i in ['.','e']]) for i in val],  len(val.split('.')) == 2] )
...
>>> isfloat('1')
False
>>> isfloat('1.2')
True
>>> isfloat('1.2e3')
True
>>> isfloat('12e3')
False
Peter Moore
la source
La fonction isnumeric semble être un mauvais choix, car elle renvoie true sur divers caractères Unicode comme les fractions. Les documents disent: "Les caractères numériques incluent les caractères numériques et tous les caractères qui ont la propriété de valeur numérique Unicode, par exemple U + 2155, VULGAR FRACTION ONE FIFTH"
gwideman
3

Cette expression régulière vérifiera les nombres à virgule flottante scientifiques:

^[-+]?(?:\b[0-9]+(?:\.[0-9]*)?|\.[0-9]+\b)(?:[eE][-+]?[0-9]+\b)?$

Cependant, je pense que votre meilleur pari est d'utiliser l'analyseur dans un essai.

John Gietzen
la source
2

Si vous n'avez pas à vous soucier d'expressions scientifiques ou autres de nombres et que vous ne travaillez qu'avec des chaînes qui pourraient être des nombres avec ou sans point:

Fonction

def is_float(s):
    result = False
    if s.count(".") == 1:
        if s.replace(".", "").isdigit():
            result = True
    return result

Version Lambda

is_float = lambda x: x.replace('.','',1).isdigit() and "." in x

Exemple

if is_float(some_string):
    some_string = float(some_string)
elif some_string.isdigit():
    some_string = int(some_string)
else:
    print "Does not convert to int or float."

De cette façon, vous ne convertissez pas accidentellement ce qui devrait être un int, en un flottant.

kodetojoy
la source
2

Version simplifiée de la fonction is_digit(str) , qui suffit dans la plupart des cas (ne prend pas en compte la notation exponentielle et la valeur "NaN" ):

def is_digit(str):
    return str.lstrip('-').replace('.', '').isdigit()
simhumileco
la source
1

J'ai utilisé la fonction déjà mentionnée, mais je remarque rapidement que les chaînes comme "Nan", "Inf" et sa variation sont considérées comme des nombres. Je vous propose donc une version améliorée de la fonction, qui renverra false sur ce type d'entrée et n'échouera pas sur les variantes "1e3":

def is_float(text):
    # check for nan/infinity etc.
    if text.isalpha():
        return False
    try:
        float(text)
        return True
    except ValueError:
        return False
mathfac
la source
1
Ne pourrions-nous pas commencer par le if text.isalpha():chèque tout de suite?
Csaba Toth
BTW j'ai besoin de la même chose: je ne veux pas accepter NaN, Inf et autres
Csaba Toth
1

Essayez de vous convertir en flottant. S'il y a une erreur, imprimez l'exception ValueError.

try:
    x = float('1.23')
    print('val=',x)
    y = float('abc')
    print('val=',y)
except ValueError as err:
    print('floatErr;',err)

Production:

val= 1.23
floatErr: could not convert string to float: 'abc'
edW
la source
1

En passant le dictionnaire comme argument, il convertira les chaînes qui peuvent être converties en flottant et en laissera d'autres

def covertDict_float(data):
        for i in data:
            if data[i].split(".")[0].isdigit():
                try:
                    data[i] = float(data[i])
                except:
                    continue
        return data
Rahul Jain
la source
0

Je cherchais un code similaire, mais il semble que l'utilisation de try / excepts soit le meilleur moyen. Voici le code que j'utilise. Il comprend une fonction de nouvelle tentative si l'entrée n'est pas valide. J'avais besoin de vérifier si l'entrée était supérieure à 0 et si oui, de la convertir en flottant.

def cleanInput(question,retry=False): 
    inputValue = input("\n\nOnly positive numbers can be entered, please re-enter the value.\n\n{}".format(question)) if retry else input(question)
    try:
        if float(inputValue) <= 0 : raise ValueError()
        else : return(float(inputValue))
    except ValueError : return(cleanInput(question,retry=True))


willbefloat = cleanInput("Give me the number: ")
Lockey
la source
0
def try_parse_float(item):
  result = None
  try:
    float(item)
  except:
    pass
  else:
    result = float(item)
  return result
Tawanda Matereke
la source
2
Bien que ce code puisse résoudre la question, inclure une explication sur comment et pourquoi cela résout le problème aiderait vraiment à améliorer la qualité de votre message et entraînerait probablement plus de votes à la hausse. N'oubliez pas que vous répondez à la question des lecteurs à l'avenir, pas seulement à la personne qui la pose maintenant. Veuillez modifier votre réponse pour ajouter des explications et donner une indication des limites et des hypothèses applicables.
double bip le
0

J'ai essayé certaines des options simples ci-dessus, en utilisant un test d'essai autour de la conversion en flottant, et j'ai trouvé qu'il y avait un problème dans la plupart des réponses.

Test simple (dans le sens des réponses ci-dessus):

entry = ttk.Entry(self, validate='key')
entry['validatecommand'] = (entry.register(_test_num), '%P')

def _test_num(P):
    try: 
        float(P)
        return True
    except ValueError:
        return False

Le problème survient lorsque:

  • Vous entrez «-» pour commencer un nombre négatif:

Vous essayez alors float('-')ce qui échoue

  • Vous entrez un nombre, mais essayez ensuite de supprimer tous les chiffres

Vous essayez alors float('')ce qui échoue également

La solution rapide que j'avais est:

def _test_num(P):
    if P == '' or P == '-': return True
    try: 
        float(P)
        return True
    except ValueError:
        return False
Richard
la source
-2
str(strval).isdigit()

semble être simple.

Gère les valeurs stockées sous forme de chaîne, d'int ou de flottant

muks
la source
Dans [2]: '123,123'.isdigit () Sortie [2]: False
Daniil Mashkin
1
Cela ne fonctionne pas pour les nombres négatifs littéraux, veuillez corriger votre réponse
RandomEli
'39 .1'.isdigit ()
Ohad the Lad
all ([x.isdigit () for x in str (VAR) .strip ('-'). replace (',', '.'). split ('.')]) Si vous cherchez un la mise en oeuvre.
lotrus28