Search code examples
pythonpython-typing

Using unpacked TypedDict for specifying function arguments


I was wondering whether following was possible with TypedDict and Unpack, inspired by PEP 692...

Regular way of using TypedDict would be:

class Config(TypedDict):
    a: str
    b: int

def inference(name, **config: Unpack[Config]):
    c = config['a']+str(config["b"])

config: Config = {"a": "1", "b": 2}
inference("example", **config)  # or I could get argument hinting for each one from Config

However, I would be really interested if I could somehow unpack those keys to make it behave like they were directly introduced as variables into function signature:

def inference(name, **?: Unpack[Config]):
    c = a+str(b)

so to mimic this explicit approach:

def inference(name, a: str, b: int):
  c = a + str(b)

My motivation is I want to still leave code uncluttered with field or key access (verbose_name.another_verbose_name or verbose_name["another_verbose_name"], where latter has no type hinting in VSC). Also, this way I could define TypedDict for Config once, and when needing some new parameter for my inference, i could just straight add it to Config definition, without changing inference signature.

Currently my workaround is explicit definition of arguments in inference signature and still invoking this with inference(name, **config). I still prefer this approach more than Dataclass, since I can skip asdict(dataclass_config) doing it so, which feels very unnecessary, especially when loading config from yaml or similar...

Maybe my approach is all wrong and not suiting to the best practices... Also would love your input on this. Im just starting using more advanced Python topics...

I can imagine that adding this functionality via PEP would require more than just typing work, so I would like to know if there is some common established solution.


Solution

  • No, there is no python syntax that will allow you to "unpack the dictionary to make it behave like they were directly introduced as variables into function signature".

    You could technically do it at runtime with something like this:

    def inference(name, **config: Unpack[Config]):
       vars().update(config)
       dosomething(a, b)
    

    but that's very hacky, and the typechecker would not recognize subsequent uses of a or b (even though they will work at runtime).

    The typechecker will only recognize a variable if it is declared or passed as argument.


    I understand you want to declutter your code, but as @Barmar's comment suggests, skipping variable declarations may not be the best way to do that if you are actually using those variables in your function. ** and Unpack are mostly useful if those arguments are passed down to another function