简体   繁体   中英

How does pytest.raises(Error) work?

New-ish to Python but I'm trying to understand this slice of code:

with pytest.raises(ValueError):
    group_adjust(vals, [grps_1, grps_2], weights)

After reading this tutorial on with , I understand pytest.raises() returns a context manager that sets up and cleans up things before and after group_adjust() is called. I also understand that group_adjust() should raise a ValueError if something goes wrong.

How does pytest "react" when a ValueError is raised? AFAIK, there's only setting up and cleaning up so I'm not sure how it catches the exception. The end goal for this is to understand the benefits of having pytest as a context manager.

__exit__ magic function accepts exception_type , exception_value and traceback parameters:

In [5]: class RaisesContext:
   ...:     def __enter__(self):
   ...:         return self
   ...:     def __exit__(self, exception_type, exception_value, traceback):
   ...:         print('Exception type:', exception_type)
   ...:         print('Exception value:', exception_value)
   ...:         print('Traceback:', traceback)
   ...:         return True
   ...:     

In [6]: with RaisesContext():
   ...:     raise ValueError('Something went wrong')
   ...: 
Exception type: <class 'ValueError'>
Exception value: Something went wrong
Traceback: <traceback object at 0x7fd92f4a2c48>

They are None , if the with block ends normally:

In [7]: with RaisesContext():
   ...:     pass
   ...: 
Exception type: None
Exception value: None
Traceback: None

I'm not completely sure about pytest but any context manager is passed 3 arguments on its exit, the exc_type, Excepton and Traceback, if no exception was raise all three are none and if the exit returns True then the exception is suppressed as well,

better explained here: https://docs.python.org/2/reference/datamodel.html#object. exit

so if I want to make a simple handler that will show the traceback without stopping the program I could do this:

import traceback
class VerboseTry:
    def __enter__(self):
        pass
    def __exit__(self,exc_type,error,trace):
        if exc_type:
            traceback.print_exception(exc_type,error,trace)
            return True

def f(depth=4):
    """this will (needlessly) raise an error with several iterations to the traceback"""
    if depth==0:
        int("this is going to fail")
    else:
        return f(depth-1)

print("starting")
with VerboseTry():
    f()
print("got to end")

to see the error that was raised without halting the program.

The with construct calls two "magic" methods, __enter__ and __exit__ at the beginning and end of the code block, respectively. Thus,

with foo:
    x = 1

Can be read as:

foo.__enter__()
x = 1
foo.__exit__()

Except that, as soon mentioned in hir answer, __exit__ is called with details on why the code block is exiting: if an exception, what kind, otherwise None .

So the object returned by pytest.raises(TYPE) has been configured to expect an exception of TYPE. The __exit__ method compares the parameter it receives declaring the actual (if any) exception with the internal data member it has storing the expected exception type, and then decides to pass or fail the test.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM