Levanta una excepción de un nivel superior, a la advertencia.

En las advertencias del módulo ( https://docs.python.org/3.5/library/warnings.html ) existe la posibilidad de generar una advertencia que parece provenir de algún lugar anterior en la stack:

warnings.warn('This is a test', stacklevel=2) 

¿Hay un equivalente para levantar errores? Sé que puedo generar un error con un rastreo alternativo, pero no puedo crear ese rastreo dentro del módulo ya que tiene que ser anterior. Me imagino algo como:

 tb = magic_create_traceback_right_here() raise ValueError('This is a test').with_traceback(tb.tb_next) 

La razón es que estoy desarrollando un módulo que tiene una función module.check_raise y quiero generar un error que parece originarse desde donde se llama la función. Si levanto un error dentro de la función module.check_raise , parece que se origina dentro de module.check_raise , lo cual no es deseado.

Además, he intentado trucos como subir una excepción ficticia, atraparla y transmitirla, pero de alguna manera el tb_next se convierte en None . Me he quedado sin ideas.

Editar:

Me gustaría la salida de este ejemplo mínimo (llamado tb2.py):

 import check_raise check_raise.raise_if_string_is_true('True') 

para ser solo esto

 Traceback (most recent call last): File "tb2.py", line 10, in  check_raise.raise_if_string_is_true(string) RuntimeError: An exception was raised. 

Si lo entiendo correctamente, te gustaría la salida de este ejemplo mínimo:

 def check_raise(function): try: return function() except Exception: raise RuntimeError('An exception was raised.') def function(): 1/0 check_raise(function) 

para ser solo esto

 Traceback (most recent call last): File "tb2.py", line 10, in  check_raise(function) RuntimeError: An exception was raised. 

De hecho, es mucho más salida; hay un encadenamiento de excepciones, que podría tratarse manejando el RuntimeError inmediatamente, eliminando su __context__ , y volviendo a __context__ , y hay otra línea de rastreo para el propio RuntimeError :

  File "tb2.py", line 5, in check_raise raise RuntimeError('An exception was raised.') 

Por lo que puedo decir, no es posible que el código puro de Python sustituya el rastreo de una excepción después de que se generó; el intérprete tiene el control de agregarle, pero solo expone el rastreo actual cuando se maneja la excepción. No hay API (ni siquiera cuando se usan funciones de rastreo) para pasar su propio rastreo al intérprete, y los objetos de rastreo son inmutables (esto es lo que aborda el hackeo de Jinja que involucra cosas de nivel C).

Asumiendo además que está interesado en el rastreo reducido, no para un uso programático adicional, sino solo para resultados fáciles de usar, su mejor apuesta será una excepthook que controla cómo se imprime el excepthook en la consola. Para determinar dónde detener la impresión, se puede usar una variable local especial (esto es un poco más robusto que limitar el rastreo a su longitud menos 1 o similar). Este ejemplo requiere Python 3.5 (para traceback.walk_tb ):

 import sys import traceback def check_raise(function): __exclude_from_traceback_from_here__ = True try: return function() except Exception: raise RuntimeError('An exception was raised.') def print_traceback(exc_type, exc_value, tb): for i, (frame, lineno) in enumerate(traceback.walk_tb(tb)): if '__exclude_from_traceback_from_here__' in frame.f_code.co_varnames: limit = i break else: limit = None traceback.print_exception( exc_type, exc_value, tb, limit=limit, chain=False) sys.excepthook = print_traceback def function(): 1/0 check_raise(function) 

Esta es la salida ahora:

 Traceback (most recent call last): File "tb2.py", line 26, in  check_raise(function) RuntimeError: An exception was raised. 

No puedo creer que estoy publicando esto

Al hacer esto vas contra el zen .

Los casos especiales no son lo suficientemente especiales para romper las reglas.

Pero si insistes aquí es tu código mágico.

check_raise.py

 import sys import traceback def raise_if_string_is_true(string): if string == 'true': #the frame that called this one f = sys._getframe().f_back #the most USELESS error message ever e = RuntimeError("An exception was raised.") #the first line of an error message print('Traceback (most recent call last):',file=sys.stderr) #the stack information, from f and above traceback.print_stack(f) #the last line of the error print(*traceback.format_exception_only(type(e),e), file=sys.stderr, sep="",end="") #exit the program #if something catches this you will cause so much confusion raise SystemExit(1) # SystemExit is the only exception that doesn't trigger an error message by default. 

Esto es Python puro, no interfiere con sys.excepthook e incluso en un bloque try con el que no se captura, except Exception: con except Exception: aunque se captura con, except:

test.py

 import check_raise check_raise.raise_if_string_is_true("true") print("this should never be printed") 

le dará el mensaje de rastreo que usted desea (horriblemente poco informativo y extremadamente falsificado).

 Tadhgs-MacBook-Pro:Documents Tadhg$ python3 test.py Traceback (most recent call last): File "test.py", line 3, in  check_raise.raise_if_string_is_true("true") RuntimeError: An exception was raised. Tadhgs-MacBook-Pro:Documents Tadhg$ 

EDITAR: La versión anterior no proporcionó citas o explicaciones.

Sugiero referirse a PEP 3134 que establece en la Motivación :

A veces puede ser útil para un controlador de excepciones volver a generar intencionalmente una excepción, ya sea para proporcionar información adicional o para traducir una excepción a otro tipo. El atributo __cause__ proporciona una forma explícita de registrar la causa directa de una excepción.

Cuando se __cause__ una Exception con un atributo __cause__ el mensaje de __cause__ toma la forma de:

 Traceback (most recent call last):  The above exception was the direct cause of the following exception: Traceback (most recent call last): 

A mi entender, esto es exactamente lo que estás tratando de lograr; indica claramente que el motivo del error no es tu módulo sino en otro lugar. Si, por el contrario, intenta omitir información en el rastreo como sugiere su edición , el rest de esta respuesta no le servirá de nada.


Solo una nota sobre la syntax:

El atributo __cause__ en los objetos de excepción siempre se inicializa a Ninguno. Se establece mediante una nueva forma de la statement ‘raise’:

  raise EXCEPTION from CAUSE 

que es equivalente a:

  exc = EXCEPTION exc.__cause__ = CAUSE raise exc 

así que el mínimo ejemplo sería algo como esto:

 def function(): int("fail") def check_raise(function): try: function() except Exception as original_error: err = RuntimeError("An exception was raised.") raise err from original_error check_raise(function) 

que da un mensaje de error como este:

 Traceback (most recent call last): File "/PATH/test.py", line 7, in check_raise function() File "/PATH/test.py", line 3, in function int("fail") ValueError: invalid literal for int() with base 10: 'fail' The above exception was the direct cause of the following exception: Traceback (most recent call last): File "/PATH/test.py", line 12, in  check_raise(function) File "/PATH/test.py", line 10, in check_raise raise err from original_error RuntimeError: An exception was raised. 

Sin embargo, la primera línea de la causa es la statement en el bloque try de check_raise :

  File "/PATH/test.py", line 7, in check_raise function() 

así que antes de que se err un err , puede (o no) ser deseable eliminar el marco de seguimiento más externo de original_error :

 except Exception as original_error: err = RuntimeError("An exception was raised.") original_error.__traceback__ = original_error.__traceback__.tb_next raise err from original_error 

De esta manera, la única línea en el rastreo que parece provenir de check_raise es la última statement de raise que no se puede omitir con el código puro de Python, aunque dependiendo de cuán informativo sea el mensaje, puede dejar muy claro que su módulo no fue la causa de el problema:

 err = RuntimeError("""{0.__qualname__} encountered an error during call to {1.__module__}.{1.__name__} the traceback for the error is shown above.""".format(function,check_raise)) 

La ventaja de generar una excepción como esta es que el mensaje de rastreo original no se pierde cuando se genera el nuevo error, lo que significa que se pueden generar una serie de excepciones muy complejas y que Python seguirá mostrando toda la información relevante correctamente:

 def check_raise(function): try: function() except Exception as original_error: err = RuntimeError("""{0.__qualname__} encountered an error during call to {1.__module__}.{1.__name__} the traceback for the error is shown above.""".format(function,check_raise)) original_error.__traceback__ = original_error.__traceback__.tb_next raise err from original_error def test_chain(): check_raise(test) def test(): raise ValueError check_raise(test_chain) 

me da el siguiente mensaje de error:

 Traceback (most recent call last): File "/Users/Tadhg/Documents/test.py", line 16, in test raise ValueError ValueError The above exception was the direct cause of the following exception: Traceback (most recent call last): File "/Users/Tadhg/Documents/test.py", line 13, in test_chain check_raise(test) File "/Users/Tadhg/Documents/test.py", line 10, in check_raise raise err from original_error RuntimeError: test encountered an error during call to __main__.check_raise the traceback for the error is shown above. The above exception was the direct cause of the following exception: Traceback (most recent call last): File "/Users/Tadhg/Documents/test.py", line 18, in  check_raise(test_chain) File "/Users/Tadhg/Documents/test.py", line 10, in check_raise raise err from original_error RuntimeError: test_chain encountered an error during call to __main__.check_raise the traceback for the error is shown above. 

Sí, es largo pero es significativamente más informativo entonces:

 Traceback (most recent call last): File "/Users/Tadhg/Documents/test.py", line 18, in  check_raise(test_chain) RuntimeError: An exception was raised. 

Sin mencionar que el error original todavía es utilizable incluso si el progtwig no termina:

 import traceback def check_raise(function): ... def fail(): raise ValueError try: check_raise(fail) except RuntimeError as e: cause = e.__cause__ print("check_raise failed because of this error:") traceback.print_exception(type(cause), cause, cause.__traceback__) print("and the program continues...")