web-dev-qa-db-fra.com

Comment puis-je vérifier si une chaîne représente un int, sans utiliser try/except?

Est-il possible de dire si string représente un entier (par exemple, '3', '-17' mais pas '3.14' ou 'asfasfas') sans utiliser un mécanisme try/except?

is_int('3.14') = False
is_int('-7')   = True
336
Adam Matan

Si vous êtes vraiment ennuyé d'utiliser try/excepts partout, écrivez simplement une fonction d'assistance:

def RepresentsInt(s):
    try: 
        int(s)
        return True
    except ValueError:
        return False

>>> print RepresentsInt("+123")
True
>>> print RepresentsInt("10.0")
False

Il y aura beaucoup plus de code pour couvrir exactement toutes les chaînes que Python considère comme des entiers. Je dis juste être Pythonic sur celui-ci.

316
Triptych

avec des entiers positifs, vous pouvez utiliser .isdigit :

>>> '16'.isdigit()
True

cela ne fonctionne pas avec les entiers négatifs cependant. supposons que vous puissiez essayer ce qui suit:

>>> s = '-17'
>>> s.startswith('-') and s[1:].isdigit()
True

cela ne fonctionnera pas avec le format '16.0', qui est semblable au casting int dans ce sens.

modifier:

def check_int(s):
    if s[0] in ('-', '+'):
        return s[1:].isdigit()
    return s.isdigit()
567
SilentGhost

Vous savez, j'ai trouvé (et j'ai testé cela maintes et maintes) qu'essayer/sauf ne fonctionne pas très bien, pour une raison quelconque. J'essaie souvent plusieurs façons de faire les choses, et je ne pense pas avoir jamais trouvé une méthode qui utilise try/except pour exécuter le meilleur de celles testées. En fait, il me semble que ces méthodes sont généralement proches de la pire, sinon le pire. Pas dans tous les cas, mais dans beaucoup de cas. Je sais que beaucoup de gens disent que c'est la méthode "Pythonic", mais c'est un domaine dans lequel je me sépare d'eux. Pour moi, ce n'est ni très performant ni très élégant, alors j'ai tendance à ne l'utiliser que pour le recouvrement des erreurs et la génération de rapports.

J'allais reprocher à PHP, Perl, Ruby, C et même à Shell d'avoir des fonctions simples pour tester une chaîne de caractères sur un entier, mais la diligence requise pour vérifier ces hypothèses m'a fait craquer! Apparemment, ce manque est une maladie commune. 

Voici un rapide et sale montage du post de Bruno:

import sys, time, re

g_intRegex = re.compile(r"^([+-]?[1-9]\d*|0)$")

testvals = [
    # integers
    0, 1, -1, 1.0, -1.0,
    '0', '0.','0.0', '1', '-1', '+1', '1.0', '-1.0', '+1.0', '06',
    # non-integers
    'abc 123',
    1.1, -1.1, '1.1', '-1.1', '+1.1',
    '1.1.1', '1.1.0', '1.0.1', '1.0.0',
    '1.0.', '1..0', '1..',
    '0.0.', '0..0', '0..',
    'one', object(), (1,2,3), [1,2,3], {'one':'two'},
    # with spaces
    ' 0 ', ' 0.', ' .0','.01 '
]

def isInt_try(v):
    try:     i = int(v)
    except:  return False
    return True

def isInt_str(v):
    v = str(v).strip()
    return v=='0' or (v if v.find('..') > -1 else v.lstrip('-+').rstrip('0').rstrip('.')).isdigit()

def isInt_re(v):
    import re
    if not hasattr(isInt_re, 'intRegex'):
        isInt_re.intRegex = re.compile(r"^([+-]?[1-9]\d*|0)$")
    return isInt_re.intRegex.match(str(v).strip()) is not None

def isInt_re2(v):
    return g_intRegex.match(str(v).strip()) is not None

def check_int(s):
    s = str(s)
    if s[0] in ('-', '+'):
        return s[1:].isdigit()
    return s.isdigit()    


def timeFunc(func, times):
    t1 = time.time()
    for n in range(times):
        for v in testvals: 
            r = func(v)
    t2 = time.time()
    return t2 - t1

def testFuncs(funcs):
    for func in funcs:
        sys.stdout.write( "\t%s\t|" % func.__name__)
    print()
    for v in testvals:
        if type(v) == type(''):
            sys.stdout.write("'%s'" % v)
        else:
            sys.stdout.write("%s" % str(v))
        for func in funcs:
            sys.stdout.write( "\t\t%s\t|" % func(v))
        sys.stdout.write("\r\n") 

if __== '__main__':
    print()
    print("tests..")
    testFuncs((isInt_try, isInt_str, isInt_re, isInt_re2, check_int))
    print()

    print("timings..")
    print("isInt_try:   %6.4f" % timeFunc(isInt_try, 10000))
    print("isInt_str:   %6.4f" % timeFunc(isInt_str, 10000)) 
    print("isInt_re:    %6.4f" % timeFunc(isInt_re, 10000))
    print("isInt_re2:   %6.4f" % timeFunc(isInt_re2, 10000))
    print("check_int:   %6.4f" % timeFunc(check_int, 10000))

Voici les résultats de la comparaison des performances:

timings..
isInt_try:   0.6426
isInt_str:   0.7382
isInt_re:    1.1156
isInt_re2:   0.5344
check_int:   0.3452

Une méthode C pourrait numériser une fois, et être fait. Je pense qu'une méthode en C qui scanne la chaîne une fois pour toutes serait la bonne chose à faire. 

MODIFIER: 

J'ai mis à jour le code ci-dessus pour qu'il fonctionne dans Python 3.5 et pour inclure la fonction check_int de la réponse la plus votée à ce jour, et pour utiliser la regex la plus populaire que je puisse trouver pour tester integer-hood. Cette expression rationnelle rejette des chaînes telles que 'abc 123'. J'ai ajouté 'abc 123' comme valeur de test.

Il est très intéressant pour moi de noter, à ce stade, AUCUNE des fonctions testées, y compris la méthode try, la fonction check_int très répandue et la regex la plus populaire pour le test de integer-hood, renvoie les réponses correctes pour tous les paramètres. Valeurs de test (bien, selon ce que vous pensez être les bonnes réponses; voir les résultats du test ci-dessous).

La fonction int () intégrée tronque de manière silencieuse la partie décimale d'un nombre à virgule flottante et renvoie la partie entière avant le point décimal, à moins que le nombre à virgule flottante ne soit d'abord converti en chaîne.

La fonction check_int () renvoie false pour des valeurs telles que 0.0 et 1.0 (qui sont techniquement des entiers) et renvoie true pour des valeurs telles que '06'.

Voici les résultats du test actuel (Python 3.5):

                  isInt_try |       isInt_str       |       isInt_re        |       isInt_re2       |   check_int   |
    0               True    |               True    |               True    |               True    |       True    |
    1               True    |               True    |               True    |               True    |       True    |
    -1              True    |               True    |               True    |               True    |       True    |
    1.0             True    |               True    |               False   |               False   |       False   |
    -1.0            True    |               True    |               False   |               False   |       False   |
    '0'             True    |               True    |               True    |               True    |       True    |
    '0.'            False   |               True    |               False   |               False   |       False   |
    '0.0'           False   |               True    |               False   |               False   |       False   |
    '1'             True    |               True    |               True    |               True    |       True    |
    '-1'            True    |               True    |               True    |               True    |       True    |
    '+1'            True    |               True    |               True    |               True    |       True    |
    '1.0'           False   |               True    |               False   |               False   |       False   |
    '-1.0'          False   |               True    |               False   |               False   |       False   |
    '+1.0'          False   |               True    |               False   |               False   |       False   |
    '06'            True    |               True    |               False   |               False   |       True    |
    'abc 123'       False   |               False   |               False   |               False   |       False   |
    1.1             True    |               False   |               False   |               False   |       False   |
    -1.1            True    |               False   |               False   |               False   |       False   |
    '1.1'           False   |               False   |               False   |               False   |       False   |
    '-1.1'          False   |               False   |               False   |               False   |       False   |
    '+1.1'          False   |               False   |               False   |               False   |       False   |
    '1.1.1'         False   |               False   |               False   |               False   |       False   |
    '1.1.0'         False   |               False   |               False   |               False   |       False   |
    '1.0.1'         False   |               False   |               False   |               False   |       False   |
    '1.0.0'         False   |               False   |               False   |               False   |       False   |
    '1.0.'          False   |               False   |               False   |               False   |       False   |
    '1..0'          False   |               False   |               False   |               False   |       False   |
    '1..'           False   |               False   |               False   |               False   |       False   |
    '0.0.'          False   |               False   |               False   |               False   |       False   |
    '0..0'          False   |               False   |               False   |               False   |       False   |
    '0..'           False   |               False   |               False   |               False   |       False   |
    'one'           False   |               False   |               False   |               False   |       False   |
    <obj..>         False   |               False   |               False   |               False   |       False   |
    (1, 2, 3)       False   |               False   |               False   |               False   |       False   |
    [1, 2, 3]       False   |               False   |               False   |               False   |       False   |
    {'one': 'two'}  False   |               False   |               False   |               False   |       False   |
    ' 0 '           True    |               True    |               True    |               True    |       False   |
    ' 0.'           False   |               True    |               False   |               False   |       False   |
    ' .0'           False   |               False   |               False   |               False   |       False   |
    '.01 '          False   |               False   |               False   |               False   |       False   |

Tout à l'heure, j'ai essayé d'ajouter cette fonction:

def isInt_float(s):
    try:
        return float(str(s)).is_integer()
    except:
        return False

Il fonctionne presque aussi bien que check_int (0,3486) et renvoie true pour des valeurs telles que 1.0 et 0.0 et +1.0 et 0. et .0 et ainsi de suite. Mais cela retourne aussi vrai pour '06', donc. Choisissez votre poison, je suppose.

69
Shavais

Utilisez une expression régulière:

import re
def RepresentsInt(s):
    return re.match(r"[-+]?\d+$", s) is not None

Si vous devez accepter les fractions décimales également:

def RepresentsInt(s):
    return re.match(r"[-+]?\d+(\.0*)?$", s) is not None

Pour améliorer les performances si vous faites cela souvent, compilez l'expression régulière une seule fois en utilisant re.compile().

22
Greg Hewgill

La solution RegEx appropriée combinerait les idées de Greg Hewgill et de Nowell, mais n'utiliserait pas de variable globale. Vous pouvez accomplir cela en attachant un attribut à la méthode. De plus, je sais qu'il est mal vu de mettre des importations dans une méthode, mais ce que je veux, c'est un effet de "module paresseux" comme http://peak.telecommunity.com/DevCenter/Importing#lazy-imports

edit: Ma technique préférée jusqu'ici consiste à utiliser exclusivement les méthodes de l'objet String.

#!/usr/bin/env python

# Uses exclusively methods of the String object
def isInteger(i):
    i = str(i)
    return i=='0' or (i if i.find('..') > -1 else i.lstrip('-+').rstrip('0').rstrip('.')).isdigit()

# Uses re module for regex
def isIntegre(i):
    import re
    if not hasattr(isIntegre, '_re'):
        print("I compile only once. Remove this line when you are confident in that.")
        isIntegre._re = re.compile(r"[-+]?\d+(\.0*)?$")
    return isIntegre._re.match(str(i)) is not None

# When executed directly run Unit Tests
if __== '__main__':
    for obj in [
                # integers
                0, 1, -1, 1.0, -1.0,
                '0', '0.','0.0', '1', '-1', '+1', '1.0', '-1.0', '+1.0',
                # non-integers
                1.1, -1.1, '1.1', '-1.1', '+1.1',
                '1.1.1', '1.1.0', '1.0.1', '1.0.0',
                '1.0.', '1..0', '1..',
                '0.0.', '0..0', '0..',
                'one', object(), (1,2,3), [1,2,3], {'one':'two'}
            ]:
        # Notice the integre uses 're' (intended to be humorous)
        integer = ('an integer' if isInteger(obj) else 'NOT an integer')
        integre = ('an integre' if isIntegre(obj) else 'NOT an integre')
        # Make strings look like strings in the output
        if isinstance(obj, str):
            obj = ("'%s'" % (obj,))
        print("%30s is %14s is %14s" % (obj, integer, integre))

Et pour les membres moins aventureux de la classe, voici le résultat:

I compile only once. Remove this line when you are confident in that.
                             0 is     an integer is     an integre
                             1 is     an integer is     an integre
                            -1 is     an integer is     an integre
                           1.0 is     an integer is     an integre
                          -1.0 is     an integer is     an integre
                           '0' is     an integer is     an integre
                          '0.' is     an integer is     an integre
                         '0.0' is     an integer is     an integre
                           '1' is     an integer is     an integre
                          '-1' is     an integer is     an integre
                          '+1' is     an integer is     an integre
                         '1.0' is     an integer is     an integre
                        '-1.0' is     an integer is     an integre
                        '+1.0' is     an integer is     an integre
                           1.1 is NOT an integer is NOT an integre
                          -1.1 is NOT an integer is NOT an integre
                         '1.1' is NOT an integer is NOT an integre
                        '-1.1' is NOT an integer is NOT an integre
                        '+1.1' is NOT an integer is NOT an integre
                       '1.1.1' is NOT an integer is NOT an integre
                       '1.1.0' is NOT an integer is NOT an integre
                       '1.0.1' is NOT an integer is NOT an integre
                       '1.0.0' is NOT an integer is NOT an integre
                        '1.0.' is NOT an integer is NOT an integre
                        '1..0' is NOT an integer is NOT an integre
                         '1..' is NOT an integer is NOT an integre
                        '0.0.' is NOT an integer is NOT an integre
                        '0..0' is NOT an integer is NOT an integre
                         '0..' is NOT an integer is NOT an integre
                         'one' is NOT an integer is NOT an integre
<object object at 0x103b7d0a0> is NOT an integer is NOT an integre
                     (1, 2, 3) is NOT an integer is NOT an integre
                     [1, 2, 3] is NOT an integer is NOT an integre
                {'one': 'two'} is NOT an integer is NOT an integre
16
Bruno Bronosky

str.isdigit() devrait faire l'affaire.

Exemples:

str.isdigit("23") ## True str.isdigit("abc") ## False str.isdigit("23.4") ## False

5
Catbuilts

L'approche de Greg Hewgill manquait de quelques composants: le "^" initial ne correspondant qu'au début de la chaîne et la compilation préalable. Mais cette approche vous permettra d'éviter d'essayer: exept:

import re
INT_RE = re.compile(r"^[-]?\d+$")
def RepresentsInt(s):
    return INT_RE.match(str(s)) is not None

Je serais intéressé pourquoi vous essayez d'éviter d'essayer: sauf?

3
Nowell
>>> "+7".lstrip("-+").isdigit()
True
>>> "-7".lstrip("-+").isdigit()
True
>>> "7".lstrip("-+").isdigit()
True
>>> "13.4".lstrip("-+").isdigit()
False

Donc, votre fonction serait:

def is_int(val):
   return val[1].isdigit() and val.lstrip("-+").isdigit()
2
alkos333

Je pense

s.startswith('-') and s[1:].isdigit()

serait mieux de réécrire à:

s.replace('-', '').isdigit()

parce que s [1:] crée également une nouvelle chaîne

Mais la meilleure solution est

s.lstrip('+-').isdigit()
2

J'ai vraiment aimé le post de Shavais, mais j'ai ajouté un cas de test supplémentaire (et la fonction isdigit () intégrée):

def isInt_loop(v):
    v = str(v).strip()
    # swapping '0123456789' for '9876543210' makes nominal difference (might have because '1' is toward the beginning of the string)
    numbers = '0123456789'
    for i in v:
        if i not in numbers:
            return False
    return True

def isInt_Digit(v):
    v = str(v).strip()
    return v.isdigit()

et il bat régulièrement de manière significative les temps du reste:

timings..
isInt_try:   0.4628
isInt_str:   0.3556
isInt_re:    0.4889
isInt_re2:   0.2726
isInt_loop:   0.1842
isInt_Digit:   0.1577

en utilisant un python 2.7 normal:

$ python --version
Python 2.7.10

Les deux cas de test que j'ai ajoutés (isInt_loop et isInt_digit) réussissent exactement les mêmes cas de test (ils n'acceptent que les entiers non signés), mais je pensais que les gens pourraient être plus intelligents en modifiant l'implémentation de chaîne (isInt_loop) par rapport à l'isdigit (), je l'ai donc incluse, même s'il y a une légère différence dans le temps d'exécution. (et les deux méthodes battent beaucoup le reste, mais ne gèrent pas les trucs en trop: "./+/-")

De plus, j'ai trouvé intéressant de noter que la méthode regex (méthode isInt_re2) a battu la comparaison de chaînes dans le même test effectué par Shavais en 2012 (actuellement 2018). Peut-être que les bibliothèques de regex ont été améliorées?

1
brw59

C’est probablement la manière la plus simple et la plus pythonique de l’aborder à mon avis. Je n'ai pas vu cette solution et elle est fondamentalement la même que celle de regex, mais sans regex. 

def is_int(test):
    import string
    return not (set(test) - set(string.digits))
1
Xenlyte

Voici une fonction qui analyse sans générer d'erreurs. Il gère les cas évidents, les retours None en cas d'échec (jusqu'à 2000 signes '-/+' par défaut sur CPython!):

#!/usr/bin/env python

def get_int(number):
    splits = number.split('.')
    if len(splits) > 2:
        # too many splits
        return None
    if len(splits) == 2 and splits[1]:
        # handle decimal part recursively :-)
        if get_int(splits[1]) != 0:
            return None

    int_part = splits[0].lstrip("+")
    if int_part.startswith('-'):
        # handle minus sign recursively :-)
        return get_int(int_part[1:]) * -1
    # successful 'and' returns last truth-y value (cast is always valid)
    return int_part.isdigit() and int(int_part)

Quelques tests:

tests = ["0", "0.0", "0.1", "1", "1.1", "1.0", "-1", "-1.1", "-1.0", "-0", "--0", "---3", '.3', '--3.', "+13", "+-1.00", "--+123", "-0.000"]

for t in tests:
    print "get_int(%s) = %s" % (t, get_int(str(t)))

Résultats:

get_int(0) = 0
get_int(0.0) = 0
get_int(0.1) = None
get_int(1) = 1
get_int(1.1) = None
get_int(1.0) = 1
get_int(-1) = -1
get_int(-1.1) = None
get_int(-1.0) = -1
get_int(-0) = 0
get_int(--0) = 0
get_int(---3) = -3
get_int(.3) = None
get_int(--3.) = 3
get_int(+13) = 13
get_int(+-1.00) = -1
get_int(--+123) = 123
get_int(-0.000) = 0

Pour vos besoins, vous pouvez utiliser:

def int_predicate(number):
     return get_int(number) is not None
1
Reut Sharabani

J'ai une possibilité qui n'utilise pas int du tout, et ne devrait pas déclencher une exception à moins que la chaîne ne représente pas un nombre

float(number)==float(number)//1

Cela devrait fonctionner pour tout type de chaîne que float accepte, positive, négative, notation technique ...

0
agomcas

Je suppose que la question est liée à la vitesse puisque l'essai/sauf a une pénalité de temps:

 données de test

Tout d'abord, j'ai créé une liste de 200 chaînes, 100 chaînes défaillantes et 100 chaînes numériques.

from random import shuffle
numbers = [u'+1'] * 100
nonumbers = [u'1abc'] * 100
testlist = numbers + nonumbers
shuffle(testlist)
testlist = np.array(testlist)

 solution numpy (fonctionne uniquement avec les tableaux et Unicode)

np.core.defchararray.isnumeric peut également fonctionner avec des chaînes unicode np.core.defchararray.isnumeric(u'+12') mais il retourne un tableau. C'est donc une bonne solution si vous devez effectuer des milliers de conversions et que des données sont manquantes ou non numériques.

import numpy as np
%timeit np.core.defchararray.isnumeric(testlist)
10000 loops, best of 3: 27.9 µs per loop # 200 numbers per loop

essayer/sauf

def check_num(s):
  try:
    int(s)
    return True
  except:
    return False

def check_list(l):
  return [check_num(e) for e in l]

%timeit check_list(testlist)
1000 loops, best of 3: 217 µs per loop # 200 numbers per loop

Semble que la solution numpy est beaucoup plus rapide. 

0
Carlos Vega