Search code examples
pythonfunctionpython-typing

Function parameter dtype declaring doesnt work?


Why doesnt this give back '12'?
The '+' sign should concatenate two strings, not add them.

def foo(a:str, b:str):
    print(a+b)
foo(1,2)
3

Solution

  • That's not what annotations are for. Annotations are metadata, not an instruction to Python to convert data.

    From the Function definitions reference documentation:

    Parameters may have annotations of the form “: expression” following the parameter name. Any parameter may have an annotation even those of the form *identifier or **identifier. Functions may have “return” annotation of the form “-> expression” after the parameter list. These annotations can be any valid Python expression and are evaluated when the function definition is executed. Annotations may be evaluated in a different order than they appear in the source code. The presence of annotations does not change the semantics of a function.

    (Bold emphisis mine).

    For example, the Python type hinting framework uses annotations to attach type information to functions for static analysis, validating that code actually passes in the types that are expected to be passed in.

    Just convert your values explicitly; in the call:

    foo(str(1), str(2))
    

    or in the function itself:

    def foo(a, b):
        print(str(a) + str(b))
    

    or in a decorator:

    import functools
    import inspect
    
    def typeconversion(f):
        """Converts arguments with a callable attached in the parameter annotation"""
        sig = inspect.signature(f)
    
        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            # convert any argument (including defaults), for which there is a
            # callable annotation
            bound = sig.bind(*args, **kwargs)
            bound.apply_defaults()
            args = bound.arguments
            for param in sig.parameters.values():
                if param.annotation is not param.empty and callable(param.annotation):
                    args[param.name] = param.annotation(args[param.name])
    
            # call the function with the converted arguments
            result = f(*bound.args, **bound.kwargs)
    
            # convert the return value
            if sig.return_annotation is not sig.empty and callable(sig.return_annotation):
                result = sig.return_annotation(result)
    
            return result
        return wrapper
    

    Demo:

    >>> @typeconversion
    ... def foo(a: str, b: str) -> int:
    ...     return a + b
    ...
    >>> foo(42, 101)
    42101