Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is typed implicit conversion (coercion) in Python 3.x possible?

Is it possible to implement custom automatic/implicit conversion (aka coercion) in Python 3.6+ that won't make mypy and other static analyzers sad? An example would be a def(foo: A), and given def b_to_a(b: B) -> A, is there a way I could potentially write foo(some_b) (where some_b: B ) instead of foo(b_to_a(some_b))?

I think there are definitely some good ways to do this in the dynamics of Python (tacking on members to classes that include converters, for instance), or even tacking on such converters to the function object itself so that it can handle conversion for selected types, but my current understanding of Python types makes me thing it would not satisfy mypy and the like.

For comparison, see Scala's implicit conversions.

like image 484
bbarker Avatar asked Jun 19 '18 01:06

bbarker


People also ask

Is there type coercion in Python?

Many programming languages have something called type coercion; it's where the language will implicitly convert one object to another type of object in certain circumstances. Python does not have type coercion.

Does Python support implicit type conversion?

Implicit Type Conversion is automatically performed by the Python interpreter. Python avoids the loss of data in Implicit Type Conversion. Explicit Type Conversion is also called Type Casting, the data types of objects are converted using predefined functions by the user.

What is type conversion coercion How does Python perform it?

Type conversion is the process of converting a data type into another data type. Implicit type conversion is performed by a Python interpreter only. Explicit type conversion is performed by the user by explicitly using type conversion functions in the program code. Explicit type conversion is also known as typecasting.

What is type coercion in Python with example?

For example, in 3+4.5, each argument is of a different type (one int, one float), and both must be converted to the same type before they can be added or it will raise a TypeError. Coercion between two operands can be performed with the coerce built-in function; thus, 3+4.5 is equivalent to calling operator.


1 Answers

Here's an implementation of this feature I came up with. We keep a dictionary of single-dispatch converters for types we know the "implicit" conversions for. We add converters to this using the @implicit decorator.

We then have a @coerce decorator that can inspect the function annotations at runtime, get the appropriate converters and apply the conversions. Below is the framework:

from functools import wraps, singledispatch
from inspect import signature
from collections import OrderedDict

converters = {}

def implicit(func):
    ret = func.__annotations__.get('return', None)
    if not ret or len(func.__annotations__) != 2:
        raise ValueError("Function not annotated properly or too many params")
    if ret not in converters:    
        @singledispatch
        def default(arg):
            raise ValueError("No such converter {} -> {}".format(type(arg).__name__, ret.__name__))    
        converters[ret] = default
    else:
        default = converters[ret]
    t = next(v for k, v in func.__annotations__.items() if k != 'return')
    default.register(t)(func)
    return wraps(func)(default)

def convert(val, t):
    if isinstance(val, t):
        return t
    else:
        return converters[t](val)


def coerce(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        sig = signature(func)
        bound = sig.bind(*args, **kwargs)
        bound.apply_defaults()
        bound.arguments = OrderedDict(
            (param, convert(val, sig.parameters[param].annotation)) 
            for param, val in bound.arguments.items())
        return func(*bound.args, **bound.kwargs)    
    return wrapper

And an example:

from typing import Tuple, Type


@implicit
def str_to_int(a: str) ->  int:
    return int(a)

@implicit
def float_to_int(a: float) -> int:
    return int(a)

@coerce
def make_ints(a: int, b: int) -> Tuple[Type, Type]:
    return (type(a), type(b))

print(make_ints("20", 5.0))
# (<class 'int'>, <class 'int'>)
like image 187
Patrick Haugh Avatar answered Sep 20 '22 04:09

Patrick Haugh