简体   繁体   中英

How to catch multiple exception of same type in decorator

I wonder if there is a way to catch all exceptions caused in one decorator in another decorator and handle these exceptions.

However, before coming up with some sort of convoluted solution to my problem, I figured I would ask the experts and see if there is something I'm missing.

My application looks similar to this:

Input will be given as numGen function. This numGen function adds the input to the random number. In my example I am checking for 10 different random numbers.

Note that the input will also be varying as well as the range of random number generation.

It appears that one cannot resume the execution of test once you hit the raise the user defined exception in Python. But in my scenario I want to check error occurred for different input values. But Current implementation is restricting to continue the check error for the different input vectors.

However, before coming up with some sort of convoluted solution to my problem, I figured I would ask the experts and see if there is something I'm missing. Unfortunately, I cannot change this paradigm easily without significant re-factoring.

My application looks similar to this: •Input will be given as numGen function. This numGen function add the input to the random number. In my example I am checking for 10 different random number. •Note Input is also will be varying as well as the range of random number generation

**:Edit exception class

from random import randint
class RND_ERROR(Exception):
    def __init__(self, ErrNum, *arg, **kwargs):
        self.RND_ERR_NR = int(ErrNum)
        self.RND_ERR_MSG = ""         
        if self.RND_ERR_NR == 0x00000000:
            self.RND_ERR_MSG = "NO ERROR"
        elif self.RND_ERR_NR == 0x00000001:
            self.RND_ERR_MSG = "RANDOM NUMBER IS ONE"
        elif self.RND_ERR_NR == 0x00000002:
            self.RND_ERR_MSG = "RANDOM NUMBER IS TWO"
        elif self.RND_ERR_NR == 0x00000003:
            self.RND_ERR_MSG = "RANDOM NUMBER IS THREE"
        else:
            self.RND_ERR_MSG = "RANDOM NUMBER GREATER THAN THREE"
    def __str__(self):
        return "RND ERROR (0x%08X): %s" % (self.RND_ERR_NR,self.RND_ERR_MSG)

    def __repr__(self):
       return self.__str__()



def handleError(func):
    errors =[]
    def wrapper(arg1):
        try:
            result = func(arg1)
        except MyException as e:
            print errors.append(e)
    return wrapper

def findError(func):
    def wrapper(arg1):
        result = func(arg1)
        print result
        for k, v in result.iteritems():
            error_nr = v % 2
            if error_nr ==0:
                pass
            elif error_nr > 0:
                raise RND_ERROR(errornum) #edited lines
    return wrapper

@handleError
@findError
def numGen(input):
    from random import randint
    result= {}
    errors = []
    for i in range(9):
        j = (randint(0,4))
        result[i] = input + j
        result.update()    # This line does nothing...
    return result
if __name__ == '__main__':
    numGen(4)

Function numGen :

This function take the input and generates a 10 random numbers. 10 random numbers are saved in result dictionary with indices. Find error decorator is called base on the result dictionary.

Decorator 1 findError :

On specific condition, it generates an exception for different results, and exceptions are raised, if error occurred.

Decorator 2 handleError :

Catches all the exceptions caused in previous decorators, append to list. reraise later point.

Problem statement:

  1. I am not able to iterate over the dictionary in a try block, So I am not able catch specific error.

  2. I want to check for each input whether error is occurred and if any error occurred raise exception and store the information of which input eg: - raise the exception, for different input. catch the exceptions, and store the input value for which exception occurred as well as exception type(say ).

  3. How to aggregate exceptions by creating an aggregated exception, like MyExceptionsCollection, that would hold the array and raise it instead of printing.

  4. How to reraise exact exception or all exceptions that were occurred

Note: Implementation has to use 2 decorators one for generating errors and catching errors. Because I will have 200- 250 functions, I need to check error for all this functions

Please help how could I achieve this. Any better solutions other than, exceptions are also welcome.

Thanks in advance

You don't need to do anything complicated with exceptions. To think that you do is what creates the problem. What you actually need (my guess, because as other said it's not so clear) is to process the returned values of numGen , and do something (maybe, raising an exception) based on them.

Once you phrase your goal this way, it's clear that you need something much simpler. Just a decorator which stores all the "wrong" values returned by numGen (according to your definition of what's wrong) and allows you to process them later. So here is my solution:

# Using a class as a decorator, so you can easily access its .wrong_values attribute
class ErrorHandler:
     def __init__(self, func):
         self.func = func
         self.wrong_values = []

     def __call__(self, arg):
         # Remove the first line if you want to keep wrong values from previous calls
         self.wrong_values.clear()
         result = self.func(arg)
         self.register_errors(result)
         return result

     def register_errors(self, result):
         wrong_values = [(k, v) for k, v in result.iteritems() if v % 2]
         self.wrong_values += wrong_values

@ErrorHandler
def numGen(input):
    from random import randint
    # You say you want 10 numbers, so the argument to range should be 10
    # And you can use dict-comp, which requires only one line and is faster
    return {i: randint(0, 4) + input for i in range(10)}

if __name__ == '__main__':
    print 'result', numGen(4)
    print 'wrong values', numGen.wrong_values

When I run this, I get

result {0: 4, 1: 8, 2: 6, 3: 8, 4: 7, 5: 5, 6: 6, 7: 5, 8: 6, 9: 5}
wrong values [(4, 7), (5, 5), (7, 5), (9, 5)]

Now you have easy access to numGen.wrong_values ; do whatever you want with them.

Looking back at your problem statement:

Problem statement:

I am not able to iterate over the dictionary in a try block, So I am not able catch specific error.

Not a problem because you don't need a try block.

How to aggregate exceptions by creating an aggregated exception, like MyExceptionsCollection, that would hold the array and raise it instead of printing.

I have no idea what it is to aggregate exceptions. I think it's more sensible to collect values and raise an exception (or whatever) from the collected values. This is achieved.

How to reraise exact exception or all exceptions that were occurred

Again, this is an artificial problem created by thinking that you have to raise exceptions first in order to "aggregate" them. You don't. Instead you aggregate values and process them.

Note: Implementation has to use 2 decorators one for generating errors and catching errors. Because I will have 200- 250 functions, I need to check error for all this functions

I don't see the need for two decorators. If you have different error-catching logic for different functions, just subclass ErrorHandler and override the register_errors method. (One more advantage of using a class, rather than a function, as a decorator.)

I would suggest using generators over throwing exceptions:

class MyException(Exception):pass


def handleError(func):
    errors =[]
    def wrapper(arg1):
        result = func(arg1)

        for err in findError(result):
            errors.append(err)

        print errors
        return result

    return wrapper

def findError(result):
    print result
    for k, v in result.iteritems():
        error_nr = v % 2
        if error_nr ==0:
            pass
        elif error_nr > 0:
            yield MyException

@handleError
def numGen(input):
    from random import randint
    result= {}
    errors = []
    for i in range(9):
        j = (randint(0,4))
        result[i] = input + j
    return result

if __name__ == '__main__':
    numGen(4)

It's hard to understand what you asking for. This is my best guess.

findErrors checks for and collects errors. If there are any, it raises an exception. Otherwise it just returns the results.

handleErrors prints the exception if there was one. Otherwise, it just returns the results.

Because you use the print statement I presume you are using Python 2.7.

class MyException(Exception):pass

def handleError(func):
    def wrapper(arg1):
        try:
            return func(arg1)

        except MyException as e:
            print "Error:", e.args

    return wrapper

def findError(func):
    def wrapper(arg1):
        errors = []
        result = func(arg1)

        for k, v in result.iteritems():
            error_nr = v % 2
            if error_nr > 0:
                errors.append((k, v, error_nr))

        if errors:
            raise MyException, errors

        return result

    return wrapper

@handleError
@findError
def numGen(input):
    from random import randint
    result= {}
    for i in range(9):
        j = randint(0,4)
        result[i] = input + j
    return result

if __name__ == '__main__':
    numGen(4)

@Sana I answered Handing multiple exception of same type and resume execution in python , which you recently posted.

This question seems to be a restatement of your original question, so the original answer still applies. :-)

The code snippet there can be used inside your decorator here.

Remember - Python can't raise multiple errors - the first uncaught error will terminate execution. If you need to know which items have failed, you can collect the errors, then format them as a "master" error.

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