2009-08-12 4 views
253

¿Hay alguna manera de saber si una cadenarepresenta un número entero (por ejemplo, '3', '-17' pero no '3.14' o 'asfasfas') Sin utilizar un try/excepto mecanismo?¿Cómo puedo verificar si una cadena representa un int, sin usar try/except?

is_int('3.14') = False 
is_int('-7') = True 
+21

Wh y ambos tratando de hacer esto "de la manera difícil?" ¿Qué pasa con try/except? –

+0

¿qué deberían representar los valores de RepresentsInt ("17") y RepresentsInt ("+ 17")? (es decir: ¿se debe ignorar el espacio en blanco o es significativo? ¿Qué pasa con un '+'?) –

+4

Sí, ¿qué pasa con try/except? Es mejor pedir perdón que pedir permiso. – mk12

Respuesta

235

Si eres realmente molesto por el uso de try/except s por todo el lugar, por favor, acaba de escribir una función auxiliar:

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

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

Va a ser mucho más código para cubrir exactamente todas las cadenas que Python considera enteros. Digo solo ser pitónico en este caso.

+67

¿Entonces es pitónico resolver un problema simple con un mecanismo complejo? Hay un algoritmo para detectar la función interna de int escrita "int" - No veo por qué no está expuesto como una función booleana. – Aivar

+57

@Aivar: esta función de 5 líneas no es un mecanismo complejo. – Triptych

+2

Antes de usar esto en el código que debe funcionar con alta frecuencia, debería ver la publicación de Shavais a continuación donde multiplica las soluciones en esta página. La solución más rápida es mi encadenamiento de método de cadena pura. –

413

con números enteros positivos que podría utilizar .isdigit:

>>> '16'.isdigit() 
True 

no funciona con enteros negativos sin embargo. supongamos que usted podría intentar lo siguiente:

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

no va a funcionar con '16.0' formato, que es similar a int de calidad en este sentido.

edición:

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

¿El retorno numérico también es cierto para los flotadores? – Skurmedel

+0

Lo siento, salté la última oración. – Skurmedel

+0

no. – SilentGhost

17

utilizar una expresión regular:

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

Si debe aceptar las fracciones decimales también:

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

Para un mejor rendimiento, si está haciendo esto a menudo, compila la expresión regular solo una vez con nosotros ing re.compile().

+14

+1: revela que esto es terriblemente complejo y caro en comparación con try/except. –

+2

Creo que esta es esencialmente una versión más lenta y personalizada de la solución 'isnumeric' que ofrece @SilentGhost. – Greg

+0

@Greg: Dado que @SilentGhost no cubre los letreros correctamente, esta versión realmente funciona. –

4

Al enfoque de Greg Hewgill le faltaban algunos componentes: el "^" inicial que solo coincidía con el comienzo de la cadena, y compilando el re antes. Sin embargo, este enfoque permitirá evitar una oportunidad: exepto:

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

estaría interesado por la que está tratando de evitar intento: salvo?

+1

Una cuestión de estilo. Creo que "try/except" debe usarse solo con errores reales, no con flujo de programa normal. –

+1

@Udi Pasmon: Python hace bastante uso de try/a excepción del flujo de programa "normal". Por ejemplo, cada iterador se detiene con una excepción planteada. –

+3

-1: aunque su sugerencia para compilar la expresión regular es correcta, se equivoca al criticar a Greg en el otro aspecto: las coincidencias de re.match con el ** inicio ** de la cadena, por lo que^en el patrón es realmente redundante . (Esto es diferente cuando usa re.search). – ThomasH

15

La solución RegEx adecuada combinaría las ideas de Greg Hewgill y Nowell, pero no usaría una variable global. Puede lograr esto adjuntando un atributo al método. Además, sé que está mal visto poner las importaciones en un método, pero lo que voy es un efecto de "módulo perezoso" como http://peak.telecommunity.com/DevCenter/Importing#lazy-imports

edición: Mi técnica favorita hasta ahora es utilizar exclusivamente métodos del objeto 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 __name__ == '__main__': 
    for o 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(o) else 'NOT an integer') 
     integre = ('an integre' if isIntegre(o) else 'NOT an integre') 
     if isinstance(o, str): 
      o = ("'%s'" % (o,)) 
     print "%30s is %14s is %14s" % (o, integer, integre) 

Y para los menos aventureros de la clase, aquí está la salida:

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

overkill: S !!! – Trent

+3

Estoy de acuerdo en que mi suite de pruebas es exagerada. Me gusta ** probar ** que mi código funciona cuando lo escribo. ¿Pero crees que mi función isInteger es excesiva? Seguramente no. –

+3

+1 para el conjunto de esfuerzo y prueba :) – Triptych

-4

Uh ..Pruebe esto:

def int_check(a): 
    if int(a) == a: 
     return True 
    else: 
     return False 

Esto funciona si no pone una cadena que no es un número.

Y también (me olvidé de poner la parte de verificación del número), hay una función que comprueba si la cadena es un número o no. Es str.isdigit(). Aquí hay un ejemplo:

a = 2 
a.isdigit() 

Si llama a.isdigit(), devolverá True.

+0

Creo que necesita cotizaciones alrededor del valor '2' asignado a' a'. –

+1

¿Por qué no es esta la mejor respuesta? Responde la pregunta exactamente. – grasshopper

+5

-1 la pregunta: * "Comprobar si ** string ** representa un int, sin usar Try/Except?" * Para @Caroline Alexiou – jfs

52

Ya sabes, he encontrado (y he probado esto una y otra vez) que try/except no funciona tan bien, por la razón que sea. Frecuentemente pruebo varias formas de hacer las cosas, y no creo haber encontrado un método que use try/except para realizar lo mejor de los examinados, de hecho, me parece que esos métodos generalmente han salido cerca del peor, si no el peor. No en todos los casos, pero en muchos casos. Sé que mucha gente dice que es la forma "Pythonic", pero esa es un área en donde me separo de ellos. Para mí, no es muy eficiente ni muy elegante, por lo tanto, tiendo a usarlo solo para detectar errores y generar informes.

Iba a quejarse de que PHP, Perl, Ruby, C e incluso el maldito caparazón tienen funciones simples para probar una cadena para enteros, pero la diligencia debida al verificar esas suposiciones me hizo tropezar. Aparentemente esta falta es una enfermedad común.

He aquí una edición rápida y sucia del puesto de Richard:

import sys, time, re 

g_intRegex = re.compile(r"[-+]?\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 
    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"[-+]?\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 timeFunc(func, times): 
    t1 = time.time() 
    for n in xrange(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: 
     sys.stdout.write("%s" % str(v)) 
     for func in funcs: 
      sys.stdout.write("\t\t%s\t|" % func(v)) 
     print 

if __name__ == '__main__': 
    print 
    print "tests.." 
    testFuncs((isInt_try, isInt_str, isInt_re, isInt_re2)) 
    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) 

Aquí es la parte interesante de la salida:

timings.. 
isInt_try: 1.2454 
isInt_str: 0.7878 
isInt_re: 1.5731 
isInt_re2: 0.8087 

Como se puede ver, el método de cadena es el más rápido. Es casi dos veces más rápido que el método de expresión regular que evita confiar en cualquier elemento global, y más de la mitad más rápido que el método try: except. El método de expresiones regulares que se basa en algunos elementos globales (o, bueno, los atributos del módulo) es un segundo cercano.

pienso en ellos, mi elección sería

isInt = isInt_str 

Pero eh .. esto es copiar y nueva copia y volver a copiar toda la cadena! (¡¡Y, sin embargo, es el método más rápido !!?) Un método C podría escanearlo una vez, y listo. Un método C que escanea la secuencia una vez es lo correcto, ¿no? Supongo que podría haber algunos problemas de codificación de cadenas. De todos modos, trataría de resolver uno ahora, pero estoy fuera de tiempo para esto. = (Tal vez voy a volver a ella más tarde

+0

Quizás parte de esto proviene del hecho de que un entero es un poco arbitrario. Un sistema de programación no puede darse el lujo de suponer que siempre va a ser una representación decimal. 0x4df, es un número entero válido en algunos lugares, y 0891 no está en otros. Me da miedo pensar qué podría surgir dado unicode en este tipo de controles. – PlexQ

+0

Es cierto que la definición de un int válido es un poco dudosa, y diferente en contextos diferentes, y la presencia de varias codificaciones de cadena complica sustancialmente la materia; sin embargo, tenemos la función int() incorporada. Tal vez la int() incorporada debería tener una máscara de bits opcional que controle algunas cosas sobre cómo se comporta, y tal vez debería haber un isInt() incorporado o is_int() que use el mismo código y banderas que int() hace. – Shavais

+2

+1 para el tiempo. Estoy de acuerdo en que todo este negocio de excepción no es realmente elegante para una pregunta tan simple. Esperaría una compilación en el método de ayuda para un problema tan común ... – RickyA

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

lo tanto, su función sería la siguiente:.

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

is_int ("2") provoca IndexError. – anttikoo

1

Esta es probablemente la forma más sencilla y Pythonic acercarse a ella en mi opinión, yo no. No veo esta solución y es básicamente la misma que la de expresión regular, pero sin la expresión regular.

def is_int(test): 
    import string 
    return not (set(test) - set(string.digits)) 
+0

'set (input_string) == set (string.digits)' si saltamos ''- +'' al inicio y '.0',' E-1' al final. – jfs

2

creo

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

sería mejor volver a escribir a:

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

porque s [1:] también crea una nueva cadena

Pero mucho mejor solución es

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

¿Adivina lo que 'replace' hace?Además, esto aceptará incorrectamente '5-2', por ejemplo. – Ryan

+0

Lanzaremos un IndexError si 's = '-'' –

+0

s = '-'; s.replace ('-', '') .isdigit() -> False –

0

He una posibilidad que no utiliza int en absoluto, y no debe lanzar una excepción a menos que la cadena no representa un número

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

que debería funcionar para cualquier tipo de cadena que flotan acepta, positivo , negativo, notación de ingeniería ...

1

Aquí hay una función que analiza sin generar errores. Se ocupa de los casos obvios devuelve None en caso de fallo (maneja hasta 2000 '-/+' signos de forma predeterminada en 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) 

Algunas pruebas:

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

Resultados:

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 

para sus necesidades, puede utilizar:

def int_predicate(number): 
    return get_int(number) is not None 
Cuestiones relacionadas