Discussion:
[Tutor] Guidance on using custom exceptions please
David Aldrich
2015-10-12 14:55:43 UTC
Permalink
Hi

Consider a 'send' method that sends a message to another system via a socket. This method will wait for a response before returning. There are two possible error conditions:


1) Timeout - i.e. no response received

2) Illegal response received

I need to communicate these errors to the caller of send(). So far I have just raised a RuntimeError exception for both errors, and stated what happened like this:

raise RuntimeError("Message timeout")

That's fine if the caller just wants to print the error but not so good if the code needs to act differently according to which error condition occurred.

So, my question is, what's the pythonic way of doing this? Should I subclass RuntimeError for each possible error condition? E.g.:

class MessageTimeoutError(RuntimeError): pass
class IllegalResponseError(RuntimeError): pass

Best regards

David

_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Steven D'Aprano
2015-10-12 23:37:01 UTC
Permalink
Post by David Aldrich
Hi
Consider a 'send' method that sends a message to another system via a
socket. This method will wait for a response before returning. There
[...]
Post by David Aldrich
So, my question is, what's the pythonic way of doing this? Should I
class MessageTimeoutError(RuntimeError): pass
class IllegalResponseError(RuntimeError): pass
I don't think you should be subclassing RuntimeError at all. I'm not
quite sure what exception you should subclass, but I am confident it
shouldn't be RuntimeError.

Help on class RuntimeError in module exceptions:

class RuntimeError(StandardError)
Unspecified run-time error.


Since you are working with sockets, I think a socket error might be most
useful:

import socket # which I expect you are already doing

class MessageTimeoutError(socket.error): pass
class IllegalResponseError(socket.error): pass

Or possibly inherit from the same exception class that socket.error
inherits from: IOError.

I'm not certain that you actually need MessageTimeoutError since the
socket module itself already defines a socket.timeout error that will be
raised on a timeout. Just re-use that.
--
Steve
_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Cameron Simpson
2015-10-12 23:59:01 UTC
Permalink
Post by Steven D'Aprano
Post by David Aldrich
Consider a 'send' method that sends a message to another system via a
socket. This method will wait for a response before returning. There
[...]
Post by David Aldrich
So, my question is, what's the pythonic way of doing this? Should I
class MessageTimeoutError(RuntimeError): pass
class IllegalResponseError(RuntimeError): pass
I don't think you should be subclassing RuntimeError at all. I'm not
quite sure what exception you should subclass, but I am confident it
shouldn't be RuntimeError.
class RuntimeError(StandardError)
Unspecified run-time error.
I tend to use RuntimeError for program logic errors (code paths that shoudn't
happen, like not handling an option combination). I would not use it for this.
Post by Steven D'Aprano
Since you are working with sockets, I think a socket error might be most
import socket # which I expect you are already doing
class MessageTimeoutError(socket.error): pass
class IllegalResponseError(socket.error): pass
Or possibly inherit from the same exception class that socket.error
inherits from: IOError.
I have mixed feeling about this; I feel that socket.error or IOError should
reflect actual OS level or IO subsystem errors, not higher level program
discrepancies such as invalid packet data. I would be included to subclass
StandardError.

Antipattern example: I discovered yesterday that PILLOW raises OSError when it
doesn't recognise an image file's content. I consider that a bad choice; I'd
prefer ValueError probably - there's been no OS level failure like lack of
permission to open the file.

On that basis, I recommend either just raising a ValueError for an invalid
packet or subclassing it.
Post by Steven D'Aprano
I'm not certain that you actually need MessageTimeoutError since the
socket module itself already defines a socket.timeout error that will be
raised on a timeout. Just re-use that.
That seems reasonable, if he's using the recv timeout facility.

Cheers,
Cameron Simpson <***@zip.com.au>
_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Peter Otten
2015-10-13 07:28:28 UTC
Permalink
Post by David Aldrich
Consider a 'send' method that sends a message to another system via a
socket. This method will wait for a response before returning. There are
1) Timeout - i.e. no response received
2) Illegal response received
I need to communicate these errors to the caller of send(). So far I have
just raised a RuntimeError exception for both errors, and stated what
raise RuntimeError("Message timeout")
That's fine if the caller just wants to print the error but not so good if
the code needs to act differently according to which error condition
occurred.
So, my question is, what's the pythonic way of doing this? Should I
class MessageTimeoutError(RuntimeError): pass
class IllegalResponseError(RuntimeError): pass
If you don't want to let the original timeout error bubble up you can create
your own little hierarchy of exceptions:

class ResponseError(Exception):
pass

class TimeoutError(ResponseError):
pass

class BadDataError(ResponseError):
pass

Then the baseclass of ResponseError doesn't matter much as client code that
wants to catch every expected error can catch ResponseError. You can later
add subclasses as needed without breaking this catch-all client.


_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
David Aldrich
2015-10-13 13:43:27 UTC
Permalink
Post by Peter Otten
If you don't want to let the original timeout error bubble up you can create
pass
pass
pass
Then the baseclass of ResponseError doesn't matter much as client code that
wants to catch every expected error can catch ResponseError. You can later
add subclasses as needed without breaking this catch-all client.
Thanks for all the answers to my question, they were all helpful.

I have one more question, which regards style. Suppose my 'send' method is in its own module: TxControl, along with the custom exceptions:

TxControl.py:

class MessageTimeoutError(Exception): pass
class UndefinedMessageTypeError(Exception): pass

def send(msg)
etc.

Then it seems that an importer of that module must include the module name when referencing the exceptions:

import TxControl

try:
send(msg)
except (TxControl.MessageTimeoutError, TxControl.UndefinedMessageTypeError) as err:
# Exception processing

Including 'TxControl' seems a bit tedious, and is even worse if TxControl imports exceptions from yet another module and allows them to pass up the stack.

How would you handle this situation stylistically?

David
_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Danny Yoo
2015-10-13 19:27:11 UTC
Permalink
Post by David Aldrich
import TxControl
send(msg)
# Exception processing
Including 'TxControl' seems a bit tedious, and is even worse if TxControl imports exceptions from yet another module and allows them to pass up the stack.
How would you handle this situation stylistically?
Stylistically, I'd keep it. This doesn't look bad to me. Certain
style guides encourage this, because it becomes easier to see where
values are coming from. One definite perk is that it avoids
conflicting names from different packages.

For example, here's one recommendation:

https://google-styleguide.googlecode.com/svn/trunk/pyguide.html?showone=Packages#Packages


(Commentary: I've been in a world where imports were unqualified by
default, and it was a mess. My programs grew large enough that it
made name conflicts almost inevitable. Qualified names are lengthy,
but they make that problem a non-issue.)
_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Wolfgang Maier
2015-10-13 21:11:12 UTC
Permalink
Post by David Aldrich
class MessageTimeoutError(Exception): pass
class UndefinedMessageTypeError(Exception): pass
def send(msg)
etc.
import TxControl
send(msg)
# Exception processing
Including 'TxControl' seems a bit tedious, and is even worse if TxControl imports exceptions from yet another module and allows them to pass up the stack.
How would you handle this situation stylistically?
In simple cases, I would just refer to them as you describe.
If things get more complex, you may want to refactor your code into a
package anyway, in which case you can define your complete exception
hierarchy in __init__.py (or in another dedicated module) and import
them from there whenever you need them in other modules.


_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Cameron Simpson
2015-10-13 21:29:21 UTC
Permalink
Post by David Aldrich
Thanks for all the answers to my question, they were all helpful.
class MessageTimeoutError(Exception): pass
class UndefinedMessageTypeError(Exception): pass
def send(msg)
etc.
import TxControl
send(msg)
# Exception processing
Including 'TxControl' seems a bit tedious, and is even worse if TxControl
imports exceptions from yet another module and allows them to pass up the
stack.
Without ignoring others' arguments for keeping the full names, you can also go:

from TxControl import send, MessageTimeoutError, UndefinedMessageTypeError

and just use them unqualified like any other name you might import.

The core issue, to me, is: are the exception names nice and descriptive (they
look ok to me) and is the module you're using them in not using other
exceptions of similar name and purpose (i.e. how confusing might the
unqualified named be)?

Also bear in mind that you can do this:

from TxControl import send as tx_send, MessageTimeoutError as TxTimeoutError,
UndefinedMessageTypeError as TxUndefinedMessageType

which gets you more descriptive names for local use. If course, if these
exceptions are widely used in many contexts (many other code pieces) then you
might want to stick with the original names for consistency.

Cheers,
Cameron Simpson <***@zip.com.au>

Go not to the elves for counsel, for they will say both no and yes.
- Frodo, The Fellowship of the Ring
_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor

Danny Yoo
2015-10-13 18:41:20 UTC
Permalink
On Mon, Oct 12, 2015 at 7:55 AM, David Aldrich
Post by David Aldrich
Hi
1) Timeout - i.e. no response received
2) Illegal response received
raise RuntimeError("Message timeout")
Hi David,

According to:

https://docs.python.org/3.5/library/exceptions.html

you can subclass the "Exception" class.

https://docs.python.org/3.5/library/exceptions.html#Exception

That's the one that you probably should subclass from, as you're
defining your own "non-system-exiting" exception. It's
"non-system-exiting" because you expect the caller to have to do some
special behavior when receiving such a condition.

Use Exception as your base. As Peter Otten describes, you can create
your own hierarchy as necessary, but anchor it from Exception first.

RuntimeError is for something that doesn't fit any of the other
categories used by the Standard Library:

https://docs.python.org/3.5/library/exceptions.html#concrete-exceptions

As such, it's probably not something you yourself should be using as a
subclass. You'd expect to see RuntimeError if something truly unusual
is happening within the Python runtime environment. But that's not
the case for the exceptions you're describing: timeout and illegal
arguments are application-level exceptions, not low-level Python
runtime environmental problems.


Hope that clears things up!
_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Continue reading on narkive:
Loading...