Jak sprawdzić, czy ciąg znaków jest liczbą (liczbą zmiennoprzecinkową)?


1606

Jaki jest najlepszy możliwy sposób sprawdzenia, czy ciąg może być reprezentowany jako liczba w Pythonie?

Obecnie posiadam funkcję:

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

Co nie tylko jest brzydkie i powolne, wydaje się niezgrabne. Nie znalazłem jednak lepszej metody, ponieważ wywołanie floatfunkcji głównej jest jeszcze gorsze.


61
Co jest nie tak z twoim obecnym rozwiązaniem? Jest krótki, szybki i czytelny.
Pułkownik Panic

5
I nie musisz po prostu zwracać prawdy czy fałszu. Zamiast tego możesz zwrócić odpowiednio zmodyfikowaną wartość - na przykład możesz użyć tej wartości do wpisania liczb nieparzystych w cudzysłowie.
Thruston

7
Czy nie lepiej byłoby zwrócić wynik liczb zmiennoprzecinkowych w przypadku pomyślnej konwersji? Nadal masz czek na sukces (wynik to Fałsz) i faktycznie masz konwersję, której prawdopodobnie i tak chcesz.
Jiminion,

8
Chociaż to pytanie jest starsze, chciałem tylko powiedzieć, że jest to elegancki sposób udokumentowany jako EAFP . Prawdopodobnie najlepsze rozwiązanie tego rodzaju problemu.
thiruvenkadam

7
Nie zwracaj wyniku float (s) lub None w przypadku niepowodzenia. jeśli użyjesz go, ponieważ x = float('0.00'); if x: use_float(x);masz teraz błąd w kodzie. Prawdziwe wartości są powodem, dla którego funkcje te zgłaszają wyjątek, a nie zwracają go Nonew pierwszej kolejności. Lepszym rozwiązaniem jest po prostu uniknięcie funkcji narzędziowej i otoczenie połączenia, aby unosić się w try catchmomencie, gdy chcesz go użyć.
ovangle

Odpowiedzi:


698

Co nie tylko jest brzydkie i wolne

Spierałbym się o jedno i drugie.

Wyrażenie regularne lub inna metoda parsowania łańcuchów byłaby brzydsza i wolniejsza.

Nie jestem pewien, czy cokolwiek może być szybsze niż powyższe. Wywołuje funkcję i zwraca. Try / Catch nie wprowadza dużego obciążenia, ponieważ najczęstszy wyjątek jest wychwytywany bez obszernego wyszukiwania ramek stosu.

Problem polega na tym, że każda funkcja konwersji numerycznej ma dwa rodzaje wyników

  • Liczba, jeśli numer jest prawidłowy
  • Kod stanu (np. Przez errno) lub wyjątek pokazujący, że nie można przeanalizować żadnego poprawnego numeru.

C (na przykład) włamuje się do tego na wiele sposobów. Python określa to jasno i wyraźnie.

Myślę, że twój kod do tego jest idealny.


21
Nie sądzę, aby kod był doskonały (ale myślę, że jest bardzo blisko): zwykle klauzulę umieszcza się tylko w części, która jest „testowana” try, więc umieszczam ją return Truew elseklauzuli try. Jednym z powodów jest to, że z kodem w pytaniu, gdybym musiał go przejrzeć, musiałbym sprawdzić, czy drugie stwierdzenie w tryklauzuli nie może wywołać błędu ValueError: oczywiście, nie wymaga to zbyt wiele czasu ani siły mózgu, ale po co używać, gdy nie jest potrzebny?
Eric O Lebigot,

4
Odpowiedź wydaje się przekonująca, ale zastanawiam się, dlaczego nie została dostarczona po wyjęciu z pudełka ... Skopiuję ją i wykorzystam w każdym razie.
mędrzec

9
Jak okropnie. Jak o tym, czy ja nie dbam o to, co liczba jest po prostu, że jest to numer (czyli to, co przyniósł mi tutaj)? Zamiast 1-liniowego IsNumeric()albo kończę na try / catch lub innym zawijaniu try / catch. Ugh
Podstawowy

6
Nie jest dostarczany „po wyjęciu z pudełka”, ponieważ if is_number(s): x = float(x) else: // failma taką samą liczbę wierszy kodu jak try: x = float(x) catch TypeError: # fail. Ta funkcja użyteczności jest całkowicie niepotrzebną abstrakcją.
ovangle

12
Ale abstrakcja to sedno bibliotek. Posiadanie funkcji „isNumber” (w dowolnym języku) bardzo pomaga, ponieważ można ją wbudować bezpośrednio w instrukcje if i mieć o wiele bardziej czytelny i łatwy do utrzymania kod, który opiera się na blokach try-catch. Ponadto, jeśli musisz użyć kodu więcej niż jeden raz w więcej niż jednej klasie / module, użyłeś więcej wierszy kodu niż miałaby wbudowana funkcja.
JamEngulfer,

1612

Jeśli szukasz liczb całkowitych (dodatnich, niepodpisanych) zamiast liczb zmiennoprzecinkowych, możesz użyć isdigit()funkcji dla obiektów łańcuchowych.

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

Metody łańcuchowe - isdigit(): Python2 , Python3

Jest też coś w ciągach znaków Unicode, których nie znam zbytnio w Unicode - jest dziesiętny / dziesiętny


232
Jest to również negatywne dla negatywów
intrepion

22
Nie działa także z wykładniczym: '1e3'.isdigit () -> False
ssc

35
Podczas gdy Number! = Digit, ludzie, którzy szukają sposobów na sprawdzenie, czy łańcuch zawiera liczbę całkowitą, mogą bardzo natknąć się na to pytanie, a podejście isDigit może bardzo dobrze nadawać się do ich zastosowania.
Adam Parkin

8
@AdamParkin: isdigit()i int()mają różne opinie na temat liczby całkowitej, np. Dla znaku Unicode u'\u00b9': u'¹'.isdigit()jest, Trueale int(u'¹')podnosi ValueError.
jfs

6
+1: isdigit () może nie być tym, czego szukał OP, ale właśnie tego chciałem. Może nie być tak, że ta odpowiedź i metoda nie obejmuje wszystkich typów liczb, ale nadal jest bardzo istotna, w przeciwieństwie do argumentów dotyczących jej dokładności. Podczas gdy „Number! = Digit”, cyfra jest nadal podzbiorem liczb, szczególnie liczb, które są dodatnie, nieujemne i używają podstawy 1-10. Co więcej, ta metoda jest szczególnie przydatna i krótka w przypadkach, w których chcesz sprawdzić, czy ciąg znaków jest identyfikatorem numerycznym, czy nie, co często mieści się w podzbiorze liczb, które właśnie opisałem.
Justin Johnson

159

TL; DR Najlepszym rozwiązaniem jests.replace('.','',1).isdigit()

Zrobiłem kilka benchmarków porównując różne podejścia

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

Jeśli ciąg nie jest liczbą, blok wyjątków jest dość wolny. Ale co ważniejsze, metoda try-wyjątkiem jest jedynym podejściem, które poprawnie obsługuje notacje naukowe.

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

Notacja zmiennoprzecinkowa „.1234” nie jest obsługiwana przez:
- 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__)

Notacja naukowa „1.000000e + 50” nie jest obsługiwana przez:
- is_number_regex
- is_number_repl_isdigit
Notacja naukowa „1e50” nie jest obsługiwana przez:
- is_number_regex
- is_number_repl_isdigit

EDYCJA: Wyniki testu porównawczego

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

gdzie przetestowano następujące funkcje

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

wprowadź opis zdjęcia tutaj


14
za ładne wykresy +1. Widziałem benchmark i widziałem wykres, wszystko TL; DR stało się jasne i intuicyjne.
jcchuks,

Zgadzam się z @JCChuks: wykres bardzo pomaga szybko uzyskać wszystkie TL; DR. Ale myślę, że TL; DR (jak: TL; DR : najlepsze rozwiązanie s.replace('.','',1).isdigit()) powinno pojawić się na początku tej odpowiedzi. W każdym razie powinna być zaakceptowana. Dzięki!
Simon C.

10
Ta metoda nie obsługuje liczb ujemnych (myślników). Radziłbym po prostu użyć metody float, ponieważ jest ona mniej podatna na błędy i będzie działać za każdym razem.
Urchin

3
Co ważne, należy zauważyć, że nawet przy założeniu, że nie może być myślnika, metoda replace-isdigit jest szybsza tylko dla liczb nieparzystych (wynik fałszywy), podczas gdy metoda try-wyjątkiem jest szybsza dla liczb (wynik prawdziwy). Jeśli większość danych wejściowych jest poprawna, lepiej zastosować rozwiązanie try-oprócz!
Markus von Broady,

1
Nie działa na notacji wykładniczej takiej jak '1.5e-9'lub na negatywy.
EL_DON

68

Jest jeden wyjątek, który możesz wziąć pod uwagę: ciąg „NaN”

Jeśli chcesz, aby is_number zwrócił FALSE dla „NaN”, ten kod nie będzie działał, ponieważ Python konwertuje go na reprezentację liczby, która nie jest liczbą (mów o problemach z tożsamością):

>>> float('NaN')
nan

W przeciwnym razie powinienem naprawdę podziękować za fragment kodu, którego teraz intensywnie używam. :)

SOL.


2
W rzeczywistości NaNmoże być dobrą wartością do zwrócenia (zamiast False), jeśli przekazany tekst nie jest w rzeczywistości reprezentacją liczby. Sprawdzanie go jest rodzajem bólu ( floattyp Pythona naprawdę potrzebuje do tego metody), ale można go używać w obliczeniach bez generowania błędu i wystarczy sprawdzić wynik.
uprzejmie

7
Kolejnym wyjątkiem jest ciąg znaków 'inf'. Albo infalbo NaNmoże być poprzedzony znakiem +lub -i nadal może być zaakceptowany.
AGF

4
Jeśli chcesz zwrócić False dla NaN i Inf, zmień wiersz na x = float (s); return (x == x) i (x - 1! = x). Powinno to zwrócić wartość True dla wszystkich pływaków oprócz Inf i NaN
RyanN

5
x-1 == xjest prawdziwe dla dużych pływaków mniejszych niż inf. W Pythonie 3.2 możesz używać math.isfinitedo testowania liczb, które nie są ani NaN, ani nieskończone, lub sprawdzać oba math.isnani math.isinfprzed tym.
Steve Jessop

56

co powiesz na to:

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

która zwróci wartość prawdy tylko wtedy, gdy jest jedna lub nie ma jej. ” w ciągu cyfr.

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

zwróci fałsz

edycja: właśnie zobaczyłem kolejny komentarz ... można dodać .replace(badstuff,'',maxnum_badstuff)dla innych przypadków. jeśli podajesz sól, a nie arbitralne przyprawy (ref: xkcd # 974 ), to zrobi dobrze: P


7
Nie uwzględnia to jednak liczb ujemnych.
Michael Barton

5
Lub liczby z wykładnikami takimi jak 1.234e56(które mogą być również zapisane jako +1.234E+56i kilka innych wariantów).
Alfe

re.match(r'^[+-]*(0[xbo])?[0-9A-Fa-f]*\.?[0-9A-Fa-f]*(E[+-]*[0-9A-Fa-f]+)$', 'str')powinien lepiej wykonać ustalenie liczby (ale nie wszystkie, nie twierdzę, że). Nie polecam korzystania z tego, zdecydowanie lepiej jest użyć oryginalnego kodu Pytającego.
Baldrickk

jeśli nie podoba ci się to rozwiązanie, przeczytaj to przed głosowaniem!
aloisdg przechodzi na codidact.com

człowieku, to najmądrzejsze rozwiązanie, jakie kiedykolwiek widziałem na tej stronie!
Karam Qusai,

41

Co nie tylko jest brzydkie i powolne, wydaje się niezgrabne.

Może to trochę przyzwyczaić się, ale jest to pythonowy sposób. Jak już wspomniano, alternatywy są gorsze. Ale jest jeszcze jedna zaleta robienia rzeczy w ten sposób: polimorfizm.

Główną ideą pisania kaczek jest to, że „jeśli chodzi i mówi jak kaczka, to jest to kaczka”. Co jeśli zdecydujesz, że musisz podklasować ciąg znaków, abyś mógł zmienić sposób określania, czy coś można przekształcić w liczbę zmiennoprzecinkową? A co, jeśli zdecydujesz się całkowicie przetestować jakiś inny obiekt? Możesz robić te rzeczy bez konieczności zmiany powyższego kodu.

Inne języki rozwiązują te problemy za pomocą interfejsów. Zapiszę analizę, które rozwiązanie jest lepsze dla innego wątku. Chodzi o to, że Python jest zdecydowanie po stronie pisania kaczki równania i prawdopodobnie będziesz musiał przyzwyczaić się do takiej składni, jeśli planujesz dużo programowania w Pythonie (ale to nie znaczy musisz to oczywiście polubić).

Jeszcze jedna rzecz, którą warto wziąć pod uwagę: Python jest dość szybki w rzucaniu i wychwytywaniu wyjątków w porównaniu do wielu innych języków (na przykład 30 razy szybszy niż .Net). Do licha, sam język generuje nawet wyjątki w komunikowaniu nietypowych, normalnych warunków programu (za każdym razem, gdy używasz pętli for). Dlatego nie martwiłbym się zbytnio aspektami wydajności tego kodu, dopóki nie zauważysz znaczącego problemu.


1
Innym częstym miejscem, w którym Python używa wyjątków dla podstawowych funkcji, jest po hasattr()prostu getattr()wywołanie zawarte w try/except. Mimo to obsługa wyjątków jest wolniejsza niż normalna kontrola przepływu, więc użycie jej do czegoś, co będzie prawdą przez większość czasu, może skutkować obniżeniem wydajności.
uprzejmie

Wygląda na to, że jeśli chcesz mieć jedną linijkę, jesteś SOL
Basic

Pythoniczny jest również pomysł, że „lepiej prosić o wybaczenie niż o pozwolenie”, jeśli chodzi o wpływ posiadania tanich wyjątków.
heltonbiker 26.04.16

40

Zaktualizowano po tym, jak Alfe wskazał, że nie trzeba sprawdzać pływaka osobno, ponieważ złożone obsługuje oba:

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

    return True

Wcześniej powiedziane: Czy w niektórych rzadkich przypadkach może być konieczne sprawdzenie liczb zespolonych (np. 1 + 2i), które nie mogą być reprezentowane przez liczbę zmiennoprzecinkową:

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

14
Nie zgadzam się. Jest to BARDZO mało prawdopodobne w normalnym użyciu i lepiej byłoby budować wywołanie is_complex_number (), gdy się ich używa, zamiast obciążać połączenie dodatkową operacją, aby uzyskać 0,0001% szansy na błędne działanie.
Jiminion,

3
Możesz float()całkowicie rozebrać materiał i po prostu sprawdzić, czy complex()połączenie zakończy się powodzeniem. Wszystko analizowane przez float()może być analizowane przez complex().
Alfe

Ta funkcja zwraca wartości NaN i Inf Pandasa jako wartości liczbowe.
fixxxer

complex('(01989)')wróci (1989+0j). Ale float('(01989)')zawiedzie. Myślę więc, że używanie complexnie jest dobrym pomysłem.
plhn

26

Aby intużyć tego:

>>> "1221323".isdigit()
True

Ale floatpotrzebujemy kilku sztuczek ;-). Każda liczba zmiennoprzecinkowa ma jeden punkt ...

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

Również dla liczb ujemnych po prostu dodaj lstrip():

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

A teraz mamy uniwersalny sposób:

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

2
Nie obsługuje rzeczy podobnych 1.234e56i podobnych. Byłbym również zainteresowany tym, jak dowiesz się, że 99999999999999999999e99999999999999999999to nie jest liczba. Próbowanie parsowania szybko się sprawdza.
Alfe

Działa to ~ 30% szybciej niż zaakceptowane rozwiązanie na liście 50-strunowych i 150% szybciej na liście 5-strunowych. 👏
Zev Averbach

15

Just Mimic C #

W języku C # istnieją dwie różne funkcje, które obsługują parsowanie wartości skalarnych:

  • Float.Parse ()
  • Float.TryParse ()

float.parse ():

def parse(string):
    try:
        return float(string)
    except Exception:
        throw TypeError

Uwaga: jeśli zastanawiasz się, dlaczego zmieniłem wyjątek na TypeError, oto dokumentacja .

float.try_parse ():

def try_parse(string, fail=None):
    try:
        return float(string)
    except Exception:
        return fail;

Uwaga: Nie chcesz zwracać wartości logicznej „False”, ponieważ nadal jest to typ wartości. Żadne nie jest lepsze, ponieważ wskazuje na niepowodzenie. Oczywiście, jeśli chcesz czegoś innego, możesz zmienić parametr fail na dowolny.

Aby rozszerzyć float o „parse ()” i „try_parse ()”, musisz dodać małpy do klasy „float”, aby dodać te metody.

Jeśli chcesz szanować istniejące wcześniej funkcje, kod powinien wyglądać następująco:

def monkey_patch():
    if(!hasattr(float, 'parse')):
        float.parse = parse
    if(!hasattr(float, 'try_parse')):
        float.try_parse = try_parse

SideNote: Osobiście wolę nazywać to Monkey Punching, ponieważ wydaje mi się, że nadużywam języka, gdy to robię, ale YMMV.

Stosowanie:

float.parse('giggity') // throws TypeException
float.parse('54.3') // returns the scalar value 54.3
float.tryParse('twank') // returns None
float.tryParse('32.2') // returns the scalar value 32.2

A wielki Mędrzec Python powiedział do Stolicy Apostolskiej Sharpisus: „Wszystko, co możesz zrobić, mogę zrobić lepiej; Mogę zrobić wszystko lepiej niż ty”.


Ostatnio piszę głównie w JS i nie testowałem tego, więc mogą występować drobne błędy. Jeśli widzisz, popraw swoje błędy.
Evan Plaice,

Aby dodać obsługę liczb zespolonych, zapoznaj się z odpowiedzią @Matthew Wilcoxson. stackoverflow.com/a/3335060/290340 .
Evan Plaice,

1
Używanie !zamiast notmoże być drobnym błędem, ale zdecydowanie nie można przypisać atrybutów do wbudowanego floatw CPython.
BlackJack

15

W przypadku ciągów nieliczbowych try: except:jest faktycznie wolniejszy niż wyrażenia regularne. W przypadku ciągów prawidłowych liczb wyrażenie regularne jest wolniejsze. Tak więc odpowiednia metoda zależy od danych wejściowych.

Jeśli okaże się, że jesteś w powiązaniu z wydajnością, możesz użyć nowego modułu innej firmy o nazwie fastnumbers, który udostępnia funkcję o nazwie isfloat . Pełne ujawnienie, jestem autorem. Zawarłem jego wyniki w poniższych harmonogramach.


from __future__ import print_function
import timeit

prep_base = '''\
x = 'invalid'
y = '5402'
z = '4.754e3'
'''

prep_try_method = '''\
def is_number_try(val):
    try:
        float(val)
        return True
    except ValueError:
        return False

'''

prep_re_method = '''\
import re
float_match = re.compile(r'[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?$').match
def is_number_re(val):
    return bool(float_match(val))

'''

fn_method = '''\
from fastnumbers import isfloat

'''

print('Try with non-number strings', timeit.timeit('is_number_try(x)',
    prep_base + prep_try_method), 'seconds')
print('Try with integer strings', timeit.timeit('is_number_try(y)',
    prep_base + prep_try_method), 'seconds')
print('Try with float strings', timeit.timeit('is_number_try(z)',
    prep_base + prep_try_method), 'seconds')
print()
print('Regex with non-number strings', timeit.timeit('is_number_re(x)',
    prep_base + prep_re_method), 'seconds')
print('Regex with integer strings', timeit.timeit('is_number_re(y)',
    prep_base + prep_re_method), 'seconds')
print('Regex with float strings', timeit.timeit('is_number_re(z)',
    prep_base + prep_re_method), 'seconds')
print()
print('fastnumbers with non-number strings', timeit.timeit('isfloat(x)',
    prep_base + 'from fastnumbers import isfloat'), 'seconds')
print('fastnumbers with integer strings', timeit.timeit('isfloat(y)',
    prep_base + 'from fastnumbers import isfloat'), 'seconds')
print('fastnumbers with float strings', timeit.timeit('isfloat(z)',
    prep_base + 'from fastnumbers import isfloat'), 'seconds')
print()

Try with non-number strings 2.39108395576 seconds
Try with integer strings 0.375686168671 seconds
Try with float strings 0.369210958481 seconds

Regex with non-number strings 0.748660802841 seconds
Regex with integer strings 1.02021503448 seconds
Regex with float strings 1.08564686775 seconds

fastnumbers with non-number strings 0.174362897873 seconds
fastnumbers with integer strings 0.179651021957 seconds
fastnumbers with float strings 0.20222902298 seconds

Jak widzisz

  • try: except: był szybki dla wprowadzania numerycznego, ale bardzo wolny dla nieprawidłowego wprowadzania
  • Wyrażenie regularne jest bardzo wydajne, gdy dane wejściowe są nieprawidłowe
  • fastnumbers wygrywa w obu przypadkach

Poprawiłem się: -} po prostu nie wyglądało na to, że to robi. Może używanie nazw takich jak prep_code_basisi prep_code_re_methodzapobiegłoby mojej pomyłce.
Alfe

Czy masz coś przeciwko wyjaśnieniu, jak działa twój moduł, przynajmniej dla isfloatfunkcji?
Solomon Ucko

@SolomonUcko Tu jest link do kodu źródłowego dla części kontroli ciąg: github.com/SethMMorton/fastnumbers/blob/v1.0.0/src/... . Zasadniczo przechodzi w kolejności po każdym znaku w łańcuchu i sprawdza, czy podąża za wzorem prawidłowego zmiennoprzecinkowego. Jeśli dane wejściowe są już liczbą, po prostu używa szybkiego PyFloat_Check .
SethMMorton

1
Przetestowane na podstawie najlepszych alternatyw w tym wątku. Potwierdzam, że to rozwiązanie jest zdecydowanie najszybsze. Druga najszybsza metoda str(s).strip('-').replace('.','',1).isdigit()jest około 10-krotnie wolniejsza!
Alexander McFarlane

14

Wiem, że jest to szczególnie stare, ale dodam odpowiedź, która moim zdaniem obejmuje informacje brakujące w głosowaniu, które uzyskały najwyższą ocenę, które mogą być bardzo cenne dla każdego, kto to znajdzie:

Dla każdej z poniższych metod połącz je z liczbą, jeśli potrzebujesz jakichkolwiek danych wejściowych do zaakceptowania. (Zakładając, że używamy wokalnych definicji liczb całkowitych zamiast 0–255 itd.)

x.isdigit() działa dobrze do sprawdzania, czy x jest liczbą całkowitą.

x.replace('-','').isdigit() działa dobrze do sprawdzania, czy x jest ujemne. (Sprawdź - w pierwszej pozycji)

x.replace('.','').isdigit() działa dobrze do sprawdzania, czy x jest liczbą dziesiętną.

x.replace(':','').isdigit() działa dobrze do sprawdzania, czy x jest stosunkiem.

x.replace('/','',1).isdigit() działa dobrze do sprawdzania, czy x jest ułamkiem.


1
Chociaż w przypadku ułamków prawdopodobnie trzeba to zrobić, w x.replace('/','',1).isdigit()przeciwnym razie daty takie jak 07.04.2017 byłyby błędnie interpretowane jako liczby.
Yuxuan Chen,

Najlepsze sposoby na połączenie warunków: stackoverflow.com/q/3411771/5922329
Daniel Braun

13

Ta odpowiedź zawiera przewodnik krok po kroku mający funkcję z przykładami do znalezienia łańcucha:

  • Dodatnia liczba całkowita
  • Pozytywna / negatywna - liczba całkowita / liczba zmiennoprzecinkowa
  • Jak odrzucić ciągi „NaN” (nie liczbę) podczas sprawdzania liczby?

Sprawdź, czy ciąg jest dodatnią liczbą całkowitą

Możesz użyć, str.isdigit()aby sprawdzić, czy podany ciąg jest dodatnią liczbą całkowitą.

Przykładowe wyniki:

# For digit
>>> '1'.isdigit()
True
>>> '1'.isalpha()
False

Sprawdź, czy ciąg jest dodatni / ujemny - liczba całkowita / liczba zmiennoprzecinkowa

str.isdigit()zwraca, Falsejeśli ciąg jest liczbą ujemną lub liczbą zmiennoprzecinkową. Na przykład:

# returns `False` for float
>>> '123.3'.isdigit()
False
# returns `False` for negative number
>>> '-123'.isdigit()
False

Jeśli chcesz również sprawdzić ujemne liczby całkowitefloat , a następnie możesz napisać niestandardową funkcję, aby to sprawdzić jako:

def is_number(n):
    try:
        float(n)   # Type-casting the string to `float`.
                   # If string is not a valid `float`, 
                   # it'll raise `ValueError` exception
    except ValueError:
        return False
    return True

Przykładowy przebieg:

>>> is_number('123')    # positive integer number
True

>>> is_number('123.4')  # positive float number
True

>>> is_number('-123')   # negative integer number
True

>>> is_number('-123.4') # negative `float` number
True

>>> is_number('abc')    # `False` for "some random" string
False

Odrzuć ciągi „NaN” (nie liczbę) podczas sprawdzania liczby

Powyższe funkcje zwrócą Trueciąg „NAN” (nie liczba), ponieważ dla Pythona jest to poprawna liczba zmiennoprzecinkowa, która nie jest liczbą. Na przykład:

>>> is_number('NaN')
True

Aby sprawdzić, czy liczba to „NaN”, możesz użyć math.isnan()jako:

>>> import math
>>> nan_num = float('nan')

>>> math.isnan(nan_num)
True

Lub jeśli nie chcesz importować dodatkowej biblioteki, aby to sprawdzić, możesz po prostu to sprawdzić, porównując go z samym sobą ==. Python zwraca, Falsegdy liczba nanzmiennoprzecinkowa jest porównywana z samym sobą. Na przykład:

# `nan_num` variable is taken from above example
>>> nan_num == nan_num
False

Stąd powyżej funkcja is_numbermoże być aktualizowana, aby powrócić Falsedo"NaN" jako:

def is_number(n):
    is_number = True
    try:
        num = float(n)
        # check for "nan" floats
        is_number = num == num   # or use `math.isnan(num)`
    except ValueError:
        is_number = False
    return is_number

Przykładowy przebieg:

>>> is_number('Nan')   # not a number "Nan" string
False

>>> is_number('nan')   # not a number string "nan" with all lower cased
False

>>> is_number('123')   # positive integer
True

>>> is_number('-123')  # negative integer
True

>>> is_number('-1.12') # negative `float`
True

>>> is_number('abc')   # "some random" string
False

PS: Każda operacja dla każdej kontroli w zależności od rodzaju numeru wiąże się z dodatkowym kosztem. Wybierz wersję is_numberfunkcji, która odpowiada Twoim wymaganiom.


12

Rzutowanie do float i przechwytywanie ValueError jest prawdopodobnie najszybszym sposobem, ponieważ float () jest specjalnie do tego przeznaczony. Wszystko inne, które wymaga analizowania ciągów (wyrażenia regularne itp.), Będzie prawdopodobnie wolniejsze, ponieważ nie jest dostrojone do tej operacji. Moje 0,02 $.


11
Twoje „2e-2” dolary też są zmiennoprzecinkowe (dodatkowy argument za używaniem zmiennoprzecinkowego :)
tzot

8
@tzot NIGDY nie należy używać pływaka do reprezentowania wartości pieniężnej.
Łukasz

6
@Luke: Całkowicie się z tobą zgadzam, chociaż nigdy nie sugerowałem używania pływaków do reprezentowania wartości pieniężnych; Powiedziałem tylko, że wartości pieniężne można przedstawić jako
zmiennoprzecinkowe

11

Możesz używać ciągów Unicode, mają metodę robienia tego, co chcesz:

>>> s = u"345"
>>> s.isnumeric()
True

Lub:

>>> s = "345"
>>> u = unicode(s)
>>> u.isnumeric()
True

http://www.tutorialspoint.com/python/string_isnumeric.htm

http://docs.python.org/2/howto/unicode.html


2
dla wartości nieujemnych jest w porządku ;-)
andilabs

1
s.isdecimal()sprawdza, czy słańcuch jest liczbą całkowitą nieujemną. s.isnumeric()obejmuje postacie, które int()odrzucają.
jfs

9

Chciałem zobaczyć, która metoda jest najszybsza. Ogólnie najlepsze i najbardziej spójne wyniki dała check_replacefunkcja. Najszybsze wyniki dała check_exceptionfunkcja, ale tylko wtedy, gdy nie został zgłoszony żaden wyjątek - co oznacza, że ​​jego kod jest najbardziej wydajny, ale narzut związany z rzuceniem wyjątku jest dość duży.

Pamiętaj, że sprawdzanie poprawności rzutowania to jedyna metoda, która jest dokładna, na przykład działa to, check_exceptionale pozostałe dwie funkcje testowe zwrócą wartość False dla prawidłowej liczby zmiennoprzecinkowej:

huge_number = float('1e+100')

Oto kod testu porównawczego:

import time, re, random, string

ITERATIONS = 10000000

class Timer:    
    def __enter__(self):
        self.start = time.clock()
        return self
    def __exit__(self, *args):
        self.end = time.clock()
        self.interval = self.end - self.start

def check_regexp(x):
    return re.compile("^\d*\.?\d*$").match(x) is not None

def check_replace(x):
    return x.replace('.','',1).isdigit()

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

to_check = [check_regexp, check_replace, check_exception]

print('preparing data...')
good_numbers = [
    str(random.random() / random.random()) 
    for x in range(ITERATIONS)]

bad_numbers = ['.' + x for x in good_numbers]

strings = [
    ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(random.randint(1,10)))
    for x in range(ITERATIONS)]

print('running test...')
for func in to_check:
    with Timer() as t:
        for x in good_numbers:
            res = func(x)
    print('%s with good floats: %s' % (func.__name__, t.interval))
    with Timer() as t:
        for x in bad_numbers:
            res = func(x)
    print('%s with bad floats: %s' % (func.__name__, t.interval))
    with Timer() as t:
        for x in strings:
            res = func(x)
    print('%s with strings: %s' % (func.__name__, t.interval))

Oto wyniki z Python 2.7.10 na MacBooku Pro 2017 2017:

check_regexp with good floats: 12.688639
check_regexp with bad floats: 11.624862
check_regexp with strings: 11.349414
check_replace with good floats: 4.419841
check_replace with bad floats: 4.294909
check_replace with strings: 4.086358
check_exception with good floats: 3.276668
check_exception with bad floats: 13.843092
check_exception with strings: 15.786169

Oto wyniki z Python 3.6.5 na MacBooku Pro 2017 2017:

check_regexp with good floats: 13.472906000000009
check_regexp with bad floats: 12.977665000000016
check_regexp with strings: 12.417542999999995
check_replace with good floats: 6.011045999999993
check_replace with bad floats: 4.849356
check_replace with strings: 4.282754000000011
check_exception with good floats: 6.039081999999979
check_exception with bad floats: 9.322753000000006
check_exception with strings: 9.952595000000002

Oto wyniki z PyPy 2.7.13 na MacBooku Pro 2017 2017:

check_regexp with good floats: 2.693217
check_regexp with bad floats: 2.744819
check_regexp with strings: 2.532414
check_replace with good floats: 0.604367
check_replace with bad floats: 0.538169
check_replace with strings: 0.598664
check_exception with good floats: 1.944103
check_exception with bad floats: 2.449182
check_exception with strings: 2.200056

10
Powinieneś również przetestować wydajność pod kątem nieprawidłowych przypadków. Z tymi liczbami nie powstaje wyjątek, który jest dokładnie częścią „powolną”.
Ugo Méda

1
@ UgoMéda wziąłem twoją radę od 2013 roku i zrobiłem to :)
Ron Reiter

„Pamiętaj, że sprawdzanie poprawności rzutu jest jedyną dokładną metodą” <- to nie jest prawdą. Uruchomiłem test, używając wyrażenia regularnego w mojej odpowiedzi powyżej, i faktycznie działa szybciej niż wyrażenie regularne. Dodam wyniki do mojej odpowiedzi powyżej.
David Ljung Madison Stellar

Nawiasem mówiąc, jako zabawny punkt, twórca złych liczb może faktycznie utworzyć pewne prawidłowe liczby, choć byłoby to dość rzadkie. :)
David Ljung Madison Stellar

8

Podsumowując, sprawdzając Nan, nieskończoność i liczby zespolone (wydaje się, że są one określone przez j, a nie i, tj. 1 + 2j), daje:

def is_number(s):
    try:
        n=str(float(s))
        if n == "nan" or n=="inf" or n=="-inf" : return False
    except ValueError:
        try:
            complex(s) # for complex
        except ValueError:
            return False
    return True

Jak dotąd najlepsza odpowiedź. Dzięki
anish

6

Dane wejściowe mogą być następujące:

a="50" b=50 c=50.1 d="50.1"


1-Ogólne dane wejściowe:

Wejście tej funkcji może być wszystkim!

Sprawdza, czy podana zmienna jest liczbą. Ciągi liczbowe składają się z opcjonalnego znaku, dowolnej liczby cyfr, opcjonalnej części dziesiętnej i opcjonalnej części wykładniczej. Zatem + 0123.45e6 jest prawidłową wartością liczbową. Notacje szesnastkowe (np. 0xf4c3b00c) i binarne (np. 0b10100111001) są niedozwolone.

funkcja is_numeric

import ast
import numbers              
def is_numeric(obj):
    if isinstance(obj, numbers.Number):
        return True
    elif isinstance(obj, str):
        nodes = list(ast.walk(ast.parse(obj)))[1:]
        if not isinstance(nodes[0], ast.Expr):
            return False
        if not isinstance(nodes[-1], ast.Num):
            return False
        nodes = nodes[1:-1]
        for i in range(len(nodes)):
            #if used + or - in digit :
            if i % 2 == 0:
                if not isinstance(nodes[i], ast.UnaryOp):
                    return False
            else:
                if not isinstance(nodes[i], (ast.USub, ast.UAdd)):
                    return False
        return True
    else:
        return False

test:

>>> is_numeric("54")
True
>>> is_numeric("54.545")
True
>>> is_numeric("0x45")
True

is_floatfunkcja

Sprawdza, czy dana zmienna jest zmiennoprzecinkowa. ciągi pływające składają się z opcjonalnego znaku, dowolnej liczby cyfr, ...

import ast

def is_float(obj):
    if isinstance(obj, float):
        return True
    if isinstance(obj, int):
        return False
    elif isinstance(obj, str):
        nodes = list(ast.walk(ast.parse(obj)))[1:]
        if not isinstance(nodes[0], ast.Expr):
            return False
        if not isinstance(nodes[-1], ast.Num):
            return False
        if not isinstance(nodes[-1].n, float):
            return False
        nodes = nodes[1:-1]
        for i in range(len(nodes)):
            if i % 2 == 0:
                if not isinstance(nodes[i], ast.UnaryOp):
                    return False
            else:
                if not isinstance(nodes[i], (ast.USub, ast.UAdd)):
                    return False
        return True
    else:
        return False

test:

>>> is_float("5.4")
True
>>> is_float("5")
False
>>> is_float(5)
False
>>> is_float("5")
False
>>> is_float("+5.4")
True

co jest ast ?


2- Jeśli masz pewność, że zmienna treść to String :

użyj metody str.isdigit ()

>>> a=454
>>> a.isdigit()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute 'isdigit'
>>> a="454"
>>> a.isdigit()
True

3-numeryczne wprowadzanie danych:

wykryj wartość int:

>>> isinstance("54", int)
False
>>> isinstance(54, int)
True
>>> 

wykrywanie pływaka:

>>> isinstance("45.1", float)
False
>>> isinstance(45.1, float)
True

co to jest „ ast”?

4

Zrobiłem test prędkości. Powiedzmy, że jeśli łańcuch może być liczbą, strategia try / try jest najszybsza z możliwych. Jeśli łańcuch nie jest prawdopodobnie liczbą i jesteś zainteresowany sprawdzaniem liczb całkowitych , warto wykonać test (isdigit plus nagłówek „-”). Jeśli chcesz sprawdzić liczbę zmiennoprzecinkową, musisz użyć kodu try / oprócz bez znaku ucieczki.


4

Musiałem ustalić, czy ciąg rzutowany na podstawowe typy (float, int, str, bool). Po nie znalezieniu niczego w Internecie stworzyłem to:

def str_to_type (s):
    """ Get possible cast type for a string

    Parameters
    ----------
    s : string

    Returns
    -------
    float,int,str,bool : type
        Depending on what it can be cast to

    """    
    try:                
        f = float(s)        
        if "." not in s:
            return int
        return float
    except ValueError:
        value = s.upper()
        if value == "TRUE" or value == "FALSE":
            return bool
        return type(s)

Przykład

str_to_type("true") # bool
str_to_type("6.0") # float
str_to_type("6") # int
str_to_type("6abc") # str
str_to_type(u"6abc") # unicode       

Możesz uchwycić typ i użyć go

s = "6.0"
type_ = str_to_type(s) # float
f = type_(s) 

3

RyanN sugeruje

Jeśli chcesz zwrócić False dla NaN i Inf, zmień wiersz na x = float (s); return (x == x) i (x - 1! = x). Powinno to zwrócić wartość True dla wszystkich pływaków oprócz Inf i NaN

Ale to nie do końca działa, ponieważ dla wystarczająco dużych pływaków x-1 == xzwraca true. Na przykład,2.0**54 - 1 == 2.0**54


3

Myślę, że twoje rozwiązanie jest w porządku, ale tak jest poprawna implementacja wyrażenia regularnego.

Wydaje się, że istnieje wiele nienawiści do wyrażeń regularnych wobec tych odpowiedzi, które moim zdaniem są nieuzasadnione, wyrażenia regularne mogą być dość czyste, poprawne i szybkie. To naprawdę zależy od tego, co próbujesz zrobić. Pierwotne pytanie brzmiało, jak „sprawdzić, czy ciąg może być reprezentowany przez liczbę (liczba zmiennoprzecinkowa)” (zgodnie z tytułem). Prawdopodobnie zechcesz użyć wartości liczbowej / zmiennoprzecinkowej po sprawdzeniu jej poprawności, w którym to przypadku próba / wyjątek ma sens. Ale jeśli z jakiegoś powodu chcesz po prostu sprawdzić, czy łańcuch jest liczbąto wyrażenie regularne również działa dobrze, ale trudno jest poprawić. Myślę, że większość dotychczasowych wyrażeń regularnych, na przykład, nie analizuje poprawnie ciągów znaków bez części całkowitej (takiej jak „.7”), która jest liczbą zmiennoprzecinkową, jeśli chodzi o python. Jest to nieco trudne do sprawdzenia w jednym wyrażeniu regularnym, w którym część ułamkowa nie jest wymagana. Dodałem dwa wyrażenia regularne, aby to pokazać.

Rodzi to interesujące pytanie, czym jest „liczba”. Czy dołączasz „inf”, który jest poprawny jako zmiennoprzecinkowy w Pythonie? Czy może zawierasz liczby, które są „liczbami”, ale może nie mogą być reprezentowane w pythonie (takie jak liczby większe niż liczba zmiennoprzecinkowa).

Istnieją również niejasności w sposobie przetwarzania liczb. Na przykład, co z „--20”? Czy to jest „liczba”? Czy to legalny sposób przedstawienia „20”? Python pozwoli ci zrobić „var = --20” i ustawić go na 20 (choć tak naprawdę dzieje się tak, ponieważ traktuje to jako wyrażenie), ale liczba zmiennoprzecinkowa („- 20”) nie działa.

W każdym razie, bez dodatkowych informacji, oto regex, który moim zdaniem obejmuje wszystkie inty i unosi się, gdy pyton je analizuje .

# Doesn't properly handle floats missing the integer part, such as ".7"
SIMPLE_FLOAT_REGEXP = re.compile(r'^[-+]?[0-9]+\.?[0-9]+([eE][-+]?[0-9]+)?$')
# Example "-12.34E+56"      # sign (-)
                            #     integer (12)
                            #           mantissa (34)
                            #                    exponent (E+56)

# Should handle all floats
FLOAT_REGEXP = re.compile(r'^[-+]?([0-9]+|[0-9]*\.[0-9]+)([eE][-+]?[0-9]+)?$')
# Example "-12.34E+56"      # sign (-)
                            #     integer (12)
                            #           OR
                            #             int/mantissa (12.34)
                            #                            exponent (E+56)

def is_float(str):
  return True if FLOAT_REGEXP.match(str) else False

Niektóre przykładowe wartości testowe:

True  <- +42
True  <- +42.42
False <- +42.42.22
True  <- +42.42e22
True  <- +42.42E-22
False <- +42.42e-22.8
True  <- .42
False <- 42nope

Uruchamianie benchmarkingu kod @ Ron-Reitera odpowiedziami pokazuje, że ten regex jest rzeczywiście szybciej niż normalny regex i jest znacznie szybciej na obsługę złych wartości niż wyjątkiem, co sprawia, że jakiś sens. Wyniki:

check_regexp with good floats: 18.001921
check_regexp with bad floats: 17.861423
check_regexp with strings: 17.558862
check_correct_regexp with good floats: 11.04428
check_correct_regexp with bad floats: 8.71211
check_correct_regexp with strings: 8.144161
check_replace with good floats: 6.020597
check_replace with bad floats: 5.343049
check_replace with strings: 5.091642
check_exception with good floats: 5.201605
check_exception with bad floats: 23.921864
check_exception with strings: 23.755481

Mam nadzieję, że to prawda - chciałbym usłyszeć o wszelkich kontrprzykładach. :)
David Ljung Madison Stellar

2
import re
def is_number(num):
    pattern = re.compile(r'^[-+]?[-0-9]\d*\.\d*|[-+]?\.?[0-9]\d*$')
    result = pattern.match(num)
    if result:
        return True
    else:
        return False


​>>>: is_number('1')
True

>>>: is_number('111')
True

>>>: is_number('11.1')
True

>>>: is_number('-11.1')
True

>>>: is_number('inf')
False

>>>: is_number('-inf')
False

2
Czy nie uważasz, że 1e6to liczba?
Mark Dickinson

1

Oto mój prosty sposób na zrobienie tego. Powiedzmy, że przeglądam niektóre ciągi i chcę je dodać do tablicy, jeśli okażą się liczbami.

try:
    myvar.append( float(string_to_check) )
except:
    continue

Zamień plik myvar.apppend na dowolną operację, którą chcesz zrobić z łańcuchem, jeśli okaże się, że jest liczbą. Chodzi o to, aby spróbować użyć operacji float () i użyć zwróconego błędu, aby ustalić, czy łańcuch jest liczbą, czy nie.


Powinieneś przenieść część dołączającą tej funkcji do instrukcji else, aby uniknąć przypadkowego wyzwolenia wyjątku, jeśli coś jest nie tak z tablicą.
DarwinSurvivor

1

Użyłem również funkcji, o której wspomniałeś, ale wkrótce zauważam, że ciągi znaków jako „Nan”, „Inf” i jej odmiany są uważane za liczbę. Proponuję więc ulepszoną wersję twojej funkcji, która zwróci false dla tego typu danych wejściowych i nie zawiedzie wariantów „1e3”:

def is_float(text):
    try:
        float(text)
        # check for nan/infinity etc.
        if text.isalpha():
            return False
        return True
    except ValueError:
        return False

1

Ten kod obsługuje wykładniki, liczby zmiennoprzecinkowe i liczby całkowite bez użycia wyrażenia regularnego.

return True if str1.lstrip('-').replace('.','',1).isdigit() or float(str1) else False

1

Funkcja pomocnika użytkownika:

def if_ok(fn, string):
  try:
    return fn(string)
  except Exception as e:
    return None

następnie

if_ok(int, my_str) or if_ok(float, my_str) or if_ok(complex, my_str)
is_number = lambda s: any([if_ok(fn, s) for fn in (int, float, complex)])

0

Możesz uogólnić technikę wyjątku w przydatny sposób, zwracając bardziej przydatne wartości niż Prawda i Fałsz. Na przykład ta funkcja umieszcza cudzysłowy w okrągłych ciągach, ale pozostawia liczby same. Właśnie tego potrzebowałem do szybkiego i brudnego filtra, aby stworzyć pewne zmienne definicje dla R.

import sys

def fix_quotes(s):
    try:
        float(s)
        return s
    except ValueError:
        return '"{0}"'.format(s)

for line in sys.stdin:
    input = line.split()
    print input[0], '<- c(', ','.join(fix_quotes(c) for c in input[1:]), ')'

0

Pracowałem nad problemem, który doprowadził mnie do tego wątku, a mianowicie jak przekonwertować zbiór danych na ciągi i liczby w najbardziej intuicyjny sposób. Po przeczytaniu oryginalnego kodu zrozumiałem, że to, czego potrzebowałem, było inne na dwa sposoby:

1 - Chciałem uzyskać wynik w postaci liczby całkowitej, jeśli ciąg reprezentował liczbę całkowitą

2 - Chciałem, aby wynik liczbowy lub ciągowy przylgnął do struktury danych

więc dostosowałem oryginalny kod, aby uzyskać tę pochodną:

def string_or_number(s):
    try:
        z = int(s)
        return z
    except ValueError:
        try:
            z = float(s)
            return z
        except ValueError:
            return s

0

Spróbuj tego.

 def is_number(var):
    try:
       if var == int(var):
            return True
    except Exception:
        return False

Nie można odpowiedzieć za pomocąis_number('10')
geotheory

@geotheory, co masz na myśli mówiąc „nie odpowiada”?
Solomon Ucko

0
def is_float(s):
    if s is None:
        return False

    if len(s) == 0:
        return False

    digits_count = 0
    dots_count = 0
    signs_count = 0

    for c in s:
        if '0' <= c <= '9':
            digits_count += 1
        elif c == '.':
            dots_count += 1
        elif c == '-' or c == '+':
            signs_count += 1
        else:
            return False

    if digits_count == 0:
        return False

    if dots_count > 1:
        return False

    if signs_count > 1:
        return False

    return True
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.