What is the better way to organize exception in a Python project? What is the right way to use exception description?For example I have a function that parse email and return some data from it's attachment. It should raise an exception that specify and explain an error in a better way.Options:Use one general exception for a process and it's message to specify the error:    class ParseMailError(Exception):        pass    def get_parsed_mail(mail):        if not mail.attachment:            raise ParseMailError('No attachment')        if not check_some_necessary_fields_format(mail):            raise ParseMailError('Invalid attachment field format')        ...For every case create an exception:    class ParseMailError(Exception):        pass    class MailNoAttachmentError(ParseMailError):        pass    class MailInvalidFieldError(ParseMailError):        pass    def get_parsed_mail(mail):        if not mail.attachment:            raise MailNoAttachmentError        if not check_some_necessary_fields_format(mail):            raise MailInvalidFieldError        ...Is there any explanation of how to make it better?
Is it better to have many specified exceptions or some general that are raised with specified description?
python;exceptions
Occam's razor:More things should not be used than are necessary.Python gives you the ability to subclass exceptions. That extensibility is great, when you need it. But it's rare you actually need to do that. Often you can get just as much error reporting clarity through customizing the message of a standard exception type. E.g.:def get_parsed_mail(mail):    if not mail.attachment:        raise ValueError('No attachment')    if not check_some_necessary_fields_format(mail):        raise ValueError('Invalid attachment field format')Standard exceptions belong to a well-structured, battle-proven hierarchy that many developers have studied and more-or-less understand. Custom exceptions do help you signal the module that generated the error (this error came from MailParser!) and as well as its kind, but often that information is overkill. Consider:try:    mail_parts = ParseMail.parse(raw_message)except Exception as e:    ...Viewed in the context it will be used, the source isn't a mystery. You know from context that the error came from ParseMail.  You know that whether it's a customized MailNoAttachmentError or merely a ValueError.Other than source, different exception types can help you figure the kind of error. What happened, exactly? The real question is, does distinguishing those types help respond to error conditions? Do you expect there to be error handlers for each of these different types of error?try:    mail_parts = ParseMail.parse(raw_message)except MailNoAttachmentError as e:    ...except MailInvalidFieldError as e:    ...Is there anything that a program can do to correct those individual cases? Or are you just reporting out to the developer / user? If just reporting to a human, customizing the exception's message is at least as important as the type of the exception. Probably more so.So custom exceptions are only occasionally necessary. Piggybacking standard exceptions, but using your own customized messages, generally works just as well and is simpler. But if you feel there is virtue in more precise error source/type signaling, that's fine too. A single kind of custom error, usually subclassed off a common error like ValueError, suffices for most modules, e.g.:class ParseMailError(ValueError):    passIf you wanted to go full Java-esque exception diversity / precision, you can create your own ParseMail exception hierarchy blended with the standard hierarchy through multiple inheritance:class ParseMailError(Exception):    passclass MailNoAttachmentError(ValueError, ParseMailError):    passclass MailInvalidFieldError(TypeError, ParseMailError):    pass   