Decorador general para envolver intentar excepto en python?
try-catch wrapper (7)
Interactuaba con una gran cantidad de json profundamente anidados que no escribí, y me gustaría hacer que mi script de Python sea más "indulgente" con entradas no válidas. Me encuentro escribiendo bloques try-except involucrados, y preferiría simplemente envolver la dudosa función.
Entiendo que es una mala política tragar excepciones, pero preferiría que se impriman y se analicen 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 ajustar el elemento de datos individuales (1) o una función maestra (2) en alguna función que convierta las excepciones de interrupción de 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 probé, con un envoltorio:
def f(func):
def silenceit():
try:
func(*args,**kwargs)
except:
print(''Error'')
return(silenceit)
He aquí por qué no funciona. Llama a una función que no existe, no trata de atraparla:
>>> f(meow())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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 he editado los ejemplos que usé anteriormente para ilustrar que estoy tratando de detectar más que errores de claves anidados, que Estoy buscando específicamente una función que envuelva un try-catch para ...
- Cuando un método no existe.
- Cuando un objeto no existe, y recibe un método llamado en él.
- Cuando un objeto que no existe se llama como un argumento a una función.
- Cualquier combinación de cualquiera de estas cosas.
- Bonificación, cuando una función no existe.
¿Por qué no 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 desea, escriba 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.
Dado que se trata de una gran cantidad de código roto, puede ser excusable utilizar eval
en este caso.
def my_eval(code):
try:
return eval(code)
except: # Can catch more specific exceptions here.
return ''''
Luego envuelva todas sus 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)""")
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 de la manera 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 incorrectamente La forma correcta sería ajustar 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 queda atrapada por el contenedor.
Esto funciona porque el nombre my_function
no se ejecuta en el momento en que se define lambda, pero cuando se ejecuta. Y esta ejecución está protegida y envuelta por la función f()
entonces. Entonces la excepción ocurre dentro de la lambda y se propaga a la función de envoltura provista por el decorador, que lo maneja con gracia.
Este movimiento hacia el interior de la función lambda no funciona si intenta reemplazar la función lambda con un contenedor como
g = lambda function: lambda *a, **k: function(*a, **k)
seguido por un
f(g(my_function))(arguments)
porque aquí la resolución del nombre está "de vuelta en la superficie": my_function
no se puede resolver y esto sucede antes de que se llame a g()
o incluso f()
. Entonces 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 hacer
value = f(lambda: x.get(''fail''))
porque el contenedor provisto por f()
llama a esa función lambda que genera una excepción que luego se silencia.
Es muy fácil de lograr usando un 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)
Solo pase a las tuplas get_decorator con tipos de error que desea silenciar y devolver el valor predeterminado. La salida será
Got error! KeyError(''b'',)
default
Got error! NameError("global name ''doesnt_exist'' is not defined",)
default
Editar: Gracias a Martineau cambié el valor predeterminado de los errores a las tuplas con Excepción básica para evitar errores.
Hay muchas buenas respuestas aquí, pero no vi ninguna que aborde la cuestión de si puede lograr esto a través de 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 declaraciones para decorarla en su propia función.
Pero tenga en cuenta que no puede simplemente poner la asignación dentro de la función decorada. Debe devolver la expresión rhs (el valor que se asignará) de la función decorada, luego haga la tarea 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 así como:
def return_on_failure(value):
def decorate(f):
def applicator(*args, **kwargs)
try:
f(*args,**kwargs)
except:
print(''Error'')
return applicator
return decorate
Podría usar un método de default y de administrador de contexto 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'']
en su caso, primero evalúa el valor de la llamada de miau (que no existe) y luego lo envuelve en el decorador. esto no funciona de esa manera.
primero, la excepción se levanta antes de que se haya envuelto, luego el envoltorio se silenceit
incorrectamente (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 "<stdin>", line 1, in <module>
File "<stdin>", 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 código
item[''a''] = get_from_object(obj, 2, 3)
Por cierto, desde el punto de vista personal, también me gusta la solución @cravoori usando 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)