Reputation: 11228
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
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
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