Câu hỏi Làm thế nào để kiểm tra xem một chuỗi là một số (float)?


Cách tốt nhất có thể để kiểm tra xem một chuỗi có thể được biểu diễn như một số trong Python không?

Chức năng hiện tại của tôi là:

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

Trong đó, không chỉ xấu xí và chậm chạp, có vẻ khó khăn. Tuy nhiên tôi đã không tìm thấy một phương pháp tốt hơn bởi vì gọi float trong chức năng chính thậm chí còn tồi tệ hơn.


1251
2017-12-09 20:03


gốc


Có gì sai với giải pháp hiện tại của bạn? Nó ngắn, nhanh và dễ đọc. - Colonel Panic
Và bạn không chỉ phải trả về Đúng hay Sai. Thay vào đó, bạn có thể trả lại giá trị được sửa đổi phù hợp - ví dụ: bạn có thể sử dụng giá trị này để đặt các số không trong dấu ngoặc kép. - Thruston
Nó sẽ không tốt hơn để trả lại kết quả của float (s) trong trường hợp chuyển đổi thành công? Bạn vẫn có kiểm tra thành công (kết quả là False) và bạn thực sự CÓ chuyển đổi, mà bạn có thể muốn. - Jiminion
Mặc dù câu hỏi này là cũ hơn, tôi chỉ muốn nói rằng đây là một cách thanh lịch được ghi nhận là EAFP. Vì vậy, có lẽ là giải pháp tốt nhất cho loại vấn đề này. - thiruvenkadam
Đừng trả lại kết quả của float (s) hoặc None on fail. nếu bạn sử dụng nó như x = float('0.00'); if x: use_float(x); bạn đã có một lỗi trong mã của mình. Các giá trị chân thực là lý do khiến các hàm này tăng ngoại lệ thay vì trả về None ngay từ đầu. Một giải pháp tốt hơn là chỉ để tránh các chức năng tiện ích và bao quanh các cuộc gọi để nổi trong một try catch khi bạn muốn sử dụng nó. - ovangle


Các câu trả lời:


Mà, không chỉ là xấu xí và chậm

Tôi sẽ tranh chấp cả hai.

Một regex hoặc phân tích chuỗi khác sẽ xấu hơn và chậm hơn.

Tôi không chắc rằng bất cứ điều gì có thể nhanh hơn nhiều so với ở trên. Nó gọi hàm và trả về. Try / Catch không giới thiệu nhiều chi phí vì ngoại lệ phổ biến nhất được phát hiện mà không cần tìm kiếm các khung ngăn xếp mở rộng.

Vấn đề là bất kỳ hàm chuyển đổi số nào có hai loại kết quả

  • Một số, nếu số đó hợp lệ
  • Mã trạng thái (ví dụ: qua errno) hoặc ngoại lệ để cho biết rằng không thể phân tích cú pháp số hợp lệ.

C (ví dụ) hacks xung quanh này một số cách. Python đặt nó rõ ràng và rõ ràng.

Tôi nghĩ rằng mã của bạn để làm điều này là hoàn hảo.


566
2017-12-09 20:30



Tôi không nghĩ rằng mã là hoàn hảo (nhưng tôi nghĩ rằng nó rất gần): nó là bình thường hơn để đặt chỉ có phần được "thử nghiệm" trong try khoản, vì vậy tôi sẽ đặt return True trong một else mệnh đề của try. Một trong những lý do là với mã trong câu hỏi, nếu tôi phải xem lại nó, tôi sẽ phải kiểm tra câu lệnh thứ hai trong try mệnh đề không thể nâng cao một ValueError: được cấp, điều này không đòi hỏi quá nhiều thời gian hoặc sức mạnh của bộ não, nhưng tại sao lại sử dụng bất kỳ khi nào không cần thiết? - Eric Lebigot
Câu trả lời có vẻ hấp dẫn, nhưng làm cho tôi tự hỏi tại sao nó không được cung cấp out-of-the-box ... Tôi sẽ sao chép này và sử dụng nó trong mọi trường hợp. - sage
Thật kinh khủng. Làm thế nào về nếu tôi không quan tâm số lượng Là chỉ là đó là một con số (đó là những gì mang lại cho tôi ở đây)? Thay vì 1 dòng IsNumeric() Tôi hoặc là kết thúc với một thử / bắt hoặc gói khác một thử / bắt. Ugh - Basic
@ Bicic tôi không nhận được quan điểm của bạn. Đặt tên cho chức năng của bạn, tính năng kiểm tra IsNumeric và sử dụng chức năng đó. Đó là ý tưởng sử dụng các hàm - có một lớp lót. - Nils
Nó không được cung cấp 'ra khỏi hộp' bởi vì if is_number(s): x = float(x) else: // fail là cùng một số dòng mã như try: x = float(x) catch TypeError: # fail. Chức năng tiện ích này là một trừu tượng hoàn toàn không cần thiết. - ovangle


Trong trường hợp bạn đang tìm kiếm các số nguyên phân tích (tích cực, không dấu) thay vì phao, bạn có thể sử dụng isdigit() cho các đối tượng chuỗi.

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

Phương thức chuỗi - isdigit()

Ngoài ra còn có một cái gì đó trên chuỗi Unicode, mà tôi không quá quen thuộc với Unicode - Là số thập phân / thập phân


1340
2017-12-09 20:15



Tuy nhiên, điều này sẽ không làm việc cho hệ thập lục phân. - Nico
Nó cũng không hoạt động với các số thập phân như 1.2 - Daniel Goldberg
Đó là một tiêu cực về âm bản cũng - intrepion
@ DanielGoldberg: Tôi nghĩ bạn cần tra cứu định nghĩa "chữ số" - Jason9987
@ Jason9987, có vẻ như bạn cần phải đọc lại câu hỏi. - Daniel Goldberg


Có một ngoại lệ mà bạn có thể muốn đưa vào tài khoản: chuỗi 'NaN'

Nếu bạn muốn is_number trả về FALSE cho 'NaN', mã này sẽ không hoạt động khi Python chuyển đổi nó thành biểu diễn của một số không phải là số (nói về các vấn đề về danh tính):

>>> float('NaN')
nan

Nếu không, tôi thực sự nên cảm ơn bạn vì đoạn mã mà tôi sử dụng rộng rãi. :)

G.


64
2017-09-01 14:06



Thực ra, NaN có thể là một giá trị tốt để trả lại (thay vì False) nếu văn bản được truyền không thực sự là biểu diễn của một số. Kiểm tra nó là một loại đau (Python's float loại thực sự cần một phương pháp cho nó) nhưng bạn có thể sử dụng nó trong tính toán mà không tạo ra một lỗi, và chỉ cần kiểm tra kết quả. - kindall
Một ngoại lệ khác là chuỗi 'inf'. Hoặc inf hoặc là NaN cũng có thể được thêm tiền tố + hoặc là - và vẫn được chấp nhận. - agf
Nếu bạn muốn trả về False cho một NaN và Inf, thay đổi dòng thành x = float (s); return (x == x) và (x - 1! = x). Điều này sẽ trả về True cho tất cả các float trừ Inf và NaN - RyanN
x-1 == x là đúng cho các phao lớn nhỏ hơn inf. Từ Python 3.2 bạn có thể sử dụng math.isfinite để kiểm tra các số không phải là NaN cũng như vô hạn hoặc kiểm tra cả hai math.isnan và math.isinf trước đó. - Steve Jessop


TL; DR Giải pháp tốt nhất là s.replace('.','',1).isdigit()

Tôi đã làm một số điểm chuẩn so sánh các cách tiếp cận khác nhau

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

Nếu chuỗi không phải là một số, khối ngoại trừ khá chậm. Nhưng quan trọng hơn, phương pháp thử ngoại trừ là phương pháp duy nhất xử lý các ký hiệu khoa học một cách chính xác.

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

Ký hiệu float ".1234" không được hỗ trợ bởi:
- 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__)

Ký hiệu khoa học "1,000000e + 50" không được hỗ trợ bởi:
- is_number_regex
- is_number_repl_isdigit
Ký hiệu khoa học "1e50" không được hỗ trợ bởi:
- is_number_regex
- is_number_repl_isdigit

EDIT: Kết quả benchmark

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

nơi các chức năng sau được kiểm tra

from re import match as re_match
from re import compile as re_compile

def is_number_tryexcept(s):
    """ Returns True is string is a number. """
    try:
        float(s)
        return True
    except ValueError:
        return False

def is_number_regex(s):
    """ Returns True is string is a number. """
    if re_match("^\d+?\.\d+?$", s) is None:
        return s.isdigit()
    return True


comp = re_compile("^\d+?\.\d+?$")    

def compiled_regex(s):
    """ Returns True is string is a number. """
    if comp.match(s) is None:
        return s.isdigit()
    return True


def is_number_repl_isdigit(s):
    """ Returns True is string is a number. """
    return s.replace('.','',1).isdigit()

enter image description here


64
2018-05-13 19:28



cho biểu đồ đẹp +1. Tôi thấy điểm chuẩn và thấy đồ thị, tất cả điều TL, DR trở nên rõ ràng và trực quan. - Julian Chukwu
Tôi đồng ý với @JCChuks: biểu đồ giúp rất nhiều để có được tất cả TL, DR nhanh chóng. Nhưng tôi nghĩ TL, DR (như: TL; DR : giải pháp tốt nhất là s.replace('.','',1).isdigit()) sẽ xuất hiện ở đầu của anwser này. Trong mọi trường hợp, nó phải là một trong những chấp nhận. Cảm ơn! - Simon C.
Tôi xin lỗi nếu điều này không đúng chỗ nhưng bạn đã sử dụng cái gì để tạo biểu đồ / đồ thị? - Pryftan
chỉ matplotlib đơn giản - Sebastian
TLDR là sai lầm và không trung thực. Là "tốt nhất" không tương quan với bất kỳ điểm chuẩn hiệu suất nào. Ví dụ, tôi thường coi trọng khả năng đọc nhiều hơn so với tối ưu hóa vi mô, vì vậy điểm chuẩn hầu như không có trọng lượng trong việc xác định giải pháp tốt nhất cho ngữ cảnh của tôi. TLDR sẽ chính xác hơn về nó: "tốt nhất nếu được xếp hạng theo thời gian thực hiện từ một tập hợp nhỏ các điểm chuẩn tùy ý" - Corey Goldberg


Còn cái này thì sao:

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

sẽ chỉ trả về true nếu có hoặc không có '.' trong chuỗi chữ số.

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

sẽ trả về false

chỉnh sửa: chỉ nhìn thấy một bình luận khác ... thêm một .replace(badstuff,'',maxnum_badstuff) cho các trường hợp khác có thể được thực hiện. nếu bạn đang đi qua muối và không gia vị tùy ý (ref:xkcd # 974) điều này sẽ làm tốt: P


52
2018-05-25 22:22



Tuy nhiên, điều này không tính đến số âm. - Michael Barton
Hoặc thập lục phân. - twasbrillig
Hoặc số với số mũ như 1.234e56 (cũng có thể được viết là +1.234E+56 và một vài biến thể khác). - Alfe
re.match(r'^[+-]*(0[xbo])?[0-9A-Fa-f]*\.?[0-9A-Fa-f]*(E[+-]*[0-9A-Fa-f]+)$', 'str') nên làm một công việc tốt hơn để xác định một số (nhưng không phải tất cả, tôi không tuyên bố rằng). Tôi không khuyên bạn nên sử dụng điều này, tốt hơn nhiều để sử dụng mã ban đầu của Người hỏi. - Baldrickk
nếu bạn không thích giải pháp này, hãy đọc điều này trước khi downvoting! - aloisdg


Cập nhật sau khi Alfe đã chỉ ra rằng bạn không cần phải kiểm tra phao một cách riêng biệt như là xử lý phức tạp cả hai:

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

    return True

Trước đây đã nói: Có một số trường hợp hiếm hoi bạn cũng có thể cần phải kiểm tra các số phức (ví dụ: 1 + 2i), không thể được biểu thị bằng phao:

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

    return True

38
2017-12-11 04:56



Tôi không đồng ý. Đó là RẤT không sử dụng bình thường, và bạn sẽ xây dựng một cuộc gọi is_complex_number () tốt hơn khi bạn đang sử dụng chúng, chứ không phải là gánh nặng một cuộc gọi với hoạt động thêm cho một cơ hội 0,0001 của misoperation. - Jiminion
Bạn có thể loại bỏ float() công cụ hoàn toàn và chỉ cần kiểm tra complex() gọi để thành công. Mọi thứ được phân tích bởi float() có thể được phân tích bởi complex(). - Alfe
Hàm này sẽ trả về các giá trị NaNs và Inf của Pandas dưới dạng các giá trị số. - fixxxer


Trong đó, không chỉ xấu xí và chậm chạp, có vẻ khó khăn.

Nó có thể mất một số nhận được sử dụng để, nhưng đây là cách pythonic làm việc đó. Như đã được chỉ ra, các lựa chọn thay thế là tồi tệ hơn. Nhưng có một lợi thế khác là làm những việc theo cách này: đa hình.

Ý tưởng trung tâm đằng sau việc gõ vịt là "nếu nó đi và nói như một con vịt, thì đó là một con vịt." Điều gì nếu bạn quyết định rằng bạn cần phải phân lớp chuỗi để bạn có thể thay đổi cách bạn xác định nếu một cái gì đó có thể được chuyển đổi thành một phao? Hoặc nếu bạn quyết định thử nghiệm một số đối tượng khác hoàn toàn thì sao? Bạn có thể làm những việc này mà không phải thay đổi mã trên.

Các ngôn ngữ khác giải quyết những vấn đề này bằng cách sử dụng giao diện. Tôi sẽ lưu phân tích giải pháp nào tốt hơn cho một chuỗi khác. Mặc dù vậy, điểm mấu chốt là con trăn được quyết định ở phía gõ vịt của phương trình, và có lẽ bạn sẽ phải quen với cú pháp như thế này nếu bạn có kế hoạch thực hiện nhiều lập trình bằng Python (nhưng điều đó không có nghĩa là bạn phải thích nó tất nhiên).

Một điều khác bạn có thể muốn xem xét: Python là khá nhanh trong việc ném và bắt ngoại lệ so với nhiều ngôn ngữ khác (nhanh hơn 30 lần so với .Net chẳng hạn). Heck, ngôn ngữ chính nó thậm chí ném ngoại lệ để giao tiếp không đặc biệt, điều kiện chương trình bình thường (mỗi khi bạn sử dụng một vòng lặp for). Vì vậy, tôi sẽ không lo lắng quá nhiều về các khía cạnh hiệu suất của mã này cho đến khi bạn nhận thấy một vấn đề đáng kể.


37
2017-09-08 08:42



Một nơi phổ biến khác mà Python sử dụng các ngoại lệ cho các hàm cơ bản là trong hasattr() mà chỉ là một getattr() gọi bọc trong một try/except. Tuy nhiên, xử lý ngoại lệ chậm hơn kiểm soát luồng thông thường, do đó, sử dụng nó cho điều gì đó sẽ đúng hầu hết thời gian có thể dẫn đến một hình phạt hiệu suất. - kindall
Có vẻ như nếu bạn muốn một lớp lót, bạn SOL - Basic
Ngoài ra pythonic là ý tưởng rằng "tốt hơn để yêu cầu sự tha thứ hơn sự cho phép", liên quan đến tác động của việc có ngoại lệ giá rẻ. - heltonbiker


Dành cho int dùng cái này:

>>> "1221323".isdigit()
True

Nhưng với float chúng tôi cần một số thủ thuật ;-). Mỗi số phao có một điểm ...

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

Ngoài ra đối với số âm chỉ cần thêm lstrip():

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

Và bây giờ chúng ta có một cách phổ quát:

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

18
2018-02-18 01:35



Không xử lý những thứ như 1.234e56 và tương tự. Ngoài ra, tôi muốn được quan tâm như thế nào bạn sẽ tìm ra rằng 99999999999999999999e99999999999999999999 không phải là một số. Cố gắng phân tích nó phát hiện nhanh chóng. - Alfe
Điều này chạy nhanh hơn 30% so với giải pháp được chấp nhận trong danh sách các chuỗi 50m và nhanh hơn 150% trên danh sách các chuỗi 5k. - Zev Averbach


Chỉ cần bắt chước C #

Trong C #, có hai hàm khác nhau xử lý phân tích các giá trị vô hướng:

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

float.parse ():

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

Lưu ý: Nếu bạn đang tự hỏi tại sao tôi đã thay đổi ngoại lệ thành TypeError, đây là tài liệu.

float.try_parse ():

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

Lưu ý: Bạn không muốn trả lại boolean 'False' vì đó vẫn là một loại giá trị. Không có gì là tốt hơn bởi vì nó cho thấy thất bại. Tất nhiên, nếu bạn muốn một cái gì đó khác nhau, bạn có thể thay đổi tham số thất bại cho bất cứ điều gì bạn muốn.

Để mở rộng float để bao gồm 'parse ()' và 'try_parse ()', bạn cần phải monkeypatch lớp 'float' để thêm các phương thức này.

Nếu bạn muốn sự tôn trọng các chức năng tồn tại từ trước, mã phải giống như sau:

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

SideNote: Cá nhân tôi thích gọi nó là Khỉ Đột vì nó cảm thấy như tôi đang lạm dụng ngôn ngữ khi tôi làm điều này nhưng YMMV.

Sử dụng:

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

Và Sage Pythonas vĩ đại nói với Holy See Sharpisus, "Bất cứ điều gì bạn có thể làm tôi có thể làm tốt hơn, tôi có thể làm bất cứ điều gì tốt hơn bạn."


14
2017-08-14 03:34



Tôi đã được mã hóa trong JS chủ yếu gần đây và đã không thực sự kiểm tra này để có thể có một số lỗi nhỏ. Nếu bạn thấy bất kỳ, hãy sửa lỗi của tôi. - Evan Plaice
Để thêm hỗ trợ cho các số phức, hãy xem câu trả lời của @Matthew Wilcoxson. stackoverflow.com/a/3335060/290340. - Evan Plaice
Sử dụng ! thay vì not có thể là một lỗi nhỏ, nhưng bạn chắc chắn không thể gán các thuộc tính cho tích hợp float trong CPython. - BlackJack


Đối với chuỗi không phải số, try: except: thực sự chậm hơn so với cụm từ thông dụng. Đối với chuỗi số hợp lệ, regex chậm hơn. Vì vậy, phương pháp thích hợp phụ thuộc vào đầu vào của bạn.

Nếu bạn thấy rằng bạn đang ở trong một ràng buộc hiệu suất, bạn có thể sử dụng một mô-đun bên thứ ba mới được gọi là fastnumbers cung cấp một hàm gọi là isfloat. Tiết lộ đầy đủ, tôi là tác giả. Tôi đã bao gồm kết quả của nó trong thời gian dưới đây.


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

Bạn có thể thấy

  • try: except: nhanh cho đầu vào số nhưng rất chậm cho đầu vào không hợp lệ
  • regex rất hiệu quả khi đầu vào không hợp lệ
  • fastnumbers thắng cả hai trường hợp

14
2018-01-05 15:21



Tôi đứng sửa chữa: -} nó không giống như nó đã làm điều này. Có thể sử dụng tên như prep_code_basis và prep_code_re_method sẽ ngăn cản sai lầm của tôi. - Alfe
Bạn có nhớ giải thích cách hoạt động của mô-đun của bạn, ít nhất là cho isfloat chức năng? - Solomon Ucko
@SolomonUcko Đây là một liên kết đến mã nguồn cho phần kiểm tra chuỗi: github.com/SethMMorton/fastnumbers/blob/v1.0.0/src/…. Về cơ bản, nó đi qua từng ký tự trong chuỗi theo thứ tự và xác nhận rằng nó tuân theo một mẫu cho một float hợp lệ. Nếu đầu vào đã là một số, nó chỉ sử dụng nhanh PyFloat_Check. - SethMMorton