Type annotation with multiple types in **kwargs

Viewed 7152

I'm trying out using Python's type annotations with abstract class. My __init__ function looks like this:

from abc import ABCMeta

class SomeClass(object, metaclass=ABCMeta):
    def __init__(self, *args, **kwargs):
        print("Initiating %s object.", self.__class__.__name__)

        self.username = kwargs['data']
        assert isinstance(self.username, str)

        is_premioum = kwargs.get('premioum', False)

        self.money_investmant = kwargs.get('investmant')
        if isinstance(self.money_investmant, str):
            self.money_investmant = float(self.money_investmant)

As you can see, kwargs could contain arguments from a several number of types- float, bool and str.

Now, I am trying to write the type annotation for the function, that looks like this:

def __init__(self, *args, **kwargs: Union[bool, str, float]) -> None:

But my PyCharm IDE alerts me:

Except type 'Integral', got 'str' instead

And:

Cannot find referance 'get' in bool | str | float'

Am I doing something wrong?

How should I write the type annotation for kwargs if it contains arguments from multiple types?

2 Answers

If one wants to describe specific named arguments expected in kwargs, one can instead pass in a TypedDict which defines required and optional parameters. Optional parameters are what were the kwargs:

This allows one to have unset (NOT None default) optional arguments AND have type hints on them.

This is also useful if they keys have invalid python variable names because TypedDict is the only way to define the types of those values aside from the very general **kwargs value type hinting.

import typing
from abc import ABCMeta


class RequiredProps(typing.TypedDict):
    # all of these must be present
    data: str

class OptionalProps(typing.TypedDict, total=False):
    # these can be included or they can be omitted
    premium: bool
    investment: typing.Union[str, float]

class ReqAndOptional(RequiredProps, OptionalProps):
    pass

class SomeClass(object, metaclass=ABCMeta):
    def __init__(self, *args, kwargs: ReqAndOptional):
        print("Initiating %s object.", self.__class__.__name__)

        self.username = kwargs['data']
        assert isinstance(self.username, str)

        is_premium = kwargs.get('premium', False)
        assert isinstance(is_premium, bool)

        self.money_investment = kwargs.get('investment')
        assert isinstance(elf.money_investment, (str, float))
        if isinstance(self.money_investment, str):
            self.money_investment = float(self.money_investment)
Related