0

I am going to write a function decorator that takes an argument and returns a decorator that can be used to control the type of the argument to a one-argument function. I expect a raising error happens in case the passed argument be in a wrong type.

def typecontrol(num_type): 
    def wrapper_function(num):
        if isinstance(num, num_type):
            return num
        else:
            raise TypeError

    return wrapper_function

@typecontrol(float)
def myfunc(num):
    print(num)

I expect for example, myfunc(9.123) should print 9.123 and myfunc(9) should raise an error. But it always raises type error.

ElisaFo
  • 130
  • 13
  • 5
    Does this answer your question? [Decorators with parameters?](https://stackoverflow.com/questions/5929107/decorators-with-parameters) – Ayoub Dec 20 '19 at 15:25
  • 2
    `def myfunc(num: float): ...` and using something like `mypy` to check your code would be a lot simpler and efficient. – chepner Dec 20 '19 at 15:26
  • 1
    So, you are trying to implement static typing into a duck typing language? – Klaus D. Dec 20 '19 at 15:27
  • 1
    @KlausD. It's not really static typing. The type passed to `typecontrol` might not be known at compile time, so it's really a form of restricted dynamic typing. That said, *actual* static typing via `mypy` is probably sufficient for the OP. – chepner Dec 20 '19 at 15:31
  • @chepner I'm a Python developer, I have no idea what "compile time" is. ;) And BTW `mypy` is a tool from someone who misunderstood type annontations for people who misunderstood type annotations. – Klaus D. Dec 20 '19 at 15:59
  • @KlausD. Type annotations are what they are. I don't think anyone has ever claimed that `mypy` makes Python a statically typed language, rather than a tool that provides some of the benefits of static typing in a language that is, and will remain, dynamically typed. – chepner Dec 20 '19 at 16:03

2 Answers2

2

typecontrol will be a function that returns the decorator, not the decorator itself. You need an extra nested function:

def typecontrol(num_type):
    def decorator(f):
        def wrapper_function(num):
            if isinstance(num, num_type):
                f(num)
            else:
                raise TypeError
        return wrapper_function
    return decorator

@typecontrol(float)
def myfunc(num):
    print(num)

The wrapper function will take care of calling the wrapped function if the typecheck passes, rather than returning the typechecked argument.

In your code as written, num is actually the function you are decorating, so you are checking if a function is an instance of (in this case), float.

Some examples:

>>> myfunc(3.0)
3.0
>>> myfunc("foo")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "tmp.py", line 7, in wrapper_function
    raise TypeError
TypeError
chepner
  • 497,756
  • 71
  • 530
  • 681
0

As others have commented, a good explanation on writing decorators with arguments is found here.

However, it seems like you want to enforce types (I've gone down a similar rabbit hole before in Python) so depending on your use case would I possibly recommend two options:

  1. If you want to make sure that your program is typed correctly prior to runtime, use the mypy static type checker.
  2. If you need to parse/validate input values at runtime, I would highly suggest the pydantic package. It allows you to create "struct-like" objects (similar to a NamedTuple, or a dataclass) that will enforce runtime type checks and will appropriately coerce the input at runtime using Python 3.6+ type hints. Documentation and some helpful examples can be found here.

Depending on your use case, I hope either of these two help!

yanniskatsaros
  • 338
  • 4
  • 6