简体   繁体   中英

Python dynamic inheritance from Exceptions

I'm trying to create a custom Exception which can inherit from all the Exceptions. I've used a wrapper, here's my code:

def _MyError(_type, message=None, *args, **kwargs):
    class MyError(_type, object):
        def __init__(self, *_args, **_kwargs):
        super(MyError, self).__init__(self, message, *_args, **_kwargs)
        return

    return MyError(*args, **kwargs)


if(__name__ == '__main__'):
    try:
        raise _MyError(KeyError, message="ooops")
    except MyError as e:
        print "MyError occurred. ", e.message
    except BaseException:
        print "MyError not recognized.\n"

    try:
        raise _MyError(IndexError, message="ooops")
    except IndexError as e:
        print "MyError occurred. ", e.message
    except BaseException:
        print "MyError not recognized.\n"
    
    exit

Output:

MyError not recognized.
MyError not recognized.

Suggestions?

This is a rather tricky problem. The issue is that the very creation of your specialized MyError fails with an IndexError . Because an IndexError is not a KeyError , it passes right through the attempt to catch it and instead is caught as a BaseException .

def _MyError(_type, *args, **kwargs):
    class MyError(_type, object):
        def __init__(self, *_args, **_kwargs):
            super(MyError, self).__init__(self, *_args, **_kwargs)
            # Error on above line (IndexError from kwargs not accepted)

    return MyError(*args, **kwargs) # Error goes through here

try:
    raise _MyError(KeyError, message='hello')
    # raise never gets invoked, IndexError on calling _MyError
except KeyError: # IndexError != KeyError. This clause is skipped
    print 'KeyError'
# except BaseException: 
    # print 'BaseException'
# Commenting out the above lines will throw the error instead of catching it

You should instead create your error like so:

raise _MyError(KeyError, 'hello')

Exceptions does not accept message ; causes super(MyError, self).__init__(self, *_args, **_kwargs) fail.

>>> IndexError(message='asdf')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: exceptions.IndexError does not take keyword arguments

You need to convert message to positional argument:

def _MyError(_type, *args, **kwargs):
    class MyError(_type, object):
        def __init__(self, message=None):
            super(MyError, self).__init__(self, message)

    return MyError(*args, **kwargs)

When I try your code in Python 2.7 I get the following error:

Traceback (most recent call last):
  File "test.py", line 13, in <module>
    except MyError as e:
NameError: name 'MyError' is not defined

This is not surprising, because the name MyError does not exist at the file-level scope where you are trying to catch it.

Also consider that you're creating a new class each time you call the _MyError() function. So even if there were a MyError class available to use in the except block, it would almost certainly be the wrong class (albeit with the same name).

I suspect you might want to create a "root" MyError class, and then create a MyCustomError class in your function. You could then catch the MyError (base) class in your exception block and proceed from there.

EDIT

Per your comment, you might be able to do something like this:

class MyError:
    """Marker class for mixin."""
    pass

def my_error(_type, message=None, *args, **kwargs):
    class _MySubclass(_type, MyError):
        def __init__(self, *_args, **_kwargs):
            super(_MySubclass, self).__init__(message, *_args, **_kwargs)

    return _MySubclass(*args, **kwargs)

try:
    raise my_error(KeyError, message="key error")
except MyError as e:
    print "MyError occurred: ", e.message
except BaseException:
    print "Unknown error:", str(e)

try:
    raise my_error(IndexError, message="index error")
except MyError as e:
    print "MyError occurred: ", e.message
except BaseException:
    print "Unknown error:", str(e)

I suggest you create a class factory to build your error type dynamically, as explained in the official docs .

def error_factory(name, *base_errors):
    """Dynamically create custom Error class."""
    assert all(issubclass(e, Exception) for e in base_errors)

    return type(name, base_errors, {})


try:
    something()
except KeyError:
    MyError = error_factory("MyError", KeyError)
    raise MyError("A problem occured!")  # this error is also a `KeyError`

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM