John Eipe
John Eipe

Reputation: 11228

change to decorator pattern

I'm learning decorators and here I'm trying to change the below to decorator pattern.

def invert(x):
    return 1/x

print invert(5)

can be changed using decorators.

def safe(fun, *args):
    if args[0]!=0:
        return fun(*args)
    else:
        "Division by 0"

def invert(x):
    return 1/x

print safe(invert, 5) 

Using @wapper syntax,

def safe(fun, *args):
    if args[0]!=0:
        return fun(*args)
    else:
        "Division by 0"
@safe
def invert(x):
    return 1/x

print invert(5)

The above code gives error IndexError: tuple index out of range. I'm trying to understand what makes it wrong and how to correct it.

Upvotes: 0

Views: 146

Answers (2)

Lauritz V. Thaulow
Lauritz V. Thaulow

Reputation: 50995

You're doing it wrong.

def safe(fun):
    def wrapper(*args)
        if args[0]!=0:
            return fun(*args)
        else:
            return "Division by 0"
    return wrapper

The function safe is called once for each function you decorate with it. The inner function, which is returned by safe -- here called wrapped -- is then called each time the decorated function is called. It is responsible for calling the original function, or doing something else entirely.

So a decorator takes exactly one argument: the function to be wrapped. BUT, there are decorator functions, which is a function returning a decorator (so one more layer of wrapped functions):

def safe(singularities):
    def decorator(fun):
        def wrapper(*args)
            if args[0] not in singularities:
                return fun(*args)
            else:
                return "Division by 0"
        return wrapper
    return decorator

So the safe decorator function may now return an infinite number of different decorators. Use like this:

@safe([0])
def invert(x):
    return 1 / x

@safe([-1, 1])
def foo(x)
    return 1 / ((x - 1) * (x + 1))

For those wondering why I've posted essentially the same answer as Lattyware after he posted his: That answer did not address all the issues until after I started writing mine, and now I've added more information than is in his, so I will not delete it, since it is not an outright duplicate.

Upvotes: 3

Gareth Latty
Gareth Latty

Reputation: 89027

Your main problem is that a decorator needs to return a function, not a value. The decorator replaces the defined function with a new one based on it.

Beyond that, you do not have a return statement in the else block, merely a string literal. You probably meant to return that.

def safe(fun):
    def f(*args):
        if not args[0] == 0:
            return fun(*args)
        else:
            return "Division by 0"
    return f

Just as a note, I presume this is for the purposes of the exercise only, but errors should not pass silently in Python - the behaviour of emitting an exception is far more useful in general than returning a string on error. In fact, to implement this more naturally for Python you want to follow a ask for forgiveness, not permission mantra:

def safe(fun):
    def f(*args):
        try:
            return fun(*args)
        except ZeroDivisionError:
            return "Division by 0"
    return f

Also, be careful of sweeping statement names like 'safe' - other exceptions could still be thrown.

Upvotes: 4

Related Questions