tuple - python type cast




如何檢查字符串是否是數字(浮點數)? (20)

檢查一個字符串是否可以在Python中表示為數字的最佳方法是什麼?

我現在有的功能是:

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

這不僅醜陋而且緩慢,看起來笨重。 但是我還沒找到更好的方法,因為在主函數中調用float更糟。


這不僅醜陋而且緩慢

我會同時爭執。

正則表達式或其他字符串解析會更慢更慢。

我不確定有什麼比以上更快。 它調用函數並返回。 Try / Catch不會引入太多開銷,因為最常見的異常是在沒有廣泛搜索堆棧幀的情況下捕獲的。

問題是任何數字轉換函數都有兩種結果

  • 一個數字,如果該數字有效
  • 狀態碼(例如,通過errno)或例外,以顯示無法解析任何有效的號碼。

C(作為一個例子)通過很多方式來解決這個問題。 Python清楚而明確地闡述了它。

我認為你的代碼是完美的。


這不僅醜陋而且緩慢,看起來笨重。

這可能需要一些習慣,但這是做Python的pythonic方式。 正如已經指出的那樣,替代品更糟。 但是這樣做的另一個好處是:多態性。

鴨子打字的核心思想是“如果它像鴨子一樣走路和說話,那麼它就是一隻鴨子。” 如果您決定需要對字符串進行子類化,那麼您可以更改如何確定某些東西是否可以轉換為浮點數? 或者如果你決定完全測試一些其他對象呢? 你可以做這些事情,而無需改變上面的代碼。

其他語言通過使用接口來解決這些問題。 我將保存哪個解決方案對另一個線程更好的分析。 不過,關鍵在於python肯定是在鴨子打字方面,如果你打算在Python中進行大量的編程,你可能必須習慣這樣的語法(但這並不意味著你當然必須喜歡它)。

另一件你可能要考慮的事情是:與許多其他語言相比,Python在拋出和捕獲異常方面速度非常快(比例如比.Net快30倍)。 哎呀,語言本身甚至會拋出異常來傳達非常規的正常程序條件(每次使用for循環時)。 因此,在你注意到一個重大問題之前,我不會太擔心這段代碼的性能問題。


TL; DR最好的解決方案是s.replace('.','',1).isdigit()

我做了一些比較不同方法的benchmarks

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

如果字符串不是數字,則除外塊非常慢。 但更重要的是,try-except方法是正確處理科學符號的唯一方法。

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

浮點記號“.1234”不受以下情況的支持:
- 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__)

科學記數法“1.000000e + 50”不支持:
- is_number_regex
- is_number_repl_isdigit
科學記數法“1e50”不受以下條件的支持:
- is_number_regex
- is_number_repl_isdigit

編輯:基準測試結果

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

以下功能已經過測試

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


Here's my simple way of doing it. Let's say that I'm looping through some strings and I want to add them to an array if they turn out to be numbers.

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

Replace the myvar.apppend with whatever operation you want to do with the string if it turns out to be a number. The idea is to try to use a float() operation and use the returned error to determine whether or not the string is a number.


I was working on a problem that led me to this thread, namely how to convert a collection of data to strings and numbers in the most intuitive way. I realized after reading the original code that what I needed was different in two ways:

1 - I wanted an integer result if the string represented an integer

2 - I wanted a number or a string result to stick into a data structure

so I adapted the original code to produce this derivative:

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

If you want to know if the entire string can be represented as a number you'll want to use a regexp (or maybe convert the float back to a string and compare it to the source string, but I'm guessing that's not very fast).


Try this.

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

You can generalize the exception technique in a useful way by returning more useful values than True and False. For example this function puts quotes round strings but leaves numbers alone. Which is just what I needed for a quick and dirty filter to make some variable definitions for 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:]), ')'


你的代碼對我來說看起來很好。

也許你認為代碼是“笨重的”,因為使用異常? 請注意,由於Python性能低下,Python程序員在改善代碼可讀性時傾向於大量使用異常。


可以說你有字符串中的數字。 str =“100949”,你想檢查它是否只有數字

if str.isdigit():
returns TRUE or FALSE 

isdigit文檔

否則你的方法很好地發現字符串中數字的出現。


在Alfe指出你並不需要單獨檢查浮點數作為複雜的句柄:

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

    return True

之前說過:您可能還需要檢查一些複雜數字(例如1 + 2i),這些數字不能用浮點數表示:

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

對於int使用這個:

>>> "1221323".isdigit()
True

但對於float我們需要一些技巧;-)。 每個浮點數都有一個點...

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

對於負數也只需添加lstrip()

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

現在我們得到一個普遍的方式:

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

對於非數字字符串,請try: except:實際上比正則表達式慢。 對於有效數字的字符串,正則表達式更慢。 所以,適當的方法取決於你的輸入。

如果您發現自己處於性能綁定中,則可以使用名為fastnumbers的新第三方模塊,該模塊提供稱為isfloat的功能。 充分披露,我是作者。 我已在下面的時間表中列出了其結果。

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

如你看到的

  • try: except:數字輸入快,但對於無效輸入非常慢
  • 當輸入無效時,正則表達式非常有效
  • fastnumbers在兩種情況下fastnumbers獲勝

我想看看哪種方法最快。 總的來說,最好的和最一致的結果是由check_replace函數給出的。 最快的結果是由check_exception函數給出的,但是只有當沒有異常被觸發時 - 意味著它的代碼是最有效的,但拋出異常的開銷非常大。

請注意,檢查成功的轉換是唯一準確的方法,例如,這與check_exception工作,但其他兩個測試函數將返回False以獲得有效的浮點數:

huge_number = float('1e+100')

以下是基準代碼:

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

以下是2017年MacBook Pro 13上Python 2.7.10的結果:

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

以下是2017年MacBook Pro 13上Python 3.6.5的結果:

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

以下是2017年MacBook Pro 13上PyPy 2.7.13的結果:

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

我知道這是特別老,但我會添加一個答案,我相信涵蓋了從最高投票答案中缺失的信息,可能對任何發現此問題的人非常有價值:

對於以下每種方法,如果您需要接受任何輸入,請將它們與計數連接起來。 (假設我們使用整數的聲音定義而不是0-255等)

x.isdigit()適用於檢查x是否為整數。

x.replace('-','').isdigit()適用於檢查x是否定的(檢入第一個位置)

x.replace('.','').isdigit()適用於檢查x是否為小數。

x.replace(':','').isdigit()適用於檢查x是否是比率。

x.replace('/','',1).isdigit()適用於檢查x是否為分數。


所以把它放在一起,檢查Nan,無窮大和復數(看起來它們是用j指定的,而不是i,即1 + 2j),結果是:

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

投射浮動並捕獲ValueError可能是最快的方法,因為float()專門用於此目的。 任何其他需要字符串解析(正則表達式等)的東西可能會比較慢,因為它沒有針對此操作進行調整。 我的$ 0.02。


瑞恩建議

如果你想為NaN和Inf返回False,把行改為x = float(s); 返回(x == x)和(x-1!= x)。 對於除Inf和NaN之外的所有浮標,這應該返回True

但是這不起作用,因為對於足夠大的浮點數, x-1 == x返回true。 例如, 2.0**54 - 1 == 2.0**54


這個怎麼樣:

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

只有在沒有'。'時才會返回true。 在數字串中。

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

將返回false

編輯:剛剛看到另一條評論...添加.replace(badstuff,'',maxnum_badstuff)可以完成其他情況。 如果你通過鹽而不是任意的調味品(ref: xkcd#974 ),這將做得很好:P





type-conversion