28

One of the new features in python3.5 is type hinting. For example the code below is valid now:

def greeting(name: str) -> str:
    return 'Hello ' + name

But, as I understand, it doesn't check anything by itself and also is interpreted absolutely the same way as this:

def greeting(name):
    return 'Hello ' + name

and was implemented mostly to help static analyzers (and to make code that is easier to understand). But is there (or is planned to be implemented in the future) any way (maybe by using some third-party libraries) to make python throw errors when an argument of an invalid type is passed to a function with annotated argument types (using only type-hinting syntax)?

Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
Ilya Peterov
  • 1,975
  • 1
  • 16
  • 33

5 Answers5

19

Type hints implement PEP 0484 which explicitly lists as a non-goal:

While the proposed typing module will contain some building blocks for runtime type checking -- in particular the get_type_hints() function -- third party packages would have to be developed to implement specific runtime type checking functionality, for example using decorators or metaclasses. Using type hints for performance optimizations is left as an exercise for the reader.

From this it seems to follow that the Python developers have no plan to add the functionality that you seek. The quote mentions decorators and that does seem the way to go. In concept it seems straightforward -- the decorator would use get_type_hints() on the function to be decorated and would iterate through the arguments, checking their types against any hints, either throwing an error if there is a clash or simply passing on the arguments to the function. This would be similar to pzelasko's answer but with the decorator using the hints to automatically handle the boiler-plate code. The simplest approach would be to simply vet the arguments, though you should also be able to make a decorator which would raise an error if the return type clashes with the hint. I don't yet have Python 3.5 and don't have the time to pursue it -- but it seems like a good learning exercise for someone who wants to learn about both decorators and type hints. Perhaps you can be one of the "third parties" the PEP alludes to.

John Coleman
  • 51,337
  • 7
  • 54
  • 119
4

If you use Python3.6 annotations, you can use typeguard decorators: https://typeguard.readthedocs.io/en/latest/userguide.html#using-the-decorator

NB: This should only be a "debug" or "testing" tool, not a production one. Thus, they advise to add the -O option to python to run without in production.

It does not check inline variable annotation checks automatically, only function parameters, function return and object types.

From the doc:

from typeguard import typechecked

@typechecked
def some_function(a: int, b: float, c: str, *args: str) -> bool:
    ...
    return retval

@typechecked
class SomeClass:
    # All type annotated methods (including static and class methods and properties)
    # are type checked.
    # Does not apply to inner classes!
    def method(x: int) -> int:
        ...

You may also automate this to all the functions with:

with install_import_hook('myapp'):
    from myapp import some_module
Vincent J
  • 4,968
  • 4
  • 40
  • 50
  • I tested it. It works nicely, however, I wonder about the overhead for production code ... – Manuel Yguel Mar 16 '22 at 09:37
  • @ManuelYguel you should see almost no difference on production code, since you would use -O option which deactivate these checks. They are supposed to be only used for development purposes. – Vincent J Mar 16 '22 at 20:45
1

I think the simplest way is to check the type:

def greeting(name):
    if not isinstance(name, str):
        raise TypeError('Expected str; got %s' % type(name).__name__)
    return 'Hello ' + name
pzelasko
  • 2,082
  • 1
  • 16
  • 24
  • Yes, that's what I usually do, though it would be nice if there was a possibility to make python treat the code in my first example the same way as it treats the code in your answer. – Ilya Peterov Sep 19 '15 at 11:19
  • Edited the question a little to make it clearer that I want a solution using type-hinting syntax. Thank you for the reply anyway:) – Ilya Peterov Sep 19 '15 at 11:23
  • 3
    `type()` won't work for sub classes. I think using [isinstance()](https://stackoverflow.com/a/152596/595416) is the preferred method. – Jon Nalley Jul 19 '18 at 17:12
  • 1
    `isintstance()` -> `isinstance()`, please correct – Ivan Zhovannik Jul 24 '21 at 21:16
1

Since the release of python 3.7, it has been possible to do this using functools.singledispatch.

from functools import singledispatch

@singledispatch
def greet(arg: object):
    raise NotImplementedError(f"Don't know how to greet {type(arg)}")

@greet.register
def _(arg: str):
     print(f"Hello, {arg}!")
    
@greet.register
def _(arg: int):
    print(', '.join("Hello" for _ in range(arg)), "!")

greet("Bob")          # string implementation is called — prints "Hello, Bob!"
greet(4)              # int implementation is called — prints "Hello, Hello, Hello, Hello!"
greet(["Alice, Bob"]) # no list implementation, so falls back to the base implementation — will raise an exception

In the above example, a base implementation is registered that will raise NotImplementedError. This is the "fallback" implementation that is returned if none of the other implementations is suitable.

Following the definition of the base implementation, an arbitrary number of type-specific implementations can be registered, as shown in the example — the function behaves completely differently depending on the type of the argument that is fed to it, and the @singledispatch method uses type annotations to register a certain implementation of a function with a certain type.

A singledispatch function can have any number of arguments, but only the type annotation for the first argument is relevant to which implementation is called.

Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
0

One can use the library https://github.com/h2oai/typesentry for doing this on runtime.

Dark Light
  • 1,210
  • 11
  • 19