17

I'm trying to learn Decorators . I understood the concept of it and now trying to implement it.

Here is the code that I've written The code is self-explanatory. It just checks whether the argument passed in int or not.

def wrapper(func):
    def inner():
        if issubclass(x,int): pass
        else: return 'invalid values'

    return inner()

@wrapper
def add(x,y):
    return x+y

print add('a',2)

It's throwing error saying global name 'x' is not defined. I understand that it is not defined under inner, but didnt know how to rectify this code? Where I'm going wrong?

python-coder
  • 2,128
  • 5
  • 26
  • 37

5 Answers5

23

Your decorator should look like this:

def wrapper(func):
    def inner(x, y): # inner function needs parameters
        if issubclass(type(x), int): # maybe you looked for isinstance?
            return func(x, y) # call the wrapped function
        else: 
            return 'invalid values'
    return inner # return the inner function (don't call it)

Some points to note:

  • issubclass expects a class as first argument (you could replace it with a simple try/except TypeError).
  • the wrapper should return a function, not the result of a called function
  • you should actually call the wrapped function in the inner function
  • your inner function didn't have parameters

You can find a good explanation of decorators here.

Shark Lasers
  • 441
  • 6
  • 15
sloth
  • 99,095
  • 21
  • 171
  • 219
  • I'd use `type(x)` instead of `x.__class__`, if not using `isinstance()`. – Martijn Pieters Jan 06 '14 at 08:03
  • Thanks for rectifying the code. I've doubt in the concept of wrapper function. Is it actually necessary to write a nested function to check whether the values are int or not. I mean can't it be done without using a nested function. For eg:- `def wrapper(x,y): if issubclass(x.__class__, int): return func(x, y) else: return 'invalid values' ` – python-coder Jan 06 '14 at 08:04
  • @MartijnPieters Thanks for the comment; using `type(x)` is better than `x.__class__`. Fixed it. – sloth Jan 06 '14 at 08:06
  • @python-coder No, that will not work. When you use `wrapper` as a decorator, it will be called with the wrapped function as first argument. – sloth Jan 06 '14 at 08:08
  • 2
    To be more upwards-compatible, you could use a flexible list of arguments: `def inner(x, *args, **kwargs):` and `return func(x, *args, **kwargs)`. – Alfe Jan 06 '14 at 08:42
3

There are three issues I see with your current code.

First, you're calling the inner function, rather than returning a reference to it.

Second, your inner function doesn't take the same arguments as the function you're decorating. In this case, you need to take at least the x argument explicitly (some inner functions can use *args and **kwargs exclusively, but aparently not yours).

Lastly, you're never calling the wrapped function. While this isn't strictly required (it might be useful to swap out a method with a decorator during development), usually you want to call the function at some point during the inner function's code.

So, to wrap the whole thing together, I think you want your code to be something like this:

def wrapper(func):
    def inner(x, y):
        if issubclass(x, int): # issue 2
            return func(x, y) # issue 3
        else:
            return "invalid values" # consider raising an exception here instead!

    return inner # issue 1
Blckknght
  • 100,903
  • 11
  • 120
  • 169
  • Thanks for rectifying the code. I've doubt in the concept of wrapper function. Is it actually necessary to write a nested function to check whether the values are int or not. I mean can't it be done without using a nested function. For eg:- `def wrapper(x,y): if issubclass(x.__class__, int): return func(x, y) else: return 'invalid values' ` – python-coder Jan 06 '14 at 08:08
  • 1
    The nested functions are necessary to make it work as a decorator. Your alternative version could work if you wanted the users of yoru code to call `wrapper` directly, rather than calling `func` by its usual name (though you'd need to name it directly in `wrapper`). Note that your naming of the functions is a bit unusual. Usually the outer function is named for what the purpose of the decorator is (e.g. `arg_type_check` or something) and `wrapper` is the name for the inner function that the decorator returns, "wrapped" around the original function. – Blckknght Jan 06 '14 at 08:20
0

this might work.

def wrapper(func):
    def inner(*args,**kwargs):
        if ((args[0] is int) and (args[1] is int)): pass
        else: return 'invalid values'
    return inner
@wrapper
def add(x,y):
    return x+y
print add('a',2)
Kartik
  • 8,347
  • 39
  • 73
0

You may also raise an exception if you would like to terminate the add method properly if type check fails. like this

def check_int_types(func):
    def type_checker(x, y):
        if issubclass(type(x), int) and issubclass(type(y), int):
            return func(x, y)
        raise Exception("Invalid types: {}, {}".format(x, y))
    return type_checker

@check_int_types
def add(a, b):
    return a + b

def main():
    x = y = 15.0
    print add(x, y)

if __name__ == '__main__':
    main()

Result:

Traceback (most recent call last):
  File "deco_test.py", line 17, in <module>
    main()
  File "deco_test.py", line 14, in main
    print add(x, y)
  File "deco_test.py", line 5, in type_checker
    raise Exception("Invalid types: {}, {}".format(x, y))
Exception: Invalid types: 15.0, 15.0
Swadhikar
  • 2,152
  • 1
  • 19
  • 32
-2

What about this .

def wrapper(func):
    def inner():
        if isinstance(func,int):
                return func(x, y)
        else: return 'invalid values'

    return inner()
RnD
  • 15
  • 1
  • 6
  • It's bad. That `wrapper` should return a function in order to work as a decorator. Your solution returns the *result* of a call to `inner`, i. e. you will return the result of a call of `func` or a string (`'invalue values'`). Neither is a function. – Alfe Jan 06 '14 at 08:44