Maybe I missed the question, but why not:
class MyException(Exception):
pass
To override something (or pass extra args), do this:
class ValidationError(Exception):
def __init__(self, message, errors):
# Call the base class constructor with the parameters it needs
super().__init__(message)
# Now for your custom code...
self.errors = errors
That way you could pass dict of error messages to the second param, and get to it later with e.errors.
In Python 2, you have to use this slightly more complex form of super():
super(ValidationError, self).__init__(message)
Answer from gahooa on Stack OverflowMaybe I missed the question, but why not:
class MyException(Exception):
pass
To override something (or pass extra args), do this:
class ValidationError(Exception):
def __init__(self, message, errors):
# Call the base class constructor with the parameters it needs
super().__init__(message)
# Now for your custom code...
self.errors = errors
That way you could pass dict of error messages to the second param, and get to it later with e.errors.
In Python 2, you have to use this slightly more complex form of super():
super(ValidationError, self).__init__(message)
With modern Python Exceptions, you don't need to abuse .message, or override .__str__() or .__repr__() or any of it. If all you want is an informative message when your exception is raised, do this:
class MyException(Exception):
pass
raise MyException("My hovercraft is full of eels")
That will give a traceback ending with MyException: My hovercraft is full of eels.
If you want more flexibility from the exception, you could pass a dictionary as the argument:
raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})
However, to get at those details in an except block is a bit more complicated. The details are stored in the args attribute, which is a list. You would need to do something like this:
try:
raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})
except MyException as e:
details = e.args[0]
print(details["animal"])
It is still possible to pass in multiple items to the exception and access them via tuple indexes, but this is highly discouraged (and was even intended for deprecation a while back). If you do need more than a single piece of information and the above method is not sufficient for you, then you should subclass Exception as described in the tutorial.
class MyError(Exception):
def __init__(self, message, animal):
self.message = message
self.animal = animal
def __str__(self):
return self.message
naming booleans
def __init__( ... , info: bool = False):
That's just weird. It looks like we're going to have some descriptive information supplied there, and then it turns out it's a flag.
Please use a name like want_info, which suggests it's a flag.
Or better, use the conventional name for this: verbose
use Path for a path
self.file_name = "<unknown file>"
Typing that as str seems unfortunate; better to make it a Path.
Also, if we really need to represent "unknown",
then None would be the usual approach.
It would indicate that we do not yet have a valid file name.
I do appreciate that the constructor always exits with the same set of object attributes defined, even in the event of an error.
extra refcount decrement
This seems pointless:
del frame_error
del frame_current
It looks like java code that assigns frame_current = null;
to ensure eventual garbage collection.
And yes, I understand we don't want spurious references
keeping a frame alive for "too long".
Their refcounts will go to zero in a couple of lines when we return.
It's not like it matters whether they exist during the
linecache.getline() call. Simply elide the del statements.
spurious try
Checking whether currentframe() gave us None
is essentially asking "are we running under cPython?",
which seems legitimate.
But then the try seems to be trapping AttributeError
in case .co_name or similar does not exist.
That sounds like a "cannot happen" case to me.
Please use except AttributeError: if that was the true concern.
And in any event the frame_{error,current} refcounts
shall be decremented to zero upon leaving this scope,
so no need for try / finally.
consistent newline
It seems unfortunate that str(exc) will end with "\n"
in the verbose case but not in the default case.
motivation
class PayrollError(Exception):
pass
class NoFundsError(PayrollError):
pass
class EmployeeIdNotFoundError(PayrollError):
pass
or if it’s something unnecessary or not recommended
Generally, I don't get why an app author would
want to raise your CustomError.
I mean, apps routinely define an app-specific hierarchy
such as the example I supplied.
Once we have seen that calling code can actually
retry / recover from certain conditions,
then defining something more specific than ValueError makes sense.
And the tree of error classes conveniently lets a caller
catch several things or just one.
But the OP code is mostly concerned with inspecting what's on the call stack. By default the interpreter displays a nice backtrace already. It feels like the code we see here belongs up in the app's top-level default error handler which is responsible for logging what happened.
As a maintainer, I feel this code would hide important details from me. When something has gone horribly wrong in an unanticipated way, I really want to see all the details of a backtrace, not just the subset that an app author anticipated would be relevant.
If there is some other audience for these diagnostic messages,
such as web client end users, then that should be explicitly
written down in """docstrings""" or # comments.
I have a couple reservations here.
In the provided example, CustomError is used as a snap-in replacement for ValueError. The error message already makes it clear where the error comes from, because it is set by the programmer. So the exception handler does not provide a lot of additional insight.
Should you even raise an exception in this case? I find that debatable, because you can easily prevent the error in the first place by checking the value. In a real scenario, you might want to perform even more checks.
That being said, raising an exception is useful when you want to add an entry to the error logs, or when you want some corrective action to take place, like a cleanup routine (even though a context manager or a Finally clause can take care of that).
Minor points
Some statements can be simplified just a little:
self.source_line = linecache.getline(self.file_name,self.line).strip()
if not self.source_line:
self.source_line = "<unavailable
Slightly more succinct:
self.source_line = linecache.getline(self.file_name,self.line).strip() or "<unavailable source>"
However, there is a possible flaw here. If linecache.getline returns None, then the .strip you are applying could fail and raise an exception. But wait, let's look at the docs, because I admit I am not familiar with this lib. Sounds like you are in the clear here:
Get line lineno from file named filename. This function will never raise an exception — it will return '' on errors (the terminating newline character will be included for lines that are found).
I believe the frame object properties eg f_lineno etc should all be present, so hopefully no surprises here.
Instead of:
self.file_name = "<unknown file>"
self.func_name = "<unknown func>"
I would use None as a value. That simplifies parsing, and makes it clear that the value is not initialized or not yet known. The programmer retrieving these values from the exception can still reformat the error message as desired.
In __repr__ you use self.__class__.__name__ to identify your class, good idea. You can do the same in the __str__ method.
Stack trace
In more complex situations, the full stack trace is really helpful to have the context of the error, so that the programmer can dig hard into the logs and fix the issue. Perhaps you should consider making it available as an option. The idea should be to add more context and insight, not suppress useful information.
If the intention is really to generalize usage of this class, as an all-purpose exception handler, this could be problematic because we may want to handle exceptions differently, depending on their type. Some exceptions are recoverable, like transient network errors, whereas other exceptions are more fatal and point to a flaw in the program that cannot be easily managed.
Hello everyone,
This week, I worked through the various aspects of Exception Handling in Python. I have to admit, I never thought I would have so much fun learning so many new things about handling Python exceptions and especially defining custom exception classes. So I decided to offer my 2 cents on the topic.
Check out my video on Defining Custom Exception Class and its advantages here.
If you prefer reading blogs, you can find it here.
All discussions are welcome.