Sprawdzanie, czy ciąg można przekonwertować na zmiennoprzecinkowe w Pythonie

182

Mam kod Pythona, który przebiega przez listę ciągów znaków i konwertuje je na liczby całkowite lub liczby zmiennoprzecinkowe, jeśli to możliwe. Wykonanie tego dla liczb całkowitych jest dość łatwe

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

Liczby zmiennoprzecinkowe są trudniejsze. W tej chwili używam partition('.')do podzielenia łańcucha i sprawdzania, czy jedna lub obie strony są cyframi.

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)

To działa, ale oczywiście instrukcja if jest trochę niedźwiedzia. Innym rozwiązaniem, które rozważałem, jest po prostu zawinięcie konwersji w blok try / catch i sprawdzenie, czy się powiedzie, jak opisano w tym pytaniu .

Czy ktoś ma jakieś inne pomysły? Opinie na temat względnych zalet partycji i podejść do próbowania / łapania?

Chris Upchurch
źródło

Odpowiedzi:

305

Chciałbym tylko użyć ...

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

.. to proste i działa

Inną opcją byłoby wyrażenie regularne:

import re
if re.match(r'^-?\d+(?:\.\d+)?$', element) is None:
    print "Not float"
dbr
źródło
3
@ S.Lott: Większość ciągów, do których to się zastosuje, okaże się intami lub zmiennoprzecinkowymi.
Chris Upchurch,
10
Twoje wyrażenie regularne nie jest optymalne. „^ \ d + \. \ d + $” nie powiedzie się dopasowanie z taką samą prędkością jak powyżej, ale odniesie sukces szybciej. Bardziej poprawnym sposobem byłoby: „^ [+ -]? \ D (>? \. \ D +)? $” Jednak to wciąż nie pasuje do liczb takich jak: + 1.0e-10
John Gietzen
86
Tyle że zapomniałeś nazwać swoją funkcję „will_it_float”.
odmontowano
3
Druga opcja nie wyłapie wyrażenia nanowego i wykładniczego - takiego jak 2e3.
Patrick B.
4
Myślę, że regex nie analizuje liczb ujemnych.
Carlos
191

Metoda Pythona w celu sprawdzenia liczby zmiennoprzecinkowej:

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

Nie daj się zwieść goblinom ukrywającym się w łodzi pływającej! ZRÓB TESTOWANIE JEDNOSTKI!

Co jest, a nie jest pływakiem, może cię zaskoczyć:

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
źródło
6
Świetna odpowiedź. Wystarczy dodać 2 dodatkowe gdzie float = True: isfloat(" 1.23 ")i isfloat(" \n \t 1.23 \n\t\n"). Przydatny w żądaniach internetowych; nie trzeba najpierw przycinać białych znaków.
BareNakedCoder,
22
'1.43'.replace('.','',1).isdigit()

który zwróci truetylko wtedy, gdy będzie jeden ”lub„ nie ”. w ciągu cyfr.

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

wróci false

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

wróci false

TulasiReddy
źródło
3
Nie optymalne, ale w rzeczywistości całkiem sprytne. Nie obsłuży +/- i wykładników.
Mad Physicist,
Wiele lat spóźnienia, ale to fajna metoda. Pracowałem dla mnie przy użyciu następujących danych w ramce danych pandy:[i for i in df[i].apply(lambda x: str(x).replace('.','').isdigit()).any()]
Mark Moretto
1
@MarkMoretto Będziesz w szoku, gdy dowiesz się o istnieniu liczb ujemnych
David Heffernan
Najlepszy linijka dla mojego scenariusza, w którym muszę po prostu sprawdzić dodatnie zmiennoprzecinkowe lub liczby. Podoba mi się.
MJohnyJ
8

TL; DR :

  • Jeśli dane wejściowe są w większości ciągami, które można przekonwertować na zmiennoprzecinkowe, try: except:metoda jest najlepszą natywną metodą Pythona.
  • Jeśli dane wejściowe są w większości ciągami, których nie można przekonwertować na zmiennoprzecinkowe, lepsze będą wyrażenia regularne lub metoda partycji.
  • Jeśli jesteś 1) niepewny swojego wkładu lub potrzebujesz większej prędkości i 2) nie przejmuj się i możesz zainstalować rozszerzenie C innej firmy, numery szybkiego łączenia działają bardzo dobrze.

Istnieje inna metoda dostępna za pośrednictwem modułu innej firmy o nazwie fastnumbers (ujawnienie, jestem autorem); zapewnia funkcję o nazwie isfloat . Wziąłem najdelikatniejszy przykład przedstawiony przez Jacoba Gabrielsona w tej odpowiedzi , ale dodałem tę fastnumbers.isfloatmetodę. Powinienem również zauważyć, że przykład Jacoba nie oddał sprawiedliwie opcji regex, ponieważ większość czasu w tym przykładzie spędzono na przeglądach globalnych z powodu operatora kropki ... Zmodyfikowałem tę funkcję, aby uzyskać bardziej sprawiedliwe porównanie 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()

Na mojej maszynie dane wyjściowe to:

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

Jak widać, wyrażenie regularne nie jest tak złe, jak się początkowo wydawało, a jeśli naprawdę potrzebujesz szybkości, fastnumbersmetoda jest całkiem dobra.

SethMMorton
źródło
szybkie sprawdzanie liczb działa tak dobrze, jeśli masz większość ciągów, których nie można przekonwertować na
liczby zmiennoprzecinkowe
5

Jeśli zależy Ci na wydajności (i nie sugeruję, że powinieneś), podejście oparte na próbach jest wyraźnym zwycięzcą (w porównaniu z podejściem opartym na partycjach lub wyrażeniem regularnym), o ile nie spodziewasz się dużo niepoprawne ciągi, w którym to przypadku jest to potencjalnie wolniejsze (prawdopodobnie ze względu na koszt obsługi wyjątków).

Ponownie, nie sugeruję, abyś dbał o wydajność, po prostu podałbym dane na wypadek, gdybyś robił to 10 miliardów razy na sekundę, czy coś takiego. Ponadto kod oparty na partycjach nie obsługuje co najmniej jednego prawidłowego ciągu.

$ ./floatstr.py
FA..
smutna partycja: 3.1102449894
partycja szczęśliwa: 2.09208488464
..
re sad: 7.76906108856
ponownie szczęśliwy: 7.09421992302
..
spróbuj smutny: 12.1525540352
spróbuj szczęśliwy: 1.44165301323
.
================================================== ====================
FAIL: test_partition (__main __. ConvertTests)
-------------------------------------------------- --------------------
Traceback (ostatnie połączenie ostatnio):
  Plik „./floatstr.py”, wiersz 48, w partycji testowej
    self.failUnless (is_float_partition („20e2”))
AssertionError

-------------------------------------------------- --------------------
Przebiegł 8 testów w 33,670s

FAILED (awarie = 1)

Oto kod (Python 2.6, REGEXP zaczerpnięte z Johna Gietzen za odpowiedź ):

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
źródło
4

Tylko dla odmiany tutaj jest inna metoda, aby to zrobić.

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

Edycja: Jestem pewien, że nie wytrzyma wszystkich przypadków liczby zmiennoprzecinkowej, zwłaszcza gdy występuje wykładnik potęgi. Aby rozwiązać, wygląda to tak. Zwróci to wartość True tylko val jest liczbą zmiennoprzecinkową i False dla int, ale prawdopodobnie jest mniej wydajna niż 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
źródło
Funkcja izumeryczna wygląda na kiepski wybór, ponieważ zwraca wartość true dla różnych znaków Unicode, takich jak ułamki. Dokumenty mówią: „Znaki numeryczne obejmują znaki cyfrowe i wszystkie znaki, które mają właściwość wartości liczbowej Unicode, np. U + 2155, VULGAR FRACTION ONE FIFTH”
gwideman
3

Ta regex sprawdzi, czy istnieją naukowe liczby zmiennoprzecinkowe:

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

Uważam jednak, że najlepszym rozwiązaniem jest użycie parsera podczas próby.

John Gietzen
źródło
2

Jeśli nie musisz się martwić naukowymi lub innymi wyrażeniami liczb i pracujesz tylko z ciągami, które mogą być liczbami z kropką lub bez:

Funkcjonować

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

Wersja Lambda

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

Przykład

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."

W ten sposób nie zamieniasz przypadkowo inta w zmiennoprzecinkowe.

kodetojoy
źródło
2

Uproszczona wersja funkcji is_digit(str) , która wystarcza w większości przypadków (nie uwzględnia notacji wykładniczej i wartości „NaN” ):

def is_digit(str):
    return str.lstrip('-').replace('.', '').isdigit()
simhumileco
źródło
1

Użyłem już wspomnianej funkcji, ale wkrótce zauważam, że ciągi znaków jako „Nan”, „Inf” i jej odmiana są uważane za liczbę. Proponuję więc ulepszoną wersję funkcji, która zwróci false dla tego typu danych wejściowych i nie zawiedzie wariantów „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
źródło
1
Czy nie moglibyśmy zacząć od if text.isalpha():czeku od razu?
Csaba Toth,
BTW Potrzebuję tego samego: nie chcę akceptować NaN, Inf i innych rzeczy
Csaba Toth
1

Spróbuj przekonwertować na float. Jeśli wystąpi błąd, wydrukuj wyjątek ValueError.

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

Wynik:

val= 1.23
floatErr: could not convert string to float: 'abc'
edW
źródło
1

Przekazanie słownika jako argumentu spowoduje konwersję ciągów, które można przekonwertować na zmiennoprzecinkowe i pozostawi inne

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
źródło
0

Szukałem podobnego kodu, ale wygląda na to, że najlepszym rozwiązaniem jest użycie try / wyjątki. Oto kod, którego używam. Zawiera funkcję ponownej próby, jeśli dane wejściowe są nieprawidłowe. Musiałem sprawdzić, czy dane wejściowe były większe niż 0, a jeśli tak, przekonwertuj je na zmiennoprzecinkowe.

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
źródło
0
def try_parse_float(item):
  result = None
  try:
    float(item)
  except:
    pass
  else:
    result = float(item)
  return result
Tawanda Matereke
źródło
2
Chociaż ten kod może rozwiązać pytanie, w tym wyjaśnienie, w jaki sposób i dlaczego to rozwiązuje problem, naprawdę pomógłby poprawić jakość twojego postu i prawdopodobnie doprowadziłby do większej liczby głosów. Pamiętaj, że odpowiadasz na pytanie dla czytelników w przyszłości, nie tylko dla osoby zadającej teraz pytanie. Proszę edytować swoje odpowiedzi, aby dodać wyjaśnień i dać wskazówkę co zastosować ograniczenia i założenia.
podwójny sygnał dźwiękowy
0

Wypróbowałem niektóre z powyższych prostych opcji, używając testu próbnego konwersji na zmiennoprzecinkowe, i odkryłem, że w większości odpowiedzi występuje problem.

Prosty test (zgodnie z powyższymi odpowiedziami):

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

Problem pojawia się, gdy:

  • Wpisujesz „-”, aby rozpocząć liczbę ujemną:

Próbujesz wtedy, float('-')co się nie udaje

  • Wpisujesz numer, ale następnie próbujesz usunąć wszystkie cyfry

Następnie próbujesz, float('')który również zawiedzie

Moje szybkie rozwiązanie to:

def _test_num(P):
    if P == '' or P == '-': return True
    try: 
        float(P)
        return True
    except ValueError:
        return False
Richard
źródło
-2
str(strval).isdigit()

wydaje się być proste.

Obsługuje wartości przechowywane jako ciąg lub int lub zmiennoprzecinkowe

Muks
źródło
W [2]: „123 123” .isdigit () Out [2]: False
Daniil
1
To nie działa w przypadku literałów liczb ujemnych, popraw swoją odpowiedź
RandomEli,
'39 .1'.isdigit ()
Ohad the Lad
all ([x.isdigit () dla x w str (VAR) .strip ('-'). replace (',', '.'). split ('.')]) Jeśli szukasz bardziej kompletnego realizacja.
lotrus28