Decorador general para envolver probar excepto en python?

Interactuaba con un montón de json profundamente nesteds que no escribí, y me gustaría hacer que mi script en Python sea más “indulgente” que la entrada no válida. Me encuentro escribiendo bloques try-except, y preferiría simplemente cerrar la dudosa función.

Entiendo que es una mala política tragar excepciones, pero prefiero que sean impresas y analizadas más tarde que detener la ejecución. Es más valioso, en mi caso de uso, continuar ejecutándose en el ciclo que obtener todas las claves.

Esto es lo que estoy haciendo ahora:

try: item['a'] = myobject.get('key').METHOD_THAT_DOESNT_EXIST() except: item['a'] = '' try: item['b'] = OBJECT_THAT_DOESNT_EXIST.get('key2') except: item['b'] = '' try: item['c'] = func1(ARGUMENT_THAT_DOESNT_EXIST) except: item['c'] = '' ... try: item['z'] = FUNCTION_THAT_DOESNT_EXIST(myobject.method()) except: item['z'] = '' 

Esto es lo que me gustaría, (1):

 item['a'] = f(myobject.get('key').get('subkey')) item['b'] = f(myobject.get('key2')) item['c'] = f(func1(myobject) ... 

o (2):

     @f def get_stuff(): item={} item['a'] = myobject.get('key').get('subkey') item['b'] = myobject.get('key2') item['c'] = func1(myobject) ... return(item) 

    … donde puedo envolver el elemento de datos único (1), o una función maestra (2), en alguna función que convierte las excepciones que detienen la ejecución en campos vacíos, impresos en stdout. El primero sería una especie de salto de elementos: donde la clave no está disponible, se registra en blanco y se mueve, el segundo es un salto de fila, donde si alguno de los campos no funciona, todo el registro es saltado

    Tengo entendido que algún tipo de envoltorio debería poder arreglar esto. Esto es lo que intenté, con un envoltorio:

     def f(func): def silenceit(): try: func(*args,**kwargs) except: print('Error') return(silenceit) 

    Aquí es por qué no funciona. Llama a una función que no existe, no trata de atraparla:

     >>> f(meow()) Traceback (most recent call last): File "", line 1, in  NameError: name 'meow' is not defined 

    Antes de que incluso agregue un valor de retorno en blanco, me gustaría obtenerlo para intentar capturarlo correctamente. Si la función hubiera funcionado, se habría impreso “Error”, ¿verdad?

    ¿Es una función de envoltura el enfoque correcto aquí?

    ACTUALIZAR

    He recibido muchas respuestas útiles y útiles a continuación, y gracias por ellas, pero edité los ejemplos que usé anteriormente para ilustrar que estoy tratando de detectar más que errores de claves nesteds, Estoy buscando específicamente una función que envuelva un try-catch para …

    1. Cuando un método no existe.
    2. Cuando un objeto no existe, y recibe un método llamado en él.
    3. Cuando un objeto que no existe se llama como un argumento a una función.
    4. Cualquier combinación de cualquiera de estas cosas.
    5. Bonificación, cuando una función no existe.

    Puede usar un enfoque predeterminado y el enfoque del administrador de contexto tal como se describe en la presentación PyCon 2013 de Raymond Hettinger

     from collections import defaultdict from contextlib import contextmanager @contextmanager def ignored(*exceptions): try: yield except exceptions: pass item = defaultdict(str) obj = dict() with ignored(Exception): item['a'] = obj.get(2).get(3) print item['a'] obj[2] = dict() obj[2][3] = 4 with ignored(Exception): item['a'] = obj.get(2).get(3) print item['a'] 

    Hay muchas respuestas buenas aquí, pero no vi ninguna que aborde la pregunta de si se puede lograr esto a través de los decoradores.

    La respuesta corta es “no”, al menos no sin cambios estructurales en su código. Los decoradores operan en el nivel de función, no en declaraciones individuales. Por lo tanto, para usar decoradores, necesitaría mover cada una de las afirmaciones para que se decoren en su propia función.

    Pero tenga en cuenta que no puede simplemente colocar la asignación dentro de la función decorada. Debe devolver la expresión rhs (el valor que se asignará) desde la función decorada, luego hacer la asignación afuera.

    Para poner esto en términos de su código de ejemplo, uno podría escribir código con el siguiente patrón:

     @return_on_failure('') def computeA(): item['a'] = myobject.get('key').METHOD_THAT_DOESNT_EXIST() item["a"] = computeA() 

    return_on_failure podría ser algo como:

     def return_on_failure(value): def decorate(f): def applicator(*args, **kwargs): try: f(*args,**kwargs) except: print('Error') return applicator return decorate 

    Es muy fácil de lograr utilizando decorador configurable.

     def get_decorator(errors=(Exception, ), default_value=''): def decorator(func): def new_func(*args, **kwargs): try: return func(*args, **kwargs) except errors, e: print "Got error! ", repr(e) return default_value return new_func return decorator f = get_decorator((KeyError, NameError), default_value='default') a = {} @f def example1(a): return a['b'] @f def example2(a): return doesnt_exist() print example1(a) print example2(a) 

    Simplemente pase a las tuplas get_decorator con los tipos de error que desea silenciar y el valor predeterminado para devolver. La salida será

     Got error! KeyError('b',) default Got error! NameError("global name 'doesnt_exist' is not defined",) default 

    Edición: gracias a martineau, cambié el valor predeterminado de los errores a tuplas con una excepción básica para evitar errores.

    Depende de qué excepciones esperas.

    Si su único caso de uso es get() , podría hacer

     item['b'] = myobject.get('key2', '') 

    Para los otros casos, su enfoque de decorador puede ser útil, pero no en la forma en que lo hace.

    Intentaré mostrarte:

     def f(func): def silenceit(*args, **kwargs): # takes all kinds of arguments try: return func(*args, **kwargs) # returns func's result except Exeption, e: print('Error:', e) return e # not the best way, maybe we'd better return None # or a wrapper object containing e. return silenceit # on the correct level 

    Sin embargo, f(some_undefined_function()) no funcionará, porque

    a) f() aún no está activo en el momento de la ejecución y

    b) se usa mal. La forma correcta sería envolver la función y luego llamarla: f(function_to_wrap)() .

    Una “capa de lambda” ayudaría aquí:

     wrapped_f = f(lambda: my_function()) 

    ajusta una función lambda que a su vez llama a una función no existente. Llamar a wrapped_f() lleva a llamar al contenedor que llama a la lambda que intenta llamar a my_function() . Si esto no existe, la lambda genera una excepción que es atrapada por el envoltorio.

    Esto funciona porque el nombre my_function no se ejecuta en el momento en que se define la lambda, sino cuando se ejecuta. Y esta ejecución está protegida y envuelta por la función f() entonces. Así que la excepción ocurre dentro de la lambda y se propaga a la función de envoltura proporcionada por el decorador, que la maneja con gracia.

    Este movimiento hacia el interior de la función lambda no funciona si intenta reemplazar la función lambda con una envoltura como

     g = lambda function: lambda *a, **k: function(*a, **k) 

    seguido de un

     f(g(my_function))(arguments) 

    porque aquí la resolución de nombres es “de vuelta a la superficie”: my_function no se puede resolver y esto ocurre antes de que se llame a g() o incluso a f() . Así que no funciona.

    Y si intentas hacer algo como

     g(print)(x.get('fail')) 

    no puede funcionar tan bien si no tiene x , porque g() protege la print , no x .

    Si quieres proteger x aquí, tendrás que hacerlo.

     value = f(lambda: x.get('fail')) 

    porque la envoltura proporcionada por f() llama a esa función lambda que genera una excepción que luego se silencia.

    en su caso, primero evalúe el valor de la llamada de maullido (que no existe) y luego envuélvalo en el decorador. Esto no funciona de esa manera.

    Primero, la excepción se genera antes de envolverse, luego la envoltura tiene una sangría incorrecta (el silenceit no debe devolverse). Es posible que desee hacer algo como:

     def hardfail(): return meow() # meow doesn't exist def f(func): def wrapper(): try: func() except: print 'error' return wrapper softfail =f(hardfail) 

    salida:

     >>> softfail() error >>> hardfail() Traceback (most recent call last): File "", line 1, in  File "", line 2, in hardfail NameError: global name 'meow' is not defined 

    De todos modos, en su caso, no entiendo por qué no usa un método simple como

     def get_subkey(obj, key, subkey): try: return obj.get(key).get(subkey, '') except AttributeError: return '' 

    y en el código:

      item['a'] = get_subkey(myobject, 'key', 'subkey') 

    Editado:

    En caso de que quieras algo que funcione a cualquier profundidad. Puedes hacer algo como:

     def get_from_object(obj, *keys): try: value = obj for k in keys: value = value.get(k) return value except AttributeError: return '' 

    Que llamarías

     >>> d = {1:{2:{3:{4:5}}}} >>> get_from_object(d, 1, 2, 3, 4) 5 >>> get_from_object(d, 1, 2, 7) '' >>> get_from_object(d, 1, 2, 3, 4, 5, 6, 7) '' >>> get_from_object(d, 1, 2, 3) {4: 5} 

    Y usando tu codigo

     item['a'] = get_from_object(obj, 2, 3) 

    Por cierto, desde un punto de vista personal, también me gusta la solución @cravoori utilizando contextmanager. Pero esto significaría tener tres líneas de código cada vez:

     item['a'] = '' with ignored(AttributeError): item['a'] = obj.get(2).get(3) 

    Como se trata de un montón de código roto, puede ser excusable usar eval en este caso.

     def my_eval(code): try: return eval(code) except: # Can catch more specific exceptions here. return '' 

    Luego envuelve todas tus declaraciones potencialmente rotas:

     item['a'] = my_eval("""myobject.get('key').get('subkey')""") item['b'] = my_eval("""myobject.get('key2')""") item['c'] = my_eval("""func1(myobject)""") 

    ¿Por qué no solo usar el ciclo?

     for dst_key, src_key in (('a', 'key'), ('b', 'key2')): try: item[dst_key] = myobject.get(src_key).get('subkey') except Exception: # or KeyError? item[dst_key] = '' 

    O si lo deseas escribe un pequeño ayudante:

     def get_value(obj, key): try: return obj.get(key).get('subkey') except Exception: return '' 

    También puede combinar ambas soluciones si tiene algunos lugares donde necesita obtener valor y la función de ayuda sería más razonable.

    No estoy seguro de que realmente necesite un decorador para su problema.

    Extensión de la respuesta de @iruvar: a partir de Python 3.4, existe un administrador de contexto para esto en la versión estándar de Python: https://docs.python.org/3/library/contextlib.html#contextlib.suppress

     from contextlib import suppress with suppress(FileNotFoundError): os.remove('somefile.tmp') with suppress(FileNotFoundError): os.remove('someotherfile.tmp')