Como verifico se uma string é um número (float)?

1609

Qual é a melhor maneira possível de verificar se uma string pode ser representada como um número no Python?

A função que atualmente tenho atualmente é:

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

O que, não só é feio e lento, parece desajeitado. No entanto, eu não encontrei um método melhor porque chamar floata função principal é ainda pior.

Daniel Goldberg
fonte
61
O que há de errado com a sua solução atual? É curto, rápido e legível.
Coronel Panic
5
E você não precisa apenas retornar Verdadeiro ou Falso. Você pode retornar o valor modificado adequadamente - por exemplo, você pode usá-lo para colocar não números entre aspas.
Thruston
7
Não seria melhor retornar o resultado de flutuações no caso de uma conversão bem-sucedida? Você ainda tem a verificação do sucesso (o resultado é False) e, na verdade, possui a conversão, o que provavelmente deseja de qualquer maneira.
Jiminion
8
Embora essa pergunta seja mais antiga, eu só queria dizer que essa é uma maneira elegante, documentada como EAFP . Então, provavelmente, a melhor solução para esse tipo de problema.
thiruvenkadam
7
Não retorne o resultado de flutuador (es) ou Nenhum em caso de falha. se você usá-lo como x = float('0.00'); if x: use_float(x);agora possui um bug no seu código. Os valores reais são a razão pela qual essas funções geram uma exceção em vez de retornar Noneem primeiro lugar. Uma solução melhor é evitar a função de utilitário e cercar a chamada para flutuar try catchquando você quiser usá-la.
Ovangle

Respostas:

699

O que não é apenas feio e lento

Eu disputaria os dois.

Um regex ou outro método de análise de string seria mais feio e mais lento.

Não tenho certeza de que algo muito possa ser mais rápido que o acima. Chama a função e retorna. O Try / Catch não apresenta muita sobrecarga, porque a exceção mais comum é detectada sem uma extensa pesquisa de quadros de pilha.

O problema é que qualquer função de conversão numérica tem dois tipos de resultados

  • Um número, se o número for válido
  • Um código de status (por exemplo, via errno) ou exceção para mostrar que nenhum número válido pode ser analisado.

C (como exemplo) corta isso de várias maneiras. O Python apresenta de forma clara e explícita.

Eu acho que seu código para fazer isso é perfeito.

S.Lott
fonte
21
Eu não acho que o código é perfeito (mas eu acho que é muito próximo): é mais usual para colocar única peça que está sendo "testado" no trycláusula, então eu iria colocar o return Trueem uma elsecláusula do try. Uma das razões é que, com o código na pergunta, se eu tivesse que revisá-lo, eu teria que verificar se a segunda declaração da trycláusula não pode gerar um ValueError: concedido, isso não requer muito tempo ou força cerebral, mas por que usar quando não é necessário?
Eric O Lebigot
4
A resposta parece convincente, mas me faz pensar por que não é fornecida pronta para uso ... Vou copiar isso e usá-lo em qualquer caso.
sage
9
Que horrível. E se eu não me importo que o número é apenas que é um número (que é o que me trouxe aqui)? Em vez de uma linha, IsNumeric()eu termino com um try / catch ou outro empacotando um try / catch. Ugh
Básico
6
Não é fornecido 'fora da caixa' porque if is_number(s): x = float(x) else: // failé o mesmo número de linhas de código que o try: x = float(x) catch TypeError: # fail. Essa função de utilitário é uma abstração totalmente desnecessária.
Ovangle
12
Mas abstração é o objetivo das bibliotecas. Ter uma função 'isNumber' (em qualquer idioma) ajuda bastante, pois você pode construí-la diretamente nas instruções if e ter um código muito mais legível e sustentável que depende de blocos try-catch. Além disso, se você precisar usar o código mais de uma vez em mais de uma classe / módulo, você utilizou mais linhas de código do que uma função interna teria.
JamEngulfer
1612

Caso esteja procurando analisar números inteiros (positivos, não assinados) em vez de números flutuantes, você pode usar a isdigit()função para objetos de sequência.

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

Métodos de seqüência de caracteres - isdigit(): Python2 , Python3

Há também algo nas cadeias de caracteres Unicode, que eu não estou muito familiarizado com Unicode - É decimal / decimal

Zoomulator
fonte
232
Isso é um negativo sobre negativos também
intrepion
22
Também falha com exponenciais: '1e3'.isdigit () -> False
ssc
35
Enquanto Number! = Digit, as pessoas que estão procurando maneiras de testar se uma string contém um número inteiro podem se deparar com essa questão, e a abordagem isDigit pode muito bem ser perfeitamente adequada para sua aplicação.
Adam Parkin
8
@ AdamParkin: isdigit()e int()tem opiniões diferentes sobre o que é um número inteiro, por exemplo, para o caractere Unicode u'\u00b9': u'¹'.isdigit()is Truebut int(u'¹')raise ValueError.
JFS
6
+1: isdigit () pode não ser o que o OP estava procurando, mas é exatamente o que eu queria. Pode não ser que essa resposta e método não abranjam todos os tipos de números, mas ainda sejam altamente relevantes, contrariamente aos argumentos sobre sua precisão. Enquanto "Número! = Dígito", o dígito ainda é um subconjunto de números, principalmente números positivos, não negativos e que usam a base 1-10. Além disso, esse método é particularmente útil e breve para os casos em que você deseja inspecionar se uma sequência é um ID numérico ou não, que geralmente se enquadra no subconjunto de números que acabei de descrever.
Justin Johnson
161

TL; DR A melhor solução és.replace('.','',1).isdigit()

Eu fiz alguns benchmarks comparando as diferentes abordagens

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

Se a sequência não for um número, o bloco de exceção será bastante lento. Mais importante, porém, o método try-except é a única abordagem que lida com as notações científicas corretamente.

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

A notação flutuante ".1234" não é suportada por:
- 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__)

A notação científica "1.000000e + 50" não é suportada por:
- is_number_regex
- is_number_repl_isdigit
A notação científica "1e50" não é suportada por:
- is_number_regex
- is_number_repl_isdigit

EDIT: Os resultados de referência

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

onde as seguintes funções foram testadas

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

insira a descrição da imagem aqui

Idok
fonte
15
para bons gráficos +1. Eu vi benchmark e vi gráfico, toda a coisa TL; DR ficou clara e intuitiva.
jcchuks
Concordo com o @JCChuks: o gráfico ajuda muito a obter todo o TL; DR rapidamente. Mas acho que um TL; DR (como: TL; DR : a melhor solução é s.replace('.','',1).isdigit()) deve aparecer no início desta resposta. Em qualquer caso, deve ser o aceito. Obrigado!
Simon C.
10
Este método não manipula números negativos (traços). Eu recomendaria usar apenas o método float, pois ele é menos propenso a erros e funciona sempre.
Urchin
3
O que é importante observar é que, mesmo supondo que não possa haver um traço, o método replace-isdigit é apenas mais rápido para não números (resultado falso), enquanto o método try-except é mais rápido para números (resultado verdadeiro). Se a maior parte da sua entrada é válida, é melhor usar a solução try-except!
Markus von Broady
1
Não funciona em notação exponencial como '1.5e-9'ou em negativos.
EL_DON 17/10/19
68

Há uma exceção que você pode levar em consideração: a string 'NaN'

Se você deseja que is_number retorne FALSE para 'NaN', esse código não funcionará, pois o Python o converterá em sua representação de um número que não é um número (fale sobre questões de identidade):

>>> float('NaN')
nan

Caso contrário, devo agradecer o trecho de código que agora uso extensivamente. :)

G.

gvrocha
fonte
2
Na verdade, NaNpode ser um bom valor para retornar (em vez de False) se o texto passado não for de fato uma representação de um número. Verificá-lo é meio complicado (o floattipo de Python realmente precisa de um método para ele), mas você pode usá-lo em cálculos sem produzir um erro e só precisa verificar o resultado.
Kindall
7
Outra exceção é a string 'inf'. De qualquer infou NaNtambém pode ser prefixado com um +ou -e ainda ser aceito.
agf
4
Se você deseja retornar False para NaN e Inf, altere a linha para x = float (s); return (x == x) e (x - 1! = x). Isso deve retornar verdadeiro para todos os carros alegóricos, exceto Inf e NaN
Ryann
5
x-1 == xé verdadeiro para carros alegóricos grandes menores que inf. No Python 3.2, você pode usar math.isfinitepara testar números que não são NaN nem infinitos, ou verificar ambos math.isnane math.isinfantes disso.
21414 Steve Jobs (
56

que tal agora:

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

que retornará verdadeiro somente se houver um ou não '.' na sequência de dígitos.

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

retornará falso

edit: acabei de ver outro comentário ... adicionar um .replace(badstuff,'',maxnum_badstuff)para outros casos pode ser feito. se você estiver passando sal e não condimentos arbitrários (ref: xkcd # 974 ), tudo ficará bem: P

haxwithaxe
fonte
7
No entanto, isso não explica números negativos.
Michael Barton
5
Ou números com expoentes como 1.234e56(que também podem ser escritos como +1.234E+56e várias outras variantes).
Alfe
re.match(r'^[+-]*(0[xbo])?[0-9A-Fa-f]*\.?[0-9A-Fa-f]*(E[+-]*[0-9A-Fa-f]+)$', 'str')deve fazer um trabalho melhor ao determinar um número (mas não todos, não estou afirmando isso). Eu não recomendo usar isso, muito melhor usar o código original do Questionador.
Baldrickk
se você não gostar desta solução, leia isso antes de fazer uma votação!
aloisdg movendo-se para codidact.com 23/02
cara, essa é a solução mais inteligente que eu já vi neste site !, cara bem feito!
Karam Qusai
41

O que, não só é feio e lento, parece desajeitado.

Pode levar algum tempo para se acostumar, mas esta é a maneira pitônica de fazê-lo. Como já foi apontado, as alternativas são piores. Mas há uma outra vantagem de fazer as coisas dessa maneira: polimorfismo.

A idéia central por trás da digitação do pato é que "se ele anda e fala como um pato, então é um pato". E se você decidir que precisa da subclasse de seqüência de caracteres para poder alterar como determina se algo pode ser convertido em um flutuador? Ou, se você decidir testar completamente outro objeto? Você pode fazer essas coisas sem precisar alterar o código acima.

Outros idiomas resolvem esses problemas usando interfaces. Vou salvar a análise de qual solução é melhor para outro segmento. O ponto, porém, é que o python está decididamente no lado da digitação do pato da equação, e você provavelmente terá que se acostumar com uma sintaxe como essa se planeja fazer muita programação em Python (mas isso não significa você tem que gostar, é claro).

Outra coisa que você pode querer levar em consideração: o Python é muito rápido ao lançar e capturar exceções em comparação com muitas outras linguagens (30x mais rápido que o .Net, por exemplo). Caramba, o próprio idioma lança exceções para comunicar condições normais não-excepcionais do programa (toda vez que você usa um loop for). Portanto, não me preocuparia muito com os aspectos de desempenho desse código até você notar um problema significativo.

Jason Baker
fonte
1
Outro local comum em que o Python usa exceções para funções básicas é o de hasattr()que apenas uma getattr()chamada está envolvida em um try/except. Ainda assim, o tratamento de exceções é mais lento que o controle de fluxo normal, portanto, usá-lo para algo que será verdadeiro na maioria das vezes pode resultar em uma penalidade no desempenho.
Kindall
Parece que se você quer um one-liner, você está SOL
Básico
Também pitônica é a idéia de que "é melhor pedir perdão do que permissão", com relação ao impacto de ter exceções baratas.
heltonbiker
40

Atualizado depois que o Alfe apontou, você não precisa verificar se o flutuador é separado, pois os complexos manipulam os dois:

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

    return True

Anteriormente dito: Em alguns casos raros, você também pode precisar procurar números complexos (por exemplo, 1 + 2i), que não podem ser representados por um float:

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
Matthew Wilcoxson
fonte
14
Discordo. Isso é MUITO improvável no uso normal, e seria melhor você criar uma chamada is_complex_number () quando as estiver usando, em vez de cobrar uma ligação com operação extra para uma chance de 0,0001% de operação incorreta.
Jiminion
3
Você pode retirar float()completamente o material e apenas verificar se a complex()chamada foi bem-sucedida. Tudo analisado por float()pode ser analisado por complex().
Alfe
Esta função retornará os valores de NaNs e Inf do Pandas como valores numéricos.
Fixxxer 29/05
complex('(01989)')retornará (1989+0j). Mas float('(01989)')vai falhar. Então eu acho que usar complexnão é uma boa ideia.
plhn
26

Para intusar isto:

>>> "1221323".isdigit()
True

Mas para floatnós precisamos de alguns truques ;-). Cada número flutuante tem um ponto ...

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

Também para números negativos basta adicionar lstrip():

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

E agora temos uma maneira universal:

>>> '-12.34'.lstrip('-').replace('.','',1).isdigit()
True
>>> '.-234'.lstrip('-').replace('.','',1).isdigit()
False
Sdwdaw
fonte
2
Não lida com coisas como 1.234e56e similares. Além disso, eu ficaria interessado em saber como você descobriria que 99999999999999999999e99999999999999999999não é um número. Tentando analisar, descobre rapidamente.
Alfe
Isso é executado ~ 30% mais rápido que a solução aceita em uma lista de cadeias de 50m e 150% mais rápido em uma lista de cadeias de caracteres de 5k. Z
Zev Averbach
15

Apenas imitar c #

No C #, existem duas funções diferentes que lidam com a análise de valores escalares:

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

float.parse ():

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

Nota: Se você está se perguntando por que alterei a exceção para um TypeError, aqui está a documentação .

float.try_parse ():

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

Nota: Você não deseja retornar o booleano 'False' porque esse ainda é um tipo de valor. Nenhum é melhor porque indica falha. Obviamente, se você quiser algo diferente, poderá alterar o parâmetro de falha para o que quiser.

Para estender o float para incluir o 'parse ()' e 'try_parse ()', você precisará monkeypatch a classe 'float' para adicionar esses métodos.

Se você deseja respeitar as funções preexistentes, o código deve ser algo como:

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

Nota: Eu, pessoalmente, prefiro chamá-lo de Monkey Punching porque parece que estou abusando do idioma quando faço isso, mas YMMV.

Uso:

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

E o grande sábio Pythonas disse à Santa Sé Sharpisus: "Qualquer coisa que você possa fazer, eu posso fazer melhor; eu posso fazer qualquer coisa melhor que você".

Evan Plaice
fonte
Eu tenho codificado principalmente em JS ultimamente e, na verdade, não testei isso, por isso pode haver alguns erros menores. Se você vir algum, sinta-se à vontade para corrigir meus erros.
Evan Plaice
Para adicionar suporte a números complexos, consulte a resposta de @Matthew Wilcoxson. stackoverflow.com/a/3335060/290340 .
Evan Plaice
1
Usar em !vez de notpode ser um erro menor, mas você definitivamente não pode atribuir atributos ao floatinterno no CPython.
BlackJack 28/08
15

Para cadeias de não-números, try: except:é realmente mais lento que expressões regulares. Para cadeias de números válidos, regex é mais lento. Portanto, o método apropriado depende da sua entrada.

Se você achar que está em uma ligação de desempenho, poderá usar um novo módulo de terceiros chamado fastnumbers, que fornece uma função chamada isfloat . Divulgação completa, eu sou o autor. Eu incluí seus resultados nos horários abaixo.


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

Como você pode ver

  • try: except: foi rápido para entrada numérica, mas muito lento para uma entrada inválida
  • regex é muito eficiente quando a entrada é inválida
  • fastnumbers ganha em ambos os casos
SethMMorton
fonte
Estou corrigido: -} simplesmente não parecia estar fazendo isso. Talvez usando nomes como prep_code_basise prep_code_re_methodtivesse evitado meu erro.
Alfe 22/04
Você se importa em explicar como o seu módulo funciona, pelo menos para a isfloatfunção?
Solomon Ucko
@SolomonUcko Aqui está um link para o código-fonte da parte de verificação de cadeia: github.com/SethMMorton/fastnumbers/blob/v1.0.0/src/… . Basicamente, ele percorre cada caractere da sequência em ordem e valida se segue um padrão para um float válido. Se a entrada já é um número, ela apenas usa o rápido PyFloat_Check .
SethMMorton
1
Testado contra as melhores alternativas neste segmento, confirmo que esta solução é de longe a mais rápida. O segundo método mais rápido str(s).strip('-').replace('.','',1).isdigit()é aproximadamente 10 vezes mais lento!
Alexander McFarlane
14

Sei que isso é particularmente antigo, mas gostaria de acrescentar uma resposta que acredito que cubra as informações que faltam na resposta mais votada e que podem ser muito valiosas para quem encontrar isso:

Para cada um dos métodos a seguir, conecte-os a uma contagem, se você precisar de alguma entrada para ser aceita. (Supondo que estamos usando definições vocais de números inteiros em vez de 0 a 255, etc.)

x.isdigit() funciona bem para verificar se x é um número inteiro.

x.replace('-','').isdigit() funciona bem para verificar se x é negativo (primeira posição do check-in)

x.replace('.','').isdigit() funciona bem para verificar se x é um decimal.

x.replace(':','').isdigit() funciona bem para verificar se x é uma proporção.

x.replace('/','',1).isdigit() funciona bem para verificar se x é uma fração.

Aruthawolf
fonte
1
Embora para frações, você provavelmente precise fazer x.replace('/','',1).isdigit()ou datas como 7/7/2017 seriam mal interpretadas como números.
Yuxuan Chen
Para obter os melhores caminhos para a cadeia as condições: stackoverflow.com/q/3411771/5922329
Daniel Braun
13

Esta resposta fornece guia passo a passo com função de exemplos para encontrar a string:

  • Número inteiro positivo
  • Positivo / negativo - inteiro / flutuante
  • Como descartar cadeias de caracteres "NaN" (não um número) enquanto verifica o número?

Verifique se a sequência é um número inteiro positivo

Você pode usar str.isdigit()para verificar se a sequência especificada é um número inteiro positivo .

Resultados da amostra:

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

Verifique se a string é positiva / negativa - inteiro / flutuante

str.isdigit()retorna Falsese a sequência for um número negativo ou um número flutuante. Por exemplo:

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

Se você tambémfloat deseja verificar os números inteiros negativos e , pode escrever uma função personalizada para verificar como:

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

Exemplo de execução:

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

Descartar cadeias "NaN" (não um número) enquanto verifica o número

As funções acima retornarão Truepara a string "NAN" (não é um número) porque, para o Python, é válido float representando que não é um número. Por exemplo:

>>> is_number('NaN')
True

Para verificar se o número é "NaN", você pode usar math.isnan()como:

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

>>> math.isnan(nan_num)
True

Ou, se você não deseja importar uma biblioteca adicional para verificar isso, basta checá-la através da comparação com ela mesma ==. O Python retorna Falsequando o nanfloat é comparado com ele próprio. Por exemplo:

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

Por isso, acima de função is_numberpode ser atualizado para voltar Falsepara"NaN" como:

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

Exemplo de execução:

>>> 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: Cada operação para cada verificação, dependendo do tipo de número, é fornecida com sobrecarga adicional. Escolha a versão da is_numberfunção que atenda às suas necessidades.

Moinuddin Quadri
fonte
12

A conversão para float e a captura de ValueError é provavelmente a maneira mais rápida, pois float () se destina especificamente a isso. Qualquer outra coisa que exija análise de string (regex, etc) provavelmente será mais lenta devido ao fato de não estar ajustado para esta operação. Meus US $ 0,02.

codelogic
fonte
11
Seu "2e-2" de dólares são uma bóia também (um argumento adicional para a utilização de bóia :)
tzot
8
@tzot NUNCA use um float para representar um valor monetário.
Lucas
6
@ Lucas: Eu concordo totalmente com você, embora nunca tenha sugerido o uso de carros alegóricos para representar valores monetários; Eu apenas disse que os valores monetários pode ser representado como carros alegóricos :)
tzot
11

Você pode usar strings Unicode, eles têm um método para fazer exatamente o que você deseja:

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

Ou:

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

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

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

Blackzafiro
fonte
2
para ints não negativos é ok ;-)
andilabs
1
s.isdecimal()verifica se a ssequência é um número inteiro não negativo. s.isnumeric()inclui caracteres que int()rejeitam.
JFS
9

Eu queria ver qual método é mais rápido. No geral, os melhores e mais consistentes resultados foram dados pela check_replacefunção. Os resultados mais rápidos foram dados pela check_exceptionfunção, mas somente se não houver nenhuma exceção acionada - o que significa que o código é o mais eficiente, mas a sobrecarga de gerar uma exceção é bastante grande.

Observe que a verificação de uma conversão bem-sucedida é o único método preciso, por exemplo, isso funciona, check_exceptionmas as outras duas funções de teste retornam False para um float válido:

huge_number = float('1e+100')

Aqui está o código de referência:

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

Aqui estão os resultados com o Python 2.7.10 em um MacBook Pro 13 de 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

Aqui estão os resultados com o Python 3.6.5 em um MacBook Pro 13 de 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

Aqui estão os resultados com o PyPy 2.7.13 em um MacBook Pro 13 de 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
Ron Reiter
fonte
10
Você também deve testar o desempenho para casos inválidos. Nenhuma exceção é gerada com esses números, que é exatamente a parte "lenta".
Ugo Méda 28/01
1
@ UgoMéda Segui o seu conselho a partir de 2013 e fê-lo :)
Ron Reiter
"Observe que a verificação de um elenco bem-sucedido é o único método preciso" <- isso não é verdade. Eu executei seu teste usando o regexp na minha resposta acima, e ele realmente roda mais rápido que o regexp. Vou adicionar os resultados à minha resposta acima.
David Ljung Madison Stellar
Aliás, como um ponto divertido, seu criador de números ruins pode realmente criar alguns números legais, embora isso seja bastante raro. :)
David Ljung Madison Stellar
8

Então, para juntar tudo, verificando Nan, infinito e números complexos (parece que eles são especificados com j, não i, ou seja, 1 + 2j), resulta em:

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
a1an
fonte
Até agora, a melhor resposta. Obrigado
anish 15/01
6

A entrada pode ser a seguinte:

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


Entrada 1-Geral:

A entrada desta função pode ser tudo!

Localiza se a variável especificada é numérica. Sequências numéricas consistem em sinal opcional, qualquer número de dígitos, parte decimal opcional e parte exponencial opcional. Portanto, + 0123.45e6 é um valor numérico válido. Notação hexadecimal (por exemplo, 0xf4c3b00c) e binária (por exemplo, 0b10100111001) não é permitida.

função 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

teste:

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

função is_float

Localiza se a variável fornecida é flutuante. strings flutuantes consistem em sinal opcional, qualquer número de dígitos, ...

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

teste:

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

o que é ast ?


2- Se você tem certeza de que o conteúdo da variável é String :

use o método 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

Entrada numérica 3:

detectar valor int:

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

detectar flutuador:

>>> isinstance("45.1", float)
False
>>> isinstance(45.1, float)
True
Bastian
fonte
o que é " ast"?
4

Eu fiz um teste de velocidade. Vamos dizer que, se a string provavelmente for um número, a estratégia try / except será a mais rápida possível. Se a string provavelmente não for um número e você estiver interessado na verificação de número inteiro , vale a pena fazer algum teste (isdigit mais header '-'). Se você estiver interessado em verificar o número da bóia, use o código try / except sem escape.

FxIII
fonte
4

Eu precisava determinar se uma string era convertida em tipos básicos (float, int, str, bool). Depois de não encontrar nada na internet, criei isso:

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)

Exemplo

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       

Você pode capturar o tipo e usá-lo

s = "6.0"
type_ = str_to_type(s) # float
f = type_(s) 
astrodsg
fonte
3

RyanN sugere

Se você deseja retornar False para NaN e Inf, altere a linha para x = float (s); return (x == x) e (x - 1! = x). Isso deve retornar True para todos os carros alegóricos, exceto Inf e NaN

Mas isso não funciona muito bem, porque para flutuadores suficientemente grandes, x-1 == xretorna verdadeiro. Por exemplo,2.0**54 - 1 == 2.0**54

philh
fonte
3

Eu acho que a sua solução é bom, mas não é uma implementação regexp correta.

Parece haver muito ódio do regexp em relação a essas respostas, que eu acho injustificadas, o regexps pode ser razoavelmente limpo, correto e rápido. Realmente depende do que você está tentando fazer. A questão original era como você pode "verificar se uma string pode ser representada como um número (float)" (conforme o seu título). Presumivelmente, você desejaria usar o valor numérico / flutuante depois de verificar se ele é válido; nesse caso, sua tentativa / exceção faz muito sentido. Mas se, por algum motivo, você apenas deseja validar que uma string é um númeroentão uma regex também funciona bem, mas é difícil de corrigir. Eu acho que a maioria das respostas do regex até agora, por exemplo, não analisa corretamente as strings sem uma parte inteira (como ".7"), que é um float no que diz respeito ao python. E isso é um pouco complicado de verificar em um único regex em que a parte fracionária não é necessária. Eu incluí dois regex para mostrar isso.

Isso levanta a questão interessante sobre o que é um "número". Você inclui "inf", que é válido como flutuador em python? Ou você inclui números que são "números", mas talvez não possam ser representados em python (como números que são maiores que o número máximo de float).

Também há ambigüidades na maneira como você analisa números. Por exemplo, o que dizer de "--20"? Este é um "número"? Essa é uma maneira legal de representar "20"? O Python permitirá que você faça "var = --20" e defina-o como 20 (embora realmente seja porque o trata como uma expressão), mas o float ("- 20") não funciona.

De qualquer forma, sem mais informações, aqui está uma expressão regular que eu acredito que cobre todas as entradas e flutuações conforme o python as analisa .

# 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

Alguns exemplos de valores de teste:

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

A execução do código de benchmarking na resposta do @ ron-reiter mostra que esse regex é realmente mais rápido que o normal e é muito mais rápido no tratamento de valores ruins que a exceção, o que faz algum sentido. Resultados:

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
David Ljung Madison Stellar
fonte
Espero que esteja certo - adoraria ouvir sobre qualquer contra-exemplo. :)
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
xin.chen
fonte
2
Você não considera 1e6representar um número?
Mark Dickinson
1

Aqui está a minha maneira simples de fazer isso. Digamos que eu estou percorrendo algumas strings e quero adicioná-las a uma matriz se elas forem números.

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

Substitua myvar.apppend por qualquer operação que você queira executar com a string, se for um número. A idéia é tentar usar uma operação float () e usar o erro retornado para determinar se a string é ou não um número.


fonte
Você deve mover a parte anexada dessa função para uma instrução else para evitar o acionamento acidental da exceção, caso haja algo errado com a matriz.
DarwinSurvivor
1

Também usei a função que você mencionou, mas logo noto que cadeias de caracteres como "Nan", "Inf" e sua variação são consideradas como número. Então, proponho que você versão melhorada de sua função, que retornará false nesse tipo de entrada e não falhe nas variantes "1e3":

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

Esse código lida com os expoentes, flutuadores e números inteiros, sem o uso de regex.

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

Função auxiliar do usuário:

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

então

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)])
Samantha Atkins
fonte
0

Você pode generalizar a técnica de exceção de uma maneira útil, retornando valores mais úteis que True e False. Por exemplo, essa função coloca aspas em volta das seqüências, mas deixa os números sozinhos. O que é exatamente o que eu precisava para um filtro rápido e sujo para fazer algumas definições de variáveis ​​para 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:]), ')'
Thruston
fonte
0

Eu estava trabalhando em um problema que me levou a esse segmento, como converter uma coleção de dados em seqüências de caracteres e números da maneira mais intuitiva. Depois de ler o código original, percebi que o que eu precisava era diferente de duas maneiras:

1 - Eu queria um resultado inteiro se a sequência representasse um número inteiro

2 - Eu queria que um resultado de número ou string ficasse em uma estrutura de dados

então eu adaptei o código original para produzir esse derivado:

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

Tente isso.

 def is_number(var):
    try:
       if var == int(var):
            return True
    except Exception:
        return False
TheRedstoneLemon
fonte
Falha ao responder comis_number('10')
geotheory
@geotheory, o que você quer dizer com "falha em responder"?
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
Amir Saniyan
fonte