Home > Net >  Is it possible to deconstruct a dictionary into typed keyword arguments?
Is it possible to deconstruct a dictionary into typed keyword arguments?

Time:09-30

Say I have a dictionary:

from typing import Dict
T: Dict[str, int] = {
    'a': 2,
    'b': 3,
}

Now I would like to define a function with keyword arguments taken from the above dictionary:

def func(**T):
  print (locals())

The goal is to have type checking of the function done properly:

func(2)    # should work
func('2')  # should work
func(a=2)  # should work
func([2])  # should report a type issue
func(c=2)  # should give TypeError

I tried but I cannot get it to work. Is this even possible?

CodePudding user response:

it's not exactly what you're looking for, but you might try the typeguard library, I think you might be able to do something like

from typing import Dict

from typeguard import check_type


def something(**kwargs):
    check_type("kwargs", kwargs, Dict[str, int])


if __name__ == "__main__":
    something(**{"A": 1})
    something(**{"A": "1"})

but I don't think you can get type checking on the actual function call using the **kwargs syntax, as the argument names aren't defined until runtime so there's no way to assign a type to each keyword argument until runtime. if you know the allowed value types ahead of time you could do something like

from typing import Dict

from typeguard import typeguard, check_type


@typeguard
def something(a: int, b: int):
    check_type("kwargs", kwargs, Dict[str, int])


if __name__ == "__main__":
    something(**{"A": 1})
    something(**{"A": "1"})

or you could do

from typing import Dict

from typeguard import typeguard


@typeguard
def something(kwargs: Dict[str, int]):
    # then do something with **kwargs
    _inner(**kwargs)

def _inner(**kwargs):
    pass


if __name__ == "__main__":
    something(**{"A": 1})
    something(**{"A": "1"})

CodePudding user response:

There is a fairly recent proposal for a mechanism allowing TypedDict to be used to annotate **kwargs in PEP 692. However the discussion is still ongoing AFAIK and there are some valid arguments against this.

One such argument is the lack of strong motivation for such functionality. In other words, why would you really need this? Take your case for example. If you already have a typed dictionary defined somewhere and you know the function must take such a dictionary as its argument(s), why not just define a regular function parameter in the form of such a dictionary:

from typing import TypedDict


class SpecialDict(TypedDict, total=False):
    a: int
    b: int


def func(d: SpecialDict) -> None:
    print(d)


if __name__ == "__main__":
    func({"a": 1})
    func({"b": 1})
    func({"c": 1})

Causes mypy to complain about the last line with the following:

error: Extra key "c" for TypedDict "SpecialDict"

Inside the function keyword-arguments are treated like a dictionary anyway, so I don't really see the benefit of constructing your function with them as opposed to a normal dictionary. Especially since **kwargs indicate flexibility that you don't really seem to want.


Besides that, there are a number of problems with your requirements. If you define a function like this def f(**kwargs): ..., it takes only keyword arguments and no positional arguments. Meaning none of these will work, regardless of how you annotate the function:

func(2)
func('2')
func([2])

Also, you wanted some type annotation to cause a TypeError, which will never happen by itself because type annotations are mostly for the benefit of static type checkers. The Python interpreter doesn't care about them at all.

If you do want it to care, you'll have to implement the logic for checking types at runtime yourself.


I would advise to adhere to the Zen of Python and be more explicit. If you really want your function to accept only specific keyword arguments, define it accordingly:

def func(*, a: int, b: int) -> None:
    ...

You can then still call it by unpacking a dictionary with the right keys, if you want:

d = {"a": 1, "b": 2}
func(**d)

Hope this helps.

  • Related