Type conversion using type hinting

39 views Asked by At

Say, I have a function that should do some internal stuff and display the provided text:

def display_text(text: str):
  ...
  print(text)

There's also a class with a convert() method:

class String:
  def __init__(self, string: str):
    self.string = string
  
  def convert(self):
    return self.string

Now, can you type hint text argument in display_text with String, but if the provided parameter will be str, call convert and assign the returned value to text? Like that:

def display_text(text: String):
  ...
  print(text)

It should be done without any additional code in a display_text function, just with type hinting. I've seen that in some libs but couldn't figure out how does it work.

I tried searching through some libraries' (e.g. discord.py converters) code, searching similar questions on StackOverflow, only found out about typing.Protocol but still no idea how this conversion is done.

1

There are 1 answers

2
AKX On BEST ANSWER

Can you type hint text argument in display_text with String, but if the provided parameter will be str, call convert and assign the returned value to text without any additional code in a display_text function, just with type hinting

No, not just with type hinting, since type annotations are absolutely inert at runtime and do nothing (and with from __future__ import annotations, they're not even evaluated). If this is a trick question and that "in a display_text function" was the catch, then yes, you could @decorate your functions (or decorate a class holding them, or use a metaclass) to wrap functions using type annotations to cast arguments if needed.

An example of such a decorator:

import dataclasses
import inspect
from functools import wraps
from typing import Any


def convert_args(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        sig = inspect.signature(fn)
        bound_args = sig.bind(*args, **kwargs)
        bound_args.apply_defaults()
        for name, val in bound_args.arguments.items():
            param = sig.parameters[name]
            if hasattr(param.annotation, "convert"):
                bound_args.arguments[name] = param.annotation.convert(val)
        return fn(*bound_args.args, **bound_args.kwargs)

    return wrapper


@dataclasses.dataclass
class Shouty:
    val: str

    @classmethod
    def convert(cls, val: Any):
        return Shouty(val=str(val).upper())


@dataclasses.dataclass
class Shorten:
    val: str

    @classmethod
    def convert(cls, val: Any):
        return Shorten(val=str(val)[::2])


@convert_args
def display_text(arg1: Shouty, arg2: Shorten):
    print(locals())


display_text("hello", "world, this is an example")

This prints out

{'arg1': Shouty(val='HELLO'), 'arg2': Shorten(val='wrd hsi neape')}