Skip to content
Advertisement

Why does calling the __exit__ method on unittest.mock._patch throw an IndexError?

When I define a function and patch it using the with statement it runs fine.

JavaScript

Output:

JavaScript

My understanding is that using the with statement would cause the __enter__ and __exit__ methods to be called on the patch object. So I thought that would be equivalent to doing this:

JavaScript

The output from the some_func call is the same in this case:

JavaScript

But I get an IndexError when I call the __exit__ method:

JavaScript

Why am I getting an IndexError in the second case but not the first where I use with? (An additional note, I get this error on python 3.7.10 but not on python 3.7.3)

Advertisement

Answer

Your understanding is incorrect; on exit the context manager __exit__ method is passed None, None, None:

JavaScript

The arguments are always passed in, either the exception information, or None for each of the three arguments. See the documentation on object.__exit__():

If the context was exited without an exception, all three arguments will be None.

Also see the PEP 343 – The “with” Statement specification section:

The calling convention for mgr.__exit__() is as follows. If the finally-suite was reached through normal completion of BLOCK or through a non-local goto (a break, continue or return statement in BLOCK), mgr.__exit__() is called with three None arguments. If the finally-suite was reached through an exception raised in BLOCK, mgr.__exit__() is called with three arguments representing the exception type, value, and traceback.

As to why you get an index error in 3.7.10: the mock.patch() implementation in older releases used an incorrect implementation that could result in an unexpected exception, and the fix for this was to use an contextlib.ExitStack() instance. The ExitStack context manager uses def __exit__(self, *exc_details): as the method signature and expects the exc_details tuple to have at least 1 element in it, which normally is either None or an exception object. The bug fix is part of Python 3.7.8, which is why you don’t see the same context manager in 3.7.3 as you see in 3.7.10. In the older version of the code, the patcher __exit__() method also uses a catchall *args tuple, but otherwise doesn’t try to index into this tuple.

Advertisement