Miguel Gonzalez
Miguel Gonzalez

Reputation: 773

Try each function of a class with functools.wraps decorator

I'm trying to define a decorator in order to execute a class method, try it first and, if an error is detected, raise it mentioning the method in which failed, so as to the user could see in which method is the error.

Here I show a MRE (Minimal, Reproducible Example) of my code.

from functools import wraps

def trier(func):
    """Decorator for trying A-class methods"""
    @wraps(func)
    def inner_func(self, name, *args):
        
        try:
            func(self, *args)
        
        except:
            print(f"An error apeared while {name}")
    
    return inner_func
    
class A:
    def __init__(self):
        self._animals = 2
        self._humans = 5
    
    @trier('getting animals')
    def animals(self, num):
        return self._animals + num
    
    @trier('getting humans')
    def humans(self):
        return self._humans

A().animals

Many errors are raising, like:

TypeError: inner_func() missing 1 required positional argument: 'name'

or misunderstanding self class with self function.

Upvotes: 6

Views: 1430

Answers (3)

hostingutilities.com
hostingutilities.com

Reputation: 9509

As an alternative to Stefan's answer, the following simply uses @trier without any parameters to decorate functions, and then when printing out the error message we can get the name with func.__name__.

from functools import wraps

def trier(func):
    """Decorator for trying A-class methods"""
    @wraps(func)
    def inner_func(self, *args, **kwargs):

        try:
            return func(self, *args, **kwargs)

        except:
            print(f"An error apeared in {func.__name__}")

    return inner_func

class A:
    def __init__(self):
        self._animals = 2
        self._humans = 5

    @trier
    def animals(self, num):
        return self._animals + num

    @trier
    def humans(self):
        return self._humans

print(A().animals(1))

I also fixed a couple of bugs in the code: In trier's try and except the result of calling func was never returned, and you need to include **kwargs in addition to *args so you can use named parameters. I.e. A().animals(num=1) only works when you handle kwargs.

Upvotes: 4

Burakhan Aksoy
Burakhan Aksoy

Reputation: 323

I would do this like this, hope it helps.

from functools import wraps
import sys


def trier(func):
    """Decorator for trying A-class methods"""
    @wraps(func)
    def inner_func(self, *args, **kwargs):
        print(f'Calling {func.__name__}')
        try:
            func(self, *args, **kwargs)

        except:
            print(f"An error apeared on function : {func.__name__}")
            e = sys.exc_info()[2]
            raise Exception(f"Exception occured on line: {e.tb_next.tb_lineno}")

    return inner_func


class A:
    def __init__(self):
        self._animals = 2
        self._humans = 5

    @trier
    def get_animals(self, num):
        return self._animals + num

    @trier
    def get_humans(self):
        return self._humans

    @trier
    def function_raising_exception(self):
        raise Exception('This is some exception')


if __name__ == "__main__":
    a = A()
    a.get_animals(2)
    a.function_raising_exception()

Using e = sys.exc_info()[2], you can get the traceback message as well and point to the line at which the exception is occurred.

It's worth noting that when a decorator is written, it should be applicable to other functions that you'd use in your app.

Upvotes: 2

Stefan B
Stefan B

Reputation: 1677

For decorators with parameters, you need one more level of nesting:

from functools import wraps


def trier(name):
    def wrapper(func):
        @wraps(func)
        def inner(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except:
                print(f"An error apeared while executing {name!r}")
        return inner
    return wrapper


class A:

    def __init__(self):
        self._animals = 2
        self._humans = 5

    @trier('getting animals')
    def animals(self, num):
        return self._animals + num

    @trier('getting humans')
    def humans(self):
        return self._hoomans # wrong attribute name


a = A()
a.humans() # An error apeared while executing 'getting humans'

Upvotes: 2

Related Questions