Comparable exceptions

Hey everyone, I was writing a test the other day and I wanted to assert that some function raises some exception, but with some exact value. Turns out you cannot compare OSError("foobar") to OSError("foobar") as it doesn't have any __eq__. (it's the same for all exceptions?) I think exceptions are great candidates to have an __eq__ method - they already have a __hash__ (that's computed from the contained values) and they are already containers in a way (they all have the `args` attribute). Comparing exceptions in a test assertion seems like a good usecase for this. Thanks, -- Ionel Cristian Mărieș, blog.ionelmc.ro

Actually, they don't have a __hash__ either -- they just inherit the default __hash__ from object, just as they inherit __eq__ from object (and both just use the address of the object). I can see many concerns with trying to define __eq__ for exceptions in general -- often there are a variety of fields, not all of which perhaps should be considered when comparing, and then there are user-defined exceptions which may have other attributes. I don't know what your use case was, but if, as you say, the exceptions you care about already have `args` attributes, maybe you should just compare that (and the type). On Tue, Feb 24, 2015 at 3:43 PM, Ionel Cristian Mărieș <contact@ionelmc.ro> wrote:
-- --Guido van Rossum (python.org/~guido)

Yes i can compare args but that becomes extremely inconvenient whe there's a hide data structure and the exception instances are deep down. What other problems would there be if we add __eq__ on Exception? User exceptions would be free to define their own __eq__. On Wednesday, February 25, 2015, Guido van Rossum <guido@python.org> wrote:
-- Thanks, -- Ionel Cristian Mărieș, blog.ionelmc.ro

What if we make the hash from .args and .__dict__? That would cover most cases. For me it makes sense because they look like containers. There might be cases where a user doesn't want __eq__ or __hash__ for exception instances - how about we enable those methods just for the builtin types? And then the user can explicitly enable them for custom exceptions (there could be a special base ComparableException class that he could inherit from). Thanks, -- Ionel Cristian Mărieș, blog.ionelmc.ro On Wed, Feb 25, 2015 at 1:55 AM, Guido van Rossum <guido@python.org> wrote:

On Wed, Feb 25, 2015 at 6:21 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
Exceptions are not containers. There purpose is not hold args. :)
Except when it's StopIteration and you're using it to smuggle the return value of a generator :-) -- Luciano
-- Luciano Ramalho Twitter: @ramalhoorg Professor em: http://python.pro.br Twitter: @pythonprobr

On Wed, Feb 25, 2015 at 10:43 AM, Ionel Cristian Mărieș <contact@ionelmc.ro> wrote:
Are you sure their hashes are computed from contained values? I just tried it, generated two exceptions that should be indistinguishable, and they had different hashes: rosuav@sikorsky:~$ python3 Python 3.5.0a0 (default:4709290253e3, Jan 20 2015, 21:48:07) [GCC 4.7.2] on linux Type "help", "copyright", "credits" or "license" for more information.
I suspect the last line implies that there's no __hash__ defined anywhere in the exception hierarchy, and it's using the default hash implementation from object(). In any case, those two exceptions appear to have different hashes, and they have the same args and filename. ChrisA

Ok, it looks I got confused about the hash (it's based on the memory address and I wasn't saving the objects around, it was always the same address, oops). Thanks, -- Ionel Cristian Mărieș, blog.ionelmc.ro On Wed, Feb 25, 2015 at 1:57 AM, Chris Angelico <rosuav@gmail.com> wrote:

Doesn't unittest already cover this? with self.assertRaisesRegexp(OSError, '^foobar$'): do_something() On Tue, Feb 24, 2015 at 5:43 PM, Ionel Cristian Mărieș <contact@ionelmc.ro> wrote:
-- Ryan If anybody ever asks me why I prefer C++ to C, my answer will be simple: "It's becauseslejfp23(@#Q*(E*EIdc-SEGFAULT. Wait, I don't think that was nul-terminated." Personal reality distortion fields are immune to contradictory evidence. - srean Check out my website: http://kirbyfan64.github.io/

Yes, it does, however my assertion looks similar to this: assert result == [1,2, (3, 4, {"bubu": OSError('foobar')})] I could implement my own "reliable_compare" function but I'd rather not do that, as it seems like a workaround. Thanks, -- Ionel Cristian Mărieș, blog.ionelmc.ro On Wed, Feb 25, 2015 at 1:57 AM, Ryan Gonzalez <rymg19@gmail.com> wrote:

On Wed, Feb 25, 2015 at 03:03:40PM +0200, Ionel Cristian Mărieș wrote:
Yes, it does, however my assertion looks similar to this:
assert result == [1,2, (3, 4, {"bubu": OSError('foobar')})]
I'm not keen on using assert like that. I think that using assert for testing is close to abuse of the statement, and it makes it impossible to test your code running with -O. But regardless of whether that specific test is good practice or not, I think it is reasonable for exception instances to have a more useful __eq__ than that provided by inheriting from object. Perhaps add something like this to BaseException: def __eq__(self, other): if isinstance(other, type(self)): return self.args == other.args return NotImplemented -- Steve

I am firmly -1 on any changes here. It would have been a nice idea when we first introduced exceptions. Fixing this now isn't going to make much of a difference, and the internal structure of exceptions is murky enough that comparing 'args' alone doesn't cut it. On Wed, Feb 25, 2015 at 5:32 AM, Steven D'Aprano <steve@pearwood.info> wrote:
-- --Guido van Rossum (python.org/~guido)

Hi, On 2015-02-25 08:03, Ionel Cristian Mrie wrote:
Yes, it does, however my assertion looks similar to this:
assert result == [1,2, (3, 4, {"bubu": OSError('foobar')})]
How about reifying the error? I.e., instead of storing the OSError object, you wrap up the error info ('foobar') in a custom Result (e.g.) object that you implement your own comparison for? Then, instead of allowing your function to raise and exception, you catch the exception and wrap its data in a Result object? Regards, Yawar

Actually, they don't have a __hash__ either -- they just inherit the default __hash__ from object, just as they inherit __eq__ from object (and both just use the address of the object). I can see many concerns with trying to define __eq__ for exceptions in general -- often there are a variety of fields, not all of which perhaps should be considered when comparing, and then there are user-defined exceptions which may have other attributes. I don't know what your use case was, but if, as you say, the exceptions you care about already have `args` attributes, maybe you should just compare that (and the type). On Tue, Feb 24, 2015 at 3:43 PM, Ionel Cristian Mărieș <contact@ionelmc.ro> wrote:
-- --Guido van Rossum (python.org/~guido)

Yes i can compare args but that becomes extremely inconvenient whe there's a hide data structure and the exception instances are deep down. What other problems would there be if we add __eq__ on Exception? User exceptions would be free to define their own __eq__. On Wednesday, February 25, 2015, Guido van Rossum <guido@python.org> wrote:
-- Thanks, -- Ionel Cristian Mărieș, blog.ionelmc.ro

What if we make the hash from .args and .__dict__? That would cover most cases. For me it makes sense because they look like containers. There might be cases where a user doesn't want __eq__ or __hash__ for exception instances - how about we enable those methods just for the builtin types? And then the user can explicitly enable them for custom exceptions (there could be a special base ComparableException class that he could inherit from). Thanks, -- Ionel Cristian Mărieș, blog.ionelmc.ro On Wed, Feb 25, 2015 at 1:55 AM, Guido van Rossum <guido@python.org> wrote:

On Wed, Feb 25, 2015 at 6:21 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
Exceptions are not containers. There purpose is not hold args. :)
Except when it's StopIteration and you're using it to smuggle the return value of a generator :-) -- Luciano
-- Luciano Ramalho Twitter: @ramalhoorg Professor em: http://python.pro.br Twitter: @pythonprobr

On Wed, Feb 25, 2015 at 10:43 AM, Ionel Cristian Mărieș <contact@ionelmc.ro> wrote:
Are you sure their hashes are computed from contained values? I just tried it, generated two exceptions that should be indistinguishable, and they had different hashes: rosuav@sikorsky:~$ python3 Python 3.5.0a0 (default:4709290253e3, Jan 20 2015, 21:48:07) [GCC 4.7.2] on linux Type "help", "copyright", "credits" or "license" for more information.
I suspect the last line implies that there's no __hash__ defined anywhere in the exception hierarchy, and it's using the default hash implementation from object(). In any case, those two exceptions appear to have different hashes, and they have the same args and filename. ChrisA

Ok, it looks I got confused about the hash (it's based on the memory address and I wasn't saving the objects around, it was always the same address, oops). Thanks, -- Ionel Cristian Mărieș, blog.ionelmc.ro On Wed, Feb 25, 2015 at 1:57 AM, Chris Angelico <rosuav@gmail.com> wrote:

Doesn't unittest already cover this? with self.assertRaisesRegexp(OSError, '^foobar$'): do_something() On Tue, Feb 24, 2015 at 5:43 PM, Ionel Cristian Mărieș <contact@ionelmc.ro> wrote:
-- Ryan If anybody ever asks me why I prefer C++ to C, my answer will be simple: "It's becauseslejfp23(@#Q*(E*EIdc-SEGFAULT. Wait, I don't think that was nul-terminated." Personal reality distortion fields are immune to contradictory evidence. - srean Check out my website: http://kirbyfan64.github.io/

Yes, it does, however my assertion looks similar to this: assert result == [1,2, (3, 4, {"bubu": OSError('foobar')})] I could implement my own "reliable_compare" function but I'd rather not do that, as it seems like a workaround. Thanks, -- Ionel Cristian Mărieș, blog.ionelmc.ro On Wed, Feb 25, 2015 at 1:57 AM, Ryan Gonzalez <rymg19@gmail.com> wrote:

On Wed, Feb 25, 2015 at 03:03:40PM +0200, Ionel Cristian Mărieș wrote:
Yes, it does, however my assertion looks similar to this:
assert result == [1,2, (3, 4, {"bubu": OSError('foobar')})]
I'm not keen on using assert like that. I think that using assert for testing is close to abuse of the statement, and it makes it impossible to test your code running with -O. But regardless of whether that specific test is good practice or not, I think it is reasonable for exception instances to have a more useful __eq__ than that provided by inheriting from object. Perhaps add something like this to BaseException: def __eq__(self, other): if isinstance(other, type(self)): return self.args == other.args return NotImplemented -- Steve

I am firmly -1 on any changes here. It would have been a nice idea when we first introduced exceptions. Fixing this now isn't going to make much of a difference, and the internal structure of exceptions is murky enough that comparing 'args' alone doesn't cut it. On Wed, Feb 25, 2015 at 5:32 AM, Steven D'Aprano <steve@pearwood.info> wrote:
-- --Guido van Rossum (python.org/~guido)

Hi, On 2015-02-25 08:03, Ionel Cristian Mrie wrote:
Yes, it does, however my assertion looks similar to this:
assert result == [1,2, (3, 4, {"bubu": OSError('foobar')})]
How about reifying the error? I.e., instead of storing the OSError object, you wrap up the error info ('foobar') in a custom Result (e.g.) object that you implement your own comparison for? Then, instead of allowing your function to raise and exception, you catch the exception and wrap its data in a Result object? Regards, Yawar
participants (8)
-
Chris Angelico
-
Ethan Furman
-
Guido van Rossum
-
Ionel Cristian Mărieș
-
Luciano Ramalho
-
Ryan Gonzalez
-
Steven D'Aprano
-
Yawar Amin