Comment vérifier si une chaîne est un nombre (float)?

1610

Quelle est la meilleure façon de vérifier si une chaîne peut être représentée sous forme de nombre en Python?

La fonction que j'ai actuellement en ce moment est:

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

Ce qui, non seulement est laid et lent, semble maladroit. Cependant, je n'ai pas trouvé de meilleure méthode, car appeler floatla fonction principale est encore pire.

Daniel Goldberg
la source
61
Quel est le problème avec votre solution actuelle? C'est court, rapide et lisible.
Colonel Panic
5
Et vous n'avez pas seulement à retourner Vrai ou Faux. Vous pouvez renvoyer la valeur convenablement modifiée à la place - par exemple, vous pouvez l'utiliser pour mettre des non-nombres entre guillemets.
Thruston
7
Ne serait-il pas préférable de renvoyer le résultat de float (s) dans le cas d'une conversion réussie? Vous avez toujours le contrôle du succès (le résultat est faux) et vous avez réellement la conversion, que vous voudrez probablement de toute façon.
Jiminion
8
Même si cette question est plus ancienne, je voulais juste dire que c'est une manière élégante qui est documentée comme EAFP . Donc probablement la meilleure solution pour ce genre de problème.
thiruvenkadam
7
Ne retournez pas le résultat de float (s) ou None en cas d'échec. si vous l'utilisez alors car x = float('0.00'); if x: use_float(x);vous avez maintenant un bug dans votre code. Des valeurs véridiques sont la raison pour laquelle ces fonctions déclenchent une exception plutôt que de revenir Noneen premier lieu. Une meilleure solution consiste simplement à éviter la fonction utilitaire et à entourer l'appel pour qu'il flotte dans un try catchlorsque vous souhaitez l'utiliser.
ovangle

Réponses:

699

Ce qui est non seulement laid et lent

Je contesterais les deux.

Une expression rationnelle ou une autre méthode d'analyse de chaîne serait plus laide et plus lente.

Je ne suis pas sûr que quoi que ce soit pourrait être plus rapide que ce qui précède. Il appelle la fonction et retourne. Try / Catch n'introduit pas beaucoup de surcharge car l'exception la plus courante est interceptée sans une recherche approfondie des trames de pile.

Le problème est que toute fonction de conversion numérique a deux types de résultats

  • Un numéro, si le numéro est valide
  • Un code d'état (par exemple, via errno) ou une exception pour montrer qu'aucun numéro valide ne peut être analysé.

C (à titre d'exemple) contourne cela de plusieurs façons. Python le présente clairement et explicitement.

Je pense que votre code pour ce faire est parfait.

S.Lott
la source
21
Je ne pense pas que le code soit parfait (mais je pense qu'il est très proche): il est plus habituel de ne mettre que la partie "testée" dans la tryclause, donc je mettrais le return Truedans une elseclause de la try. L'une des raisons est qu'avec le code dans la question, si je devais le réviser, je devrais vérifier que la deuxième instruction de la tryclause ne peut pas déclencher une ValueError: accordée, cela ne nécessite pas trop de temps ou de puissance cérébrale, mais pourquoi en utiliser quand aucun n'est nécessaire?
Eric O Lebigot
4
La réponse semble convaincante, mais je me demande pourquoi elle n'est pas fournie prête à l'emploi ... Je vais la copier et l'utiliser dans tous les cas.
sauge
9
Quelle horreur. Que diriez - vous si je ne me soucie pas ce que le nombre est juste que c'est un certain nombre (qui est ce qui m'a amené ici)? Au lieu d'un 1 ligne, IsNumeric()je me retrouve avec un try / catch ou un autre enveloppant un try / catch. Ugh
Basic
6
Il n'est pas fourni «prêt à l'emploi» car il if is_number(s): x = float(x) else: // fails'agit du même nombre de lignes de code que le try: x = float(x) catch TypeError: # fail. Cette fonction utilitaire est une abstraction totalement inutile.
ovangle
12
Mais l'abstraction est tout l'intérêt des bibliothèques. Avoir une fonction 'isNumber' (dans n'importe quel langage) aide énormément car vous pouvez la construire directement dans des instructions if et avoir un code beaucoup plus lisible et maintenable que de s'appuyer sur des blocs try-catch. De plus, si vous devez utiliser le code plusieurs fois dans plusieurs classes / modules, vous avez alors utilisé plus de lignes de code qu'une fonction intégrée n'en aurait.
JamEngulfer
1612

Si vous recherchez des entiers (positifs, non signés) au lieu de flottants, vous pouvez utiliser la isdigit()fonction pour les objets chaîne.

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

Méthodes de chaîne - isdigit(): Python2 , Python3

Il y a aussi quelque chose sur les chaînes Unicode, que je ne connais pas trop Unicode - est décimal / décimal

Zoomulateur
la source
232
C'est aussi un négatif sur les négatifs
intrépide
22
Échoue également avec les exponentielles: '1e3'.isdigit () -> False
ssc
35
Alors que Number! = Digit, les personnes qui recherchent des moyens de tester si une chaîne contient un entier peuvent très bien tomber sur cette question, et l'approche isDigit peut très bien convenir parfaitement à leur application.
Adam Parkin
8
@AdamParkin: isdigit()et int()ont des opinions différentes sur ce qu'est un entier, par exemple, pour le caractère Unicode u'\u00b9': u'¹'.isdigit()is Truebut int(u'¹')raises ValueError.
jfs
6
+1: isdigit () n'est peut-être pas ce que recherchait l'OP, mais c'est exactement ce que je voulais. Il se peut que cette réponse et cette méthode ne couvrent pas tous les types de nombres, mais elles sont toujours très pertinentes, contrairement aux arguments concernant leur exactitude. Alors que "Number! = Digit", digit est toujours un sous-ensemble de nombres, en particulier les nombres positifs, non négatifs et utilisant la base 1-10. De plus, cette méthode est particulièrement utile et brève pour les cas où vous souhaitez vérifier si une chaîne est un ID numérique ou non, ce qui tombe souvent dans le sous-ensemble de nombres que je viens de décrire.
Justin Johnson
161

TL; DR La meilleure solution ests.replace('.','',1).isdigit()

J'ai fait quelques repères comparant les différentes approches

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

Si la chaîne n'est pas un nombre, le bloc except est assez lent. Mais plus important encore, la méthode try-except est la seule approche qui gère correctement les notations scientifiques.

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

La notation flottante ".1234" n'est pas prise en charge par:
- 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__)

La notation scientifique "1.000000e + 50" n'est pas prise en charge par:
- is_number_regex
- is_number_repl_isdigit
La notation scientifique "1e50" n'est pas prise en charge par:
- is_number_regex
- is_number_repl_isdigit

EDIT: Les résultats de référence

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

où les fonctions suivantes ont été testées

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

entrez la description de l'image ici

Idok
la source
15
pour de jolis graphiques +1. J'ai vu le benchmark et vu le graphique, tout le TL; DR est devenu clair et intuitif.
jcchuks
Je suis d'accord avec @JCChuks: le graphique aide beaucoup à obtenir rapidement tous les TL; DR. Mais je pense qu'un TL; DR (comme: TL; DR : la meilleure solution est s.replace('.','',1).isdigit()) devrait apparaître au début de cette réponse. En tout cas, ce doit être celui qui est accepté. Merci!
Simon C.
10
Cette méthode ne gère pas les nombres négatifs (tirets). Je recommanderais d'utiliser simplement la méthode float car elle est moins sujette aux erreurs et fonctionnera à chaque fois.
Urchin
3
Ce qui est important à noter, c'est que même dans l'hypothèse où il ne peut pas y avoir de tiret, la méthode replace-isdigit n'est plus rapide que pour les non-nombres (résultat faux), tandis que la méthode try-except est plus rapide pour les nombres (résultat vrai). Si la plupart de vos entrées sont valides, vous feriez mieux avec la solution try-except!
Markus von Broady
1
Ne fonctionne pas sur la notation exponentielle comme '1.5e-9'ou sur les négatifs.
EL_DON
68

Il y a une exception que vous voudrez peut-être prendre en compte: la chaîne 'NaN'

Si vous voulez que is_number renvoie FALSE pour 'NaN', ce code ne fonctionnera pas car Python le convertit en sa représentation d'un nombre qui n'est pas un nombre (parler de problèmes d'identité):

>>> float('NaN')
nan

Sinon, je devrais en fait vous remercier pour le morceau de code que j'utilise maintenant largement. :)

G.

gvrocha
la source
2
En fait, cela NaNpourrait être une bonne valeur à renvoyer (plutôt que False) si le texte passé n'est pas en fait une représentation d'un nombre. Le vérifier est une sorte de douleur (le floattype de Python a vraiment besoin d'une méthode pour cela) mais vous pouvez l'utiliser dans les calculs sans produire d'erreur, et il suffit de vérifier le résultat.
kindall
7
Une autre exception est la chaîne 'inf'. L'un infou l' autre NaNpeut également être précédé d'un +ou -et être toujours accepté.
agf
4
Si vous voulez retourner False pour un NaN et Inf, changez la ligne en x = float (s); retourner (x == x) et (x - 1! = x). Cela devrait renvoyer True pour tous les flottants sauf Inf et NaN
RyanN
5
x-1 == xest vrai pour les grands flotteurs plus petits que inf. À partir de Python 3.2, vous pouvez utiliser math.isfinitepour tester des nombres qui ne sont ni NaN ni infinis, ou vérifier les deux math.isnanet math.isinfavant cela.
Steve Jessop
56

que dis-tu de ça:

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

qui ne retournera vrai que s'il y en a un ou pas '.' dans la chaîne de chiffres.

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

retournera faux

edit: vient de voir un autre commentaire ... l'ajout d'un .replace(badstuff,'',maxnum_badstuff)pour d'autres cas peut être fait. si vous passez du sel et non des condiments arbitraires (ref: xkcd # 974 ) cela fera l'affaire: P

haxwithaxe
la source
7
Cela ne tient cependant pas compte des nombres négatifs.
Michael Barton
5
Ou des nombres avec des exposants comme 1.234e56(qui pourraient également être écrits comme +1.234E+56et plusieurs autres variantes).
Alfe
re.match(r'^[+-]*(0[xbo])?[0-9A-Fa-f]*\.?[0-9A-Fa-f]*(E[+-]*[0-9A-Fa-f]+)$', 'str')devrait faire un meilleur travail pour déterminer un nombre (mais pas tous, je ne le prétends pas). Je ne recommande pas d'utiliser cela, bien mieux d'utiliser le code original de l'interrogateur.
Baldrickk
si vous n'aimez pas cette solution, lisez ceci avant de voter!
aloisdg passe à codidact.com le
l'homme c'est la solution la plus intelligente que j'ai jamais vue sur ce site!
Karam Qusai
41

Ce qui, non seulement est laid et lent, semble maladroit.

Cela peut prendre un certain temps pour s'y habituer, mais c'est la façon pythonique de le faire. Comme cela a déjà été souligné, les alternatives sont pires. Mais il y a un autre avantage à faire les choses de cette façon: le polymorphisme.

L'idée centrale derrière la frappe de canard est que "s'il marche et parle comme un canard, alors c'est un canard". Que se passe-t-il si vous décidez que vous devez sous-classer la chaîne afin de pouvoir changer la façon dont vous déterminez si quelque chose peut être converti en flottant? Ou si vous décidez de tester un autre objet entièrement? Vous pouvez faire ces choses sans avoir à changer le code ci-dessus.

D'autres langues résolvent ces problèmes en utilisant des interfaces. Je vais enregistrer l'analyse de la meilleure solution pour un autre thread. Le point, cependant, est que python est décidément du côté du typage du canard de l'équation, et vous devrez probablement vous habituer à une syntaxe comme celle-ci si vous prévoyez de faire beaucoup de programmation en Python (mais cela ne signifie pas vous devez l'aimer bien sûr).

Une autre chose que vous voudrez peut-être prendre en considération: Python lance et capture des exceptions assez rapidement par rapport à beaucoup d'autres langages (30 fois plus rapide que .Net par exemple). Heck, le langage lui-même lève même des exceptions pour communiquer des conditions de programme normales non exceptionnelles (chaque fois que vous utilisez une boucle for). Ainsi, je ne m'inquiéterais pas trop des aspects de performance de ce code jusqu'à ce que vous remarquiez un problème important.

Jason Baker
la source
1
Un autre endroit commun où Python utilise des exceptions pour les fonctions de base est dans hasattr()lequel est juste un getattr()appel enveloppé dans un try/except. Pourtant, la gestion des exceptions est plus lente que le contrôle de flux normal, donc l'utiliser pour quelque chose qui va être vrai la plupart du temps peut entraîner une baisse des performances.
kindall
Il semble que si vous voulez un monoplace, vous êtes SOL
Basic
Pythonique est également l'idée qu'il est "préférable de demander pardon que permission", concernant l'impact d'avoir des exceptions bon marché.
heltonbiker
40

Mis à jour après qu'Alfe ait souligné que vous n'avez pas besoin de vérifier séparément le flotteur car les poignées complexes gèrent les deux:

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

    return True

Dit précédemment: Dans certains cas rares, vous devrez peut-être également vérifier les nombres complexes (par exemple 1 + 2i), qui ne peuvent pas être représentés par un flottant:

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
la source
14
Je ne suis pas d'accord. C'est TRÈS improbable en utilisation normale, et vous feriez mieux de construire un appel is_complex_number () lorsque vous les utilisez, plutôt que de surcharger un appel avec une opération supplémentaire pour une probabilité de 0,0001% de mauvaise opération.
Jiminion
3
Vous pouvez float()supprimer complètement le contenu et vérifier que l' complex()appel a réussi. Tout analysé par float()peut être analysé par complex().
Alfe
Cette fonction renverra les valeurs NaN et Inf de Pandas sous forme de valeurs numériques.
fixxxer
complex('(01989)')reviendra (1989+0j). Mais float('(01989)')échouera. Je pense donc que l'utilisation complexn'est pas une bonne idée.
plhn
26

Pour intutiliser ceci:

>>> "1221323".isdigit()
True

Mais pour floatnous avons besoin de quelques astuces ;-). Chaque nombre flottant a un point ...

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

Pour les nombres négatifs, ajoutez simplement lstrip():

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

Et maintenant, nous obtenons un moyen universel:

>>> '-12.34'.lstrip('-').replace('.','',1).isdigit()
True
>>> '.-234'.lstrip('-').replace('.','',1).isdigit()
False
Sdwdaw
la source
2
Ne gère pas les choses comme 1.234e56et similaires. De plus, je serais intéressé de savoir comment vous découvririez que ce 99999999999999999999e99999999999999999999n'est pas un nombre. Essayer de l'analyser le découvre rapidement.
Alfe
Cela s'exécute ~ 30% plus rapidement que la solution acceptée sur une liste de chaînes de 50 m et 150% plus rapide sur une liste de chaînes de 5k. 👏
Zev Averbach
15

Just Mimic C #

En C #, il existe deux fonctions différentes qui gèrent l'analyse des valeurs scalaires:

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

float.parse ():

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

Remarque: Si vous vous demandez pourquoi j'ai changé l'exception en TypeError, voici la documentation .

float.try_parse ():

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

Remarque: vous ne voulez pas renvoyer le booléen 'False' car il s'agit toujours d'un type de valeur. Aucune n'est meilleure car elle indique un échec. Bien sûr, si vous voulez quelque chose de différent, vous pouvez changer le paramètre d'échec en ce que vous voulez.

Pour étendre float pour inclure les 'parse ()' et 'try_parse ()', vous devrez monkeypatch la classe 'float' pour ajouter ces méthodes.

Si vous voulez respecter les fonctions préexistantes, le code devrait ressembler à ceci:

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

SideNote: Personnellement, je préfère l'appeler Monkey Punching parce que j'ai l'impression d'abuser de la langue quand je fais ça, mais YMMV.

Usage:

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

Et le grand Sage Pythonas a dit au Saint-Siège Sharpisus: "Tout ce que vous pouvez faire, je peux le faire mieux; je peux faire quelque chose de mieux que vous."

Plie d'Evan
la source
J'ai codé principalement dans JS ces derniers temps et je n'ai pas réellement testé cela, il peut donc y avoir des erreurs mineures. Si vous en voyez, n'hésitez pas à corriger mes erreurs.
Evan Plaice
Pour ajouter la prise en charge des nombres complexes, consultez la réponse de @Matthew Wilcoxson. stackoverflow.com/a/3335060/290340 .
Evan Plaice
1
L'utilisation de !au lieu de notpeut être une erreur mineure, mais vous ne pouvez certainement pas attribuer d'attributs au floatCPython intégré .
BlackJack
15

Pour les chaînes de non-nombres, try: except:est en fait plus lent que les expressions régulières. Pour les chaînes de nombres valides, l'expression régulière est plus lente. Ainsi, la méthode appropriée dépend de votre saisie.

Si vous constatez que vous êtes dans une liaison de performances, vous pouvez utiliser un nouveau module tiers appelé fastnumbers qui fournit une fonction appelée isfloat . Divulgation complète, je suis l'auteur. J'ai inclus ses résultats dans les horaires ci-dessous.


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

Comme vous pouvez le voir

  • try: except: était rapide pour l'entrée numérique mais très lent pour une entrée invalide
  • regex est très efficace lorsque l'entrée n'est pas valide
  • fastnumbers gagne dans les deux cas
SethMMorton
la source
Je suis corrigé: -} cela ne ressemblait tout simplement pas à cela. Peut-être que l'utilisation de noms comme prep_code_basiset prep_code_re_methodaurait empêché mon erreur.
Alfe
Cela vous dérange d'expliquer comment fonctionne votre module, au moins pour la isfloatfonction?
Solomon Ucko
@SolomonUcko Voici un lien vers le code source de la partie de vérification des chaînes: github.com/SethMMorton/fastnumbers/blob/v1.0.0/src/… . Fondamentalement, il parcourt chaque caractère de la chaîne dans l'ordre et valide qu'il suit un modèle pour un flottant valide. Si l'entrée est déjà un nombre, elle utilise simplement le PyFloat_Check rapide .
SethMMorton
1
Testé par rapport aux meilleures alternatives dans ce fil, je confirme que cette solution est de loin la plus rapide. La deuxième méthode la plus rapide str(s).strip('-').replace('.','',1).isdigit()est environ 10 fois plus lente!
Alexander McFarlane
14

Je sais que cela est particulièrement ancien, mais j'ajouterais une réponse qui, je crois, couvre les informations manquantes de la réponse la plus votée qui pourraient être très précieuses pour tous ceux qui trouveraient ceci:

Pour chacune des méthodes suivantes, connectez-les avec un nombre si vous avez besoin d'une entrée pour être acceptée. (En supposant que nous utilisons des définitions vocales d'entiers plutôt que de 0 à 255, etc.)

x.isdigit() fonctionne bien pour vérifier si x est un entier.

x.replace('-','').isdigit() fonctionne bien pour vérifier si x est négatif. (Check - in first position)

x.replace('.','').isdigit() fonctionne bien pour vérifier si x est un nombre décimal.

x.replace(':','').isdigit() fonctionne bien pour vérifier si x est un rapport.

x.replace('/','',1).isdigit() fonctionne bien pour vérifier si x est une fraction.

Aruthawolf
la source
1
Bien que pour les fractions, vous deviez probablement le faire, x.replace('/','',1).isdigit()sinon les dates telles que 4/7/2017 seraient mal interprétées comme des nombres.
Yuxuan Chen
Pour les meilleures façons d'enchaîner les conditions: stackoverflow.com/q/3411771/5922329
Daniel Braun
13

Cette réponse fournit un guide étape par étape ayant une fonction avec des exemples pour trouver la chaîne:

  • Entier positif
  • Positif / négatif - entier / flottant
  • Comment supprimer les chaînes "NaN" (pas un nombre) tout en vérifiant le nombre?

Vérifiez si la chaîne est un entier positif

Vous pouvez utiliser str.isdigit()pour vérifier si une chaîne donnée est un entier positif .

Exemples de résultats:

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

Vérifiez que la chaîne est positive / négative - entier / flottant

str.isdigit()renvoie Falsesi la chaîne est un nombre négatif ou un nombre flottant. Par exemple:

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

Si vous souhaitez également vérifier les entiers négatifs etfloat , vous pouvez écrire une fonction personnalisée pour la vérifier comme suit:

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

Exemple d'exécution:

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

Jeter les chaînes "NaN" (pas un nombre) tout en vérifiant le nombre

Les fonctions ci-dessus Truerenverront la chaîne "NAN" (pas un nombre) car pour Python, c'est un flottant valide qui représente que ce n'est pas un nombre. Par exemple:

>>> is_number('NaN')
True

Afin de vérifier si le nombre est "NaN", vous pouvez utiliser math.isnan()comme:

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

>>> math.isnan(nan_num)
True

Ou si vous ne voulez pas importer de bibliothèque supplémentaire pour vérifier cela, vous pouvez simplement la vérifier en la comparant avec elle-même en utilisant ==. Python revient Falselorsque nanfloat est comparé à lui-même. Par exemple:

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

Par conséquent, la fonctionis_numberFalse"NaN" ci-dessus peut être mise à jour pour revenir en tant que:

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

Exemple d'exécution:

>>> 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: Chaque opération pour chaque vérification en fonction du type de numéro s'accompagne de frais généraux supplémentaires. Choisissez la version de is_numberfonction qui correspond à vos besoins.

Moinuddin Quadri
la source
12

Caster pour flotter et attraper ValueError est probablement le moyen le plus rapide, car float () est spécifiquement destiné à cela. Tout autre élément nécessitant une analyse de chaîne (regex, etc.) sera probablement plus lent car il n'est pas réglé pour cette opération. Mon 0,02 $.

codelogic
la source
11
Vos dollars "2e-2" sont aussi un flotteur (un argument supplémentaire pour utiliser le flotteur :)
tzot
8
@tzot N'utilisez JAMAIS un flottant pour représenter une valeur monétaire.
Luke
6
@Luke: Je suis totalement d'accord avec vous, même si je n'ai jamais suggéré d'utiliser des flottants pour représenter des valeurs monétaires; Je viens de dire que les valeurs monétaires peuvent être représentées comme des flottants :)
tzot
11

Vous pouvez utiliser des chaînes Unicode, elles ont une méthode pour faire exactement ce que vous voulez:

>>> 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
la source
2
pour les entrées non négatives, c'est ok ;-)
andilabs
1
s.isdecimal()vérifie si la schaîne est un entier non négatif. s.isnumeric()inclut des caractères qui int()rejettent.
jfs
9

Je voulais voir quelle méthode est la plus rapide. Dans l'ensemble, les résultats les meilleurs et les plus cohérents ont été donnés par la check_replacefonction. Les résultats les plus rapides ont été donnés par la check_exceptionfonction, mais uniquement si aucune exception n'a été déclenchée - ce qui signifie que son code est le plus efficace, mais la surcharge de lever une exception est assez grande.

Veuillez noter que la vérification d'une conversion réussie est la seule méthode qui est précise, par exemple, cela fonctionne avec check_exceptionmais les deux autres fonctions de test renverront False pour un flottant valide:

huge_number = float('1e+100')

Voici le code de référence:

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

Voici les résultats avec Python 2.7.10 sur un MacBook Pro 13 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

Voici les résultats avec Python 3.6.5 sur un MacBook Pro 13 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

Voici les résultats avec PyPy 2.7.13 sur un MacBook Pro 13 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
la source
10
Vous devez également tester les performances pour les cas non valides. Aucune exception n'est levée avec ces chiffres, qui est exactement la partie "lente".
Ugo Méda
1
@ UgoMéda j'ai suivi vos conseils de 2013 et l'ai fait :)
Ron Reiter
"Veuillez noter que la vérification d'une distribution réussie est la seule méthode exacte" <- ce n'est pas vrai en réalité. J'ai exécuté votre test en utilisant l'expression rationnelle dans ma réponse ci-dessus, et il fonctionne en fait plus rapidement que l'expression rationnelle. J'ajouterai les résultats à ma réponse ci-dessus.
David Ljung Madison Stellar
Soit dit en passant, comme point amusant, votre créateur de mauvais numéros peut en fait créer des numéros légaux, bien que ce soit assez rare. :)
David Ljung Madison Stellar
8

Donc, pour mettre tout cela ensemble, en vérifiant les nombres Nan, infini et complexes (il semblerait qu'ils soient spécifiés avec j, pas i, c'est-à-dire 1 + 2j), il en résulte:

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
la source
Jusqu'à présent, la meilleure réponse. Merci
anish
6

L'entrée peut être la suivante:

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


1-Entrée générale:

L'entrée de cette fonction peut être tout!

Recherche si la variable donnée est numérique. Les chaînes numériques se composent d'un signe facultatif, d'un nombre quelconque de chiffres, d'une partie décimale facultative et d'une partie exponentielle facultative. Ainsi, + 0123.45e6 est une valeur numérique valide. La notation hexadécimale (par exemple 0xf4c3b00c) et binaire (par exemple 0b10100111001) n'est pas autorisée.

Fonction 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

tester:

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

Fonction is_float

Recherche si la variable donnée est flottante. les chaînes flottantes se composent d'un signe facultatif, d'un nombre quelconque de chiffres, ...

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

tester:

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

ce qui est ast ?


2- Si vous êtes sûr que le contenu de la variable est String :

utiliser la méthode 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 entrées numériques:

détecter la valeur int:

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

détecter flotteur:

>>> isinstance("45.1", float)
False
>>> isinstance(45.1, float)
True
Bastian
la source
qu'est-ce que " ast"?
4

J'ai fait un test de vitesse. Disons que si la chaîne est susceptible d'être un nombre, la stratégie try / except est la plus rapide possible.Si la chaîne n'est pas susceptible d'être un nombre et que vous êtes intéressé par le contrôle entier , cela vaut la peine de faire un test (isdigit plus en-tête «-»). Si vous souhaitez vérifier le nombre flottant, vous devez utiliser le code try / except sans échappement.

FxIII
la source
4

J'avais besoin de déterminer si une chaîne était convertie en types de base (float, int, str, bool). Après n'avoir rien trouvé sur Internet, j'ai créé ceci:

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)

Exemple

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       

Vous pouvez capturer le type et l'utiliser

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

RyanN suggère

Si vous voulez retourner False pour un NaN et Inf, changez la ligne en x = float (s); retourner (x == x) et (x - 1! = x). Cela devrait retourner True pour tous les flottants sauf Inf et NaN

Mais cela ne fonctionne pas tout à fait, car pour des flotteurs suffisamment grands, x-1 == xrenvoie true. Par exemple,2.0**54 - 1 == 2.0**54

philh
la source
3

Je pense que votre solution est très bien, mais il y a une implémentation regexp correcte.

Il semble y avoir beaucoup de haine d'expression régulière envers ces réponses, ce qui, à mon avis, n'est pas justifié, les expressions régulières peuvent être raisonnablement propres, correctes et rapides. Cela dépend vraiment de ce que vous essayez de faire. La question initiale était de savoir comment "vérifier si une chaîne peut être représentée sous la forme d'un nombre (float)" (selon votre titre). Vraisemblablement, vous voudrez utiliser la valeur numérique / flottante une fois que vous avez vérifié qu'elle est valide, auquel cas votre essai / sauf a beaucoup de sens. Mais si, pour une raison quelconque, vous voulez simplement valider qu'une chaîne est un nombrealors une expression régulière fonctionne également bien, mais il est difficile de se corriger. Je pense que la plupart des réponses d'expressions régulières jusqu'à présent, par exemple, n'analysent pas correctement les chaînes sans une partie entière (comme ".7") qui est un flottant en ce qui concerne python. Et c'est un peu difficile à vérifier dans une seule expression régulière où la partie fractionnaire n'est pas requise. J'ai inclus deux regex pour le montrer.

Cela soulève la question intéressante de savoir ce qu'est un «nombre». Incluez-vous "inf" qui est valide comme flottant en python? Ou incluez-vous des nombres qui sont des "nombres" mais qui ne peuvent peut-être pas être représentés en python (tels que des nombres plus grands que le flottant max).

Il y a aussi des ambiguïtés dans la façon dont vous analysez les nombres. Par exemple, qu'en est-il de "--20"? Est-ce un "nombre"? Est-ce une manière légale de représenter "20"? Python vous permettra de faire "var = --20" et de le mettre à 20 (bien que ce soit vraiment parce qu'il le traite comme une expression), mais float ("- 20") ne fonctionne pas.

Quoi qu'il en soit, sans plus d'informations, voici une expression régulière qui, je crois, couvre tous les entiers et flotte au fur et à mesure que python les analyse .

# 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

Quelques exemples de valeurs de test:

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

L'exécution du code d'analyse comparative dans la réponse de @ ron-reiter montre que cette expression régulière est en fait plus rapide que l'expression régulière et est beaucoup plus rapide à gérer les mauvaises valeurs que l'exception, ce qui est logique. Résultats:

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
la source
J'espère que c'est vrai - j'aimerais entendre des contre-exemples. :)
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
la source
2
Ne pensez-vous pas 1e6représenter un nombre?
Mark Dickinson
1

Voici ma façon simple de le faire. Disons que je fais une boucle sur certaines chaînes et que je veux les ajouter à un tableau si elles se révèlent être des nombres.

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

Remplacez myvar.apppend par l'opération que vous souhaitez effectuer avec la chaîne s'il s'avère qu'il s'agit d'un nombre. L'idée est d'essayer d'utiliser une opération float () et d'utiliser l'erreur retournée pour déterminer si la chaîne est ou non un nombre.


la source
Vous devez déplacer la partie append de cette fonction dans une instruction else pour éviter de déclencher accidentellement l'exception en cas de problème avec le tableau.
DarwinSurvivor
1

J'ai également utilisé la fonction que vous avez mentionnée, mais bientôt je remarque que les chaînes comme "Nan", "Inf" et sa variation sont considérées comme des nombres. Je vous propose donc une version améliorée de votre fonction, qui retournera false sur ce type d'entrée et ne manquera pas les 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
la source
1

Ce code gère les exposants, les flottants et les entiers, sans utiliser d'expression régulière.

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

Fonction d'assistance utilisateur:

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

puis

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
la source
0

Vous pouvez généraliser la technique d'exception de manière utile en renvoyant des valeurs plus utiles que True et False. Par exemple, cette fonction met des guillemets autour des chaînes mais laisse les nombres seuls. C'est exactement ce dont j'avais besoin pour un filtre rapide et sale pour faire des définitions de variables pour 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
la source
0

Je travaillais sur un problème qui m'a conduit à ce fil, à savoir comment convertir une collection de données en chaînes et en nombres de la manière la plus intuitive. J'ai réalisé après avoir lu le code original que ce dont j'avais besoin était différent de deux manières:

1 - Je voulais un résultat entier si la chaîne représentait un entier

2 - Je voulais qu'un nombre ou un résultat de chaîne colle dans une structure de données

j'ai donc adapté le code original pour produire ce dérivé:

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

Essaye ça.

 def is_number(var):
    try:
       if var == int(var):
            return True
    except Exception:
        return False
TheRedstoneLemon
la source
is_number('10')
Ne
@geotheory, que voulez-vous dire par "ne répond pas"?
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
la source