[Python] Is it possible to use functools.singledispatch with composite/nested/container types...

Discussão em 'Python' iniciado por Stack, Julho 27, 2025.

  1. Stack

    Stack Membro Participativo

    I wonder whether the following can be achieved, and if so, without requiring much extra code:

    from __future__ import annotations
    from functools import singledispatch

    @singledispatch
    def somefunc(value):
    print(f"Type {type(value).__qualname__!r} "
    f"is not registered for dispatch.")

    @somefunc.register
    def _(value: list[int]):
    print(f"Dispatched type list[int]!")

    @somefunc.register
    def _(value: list[str]):
    print(f"Dispatched type list[str]!")

    somefunc('123')
    somefunc([123])
    somefunc(list('123'))


    And get output:

    Type 'str' is not registered for dispatch.
    Dispatched type list[int]!
    Dispatched type list[str]!


    Running this snippet with python 3.9.6, however, instead results in an error at line 742 of functools.py:

    TypeError: issubclass() argument 2 cannot be
    a parameterized generic


    As singledispatch does work for user defined classes, one way to make this work is to typecheck the elements in the passed list, wrap the passed list into a class representing e.g. list[str] and have the dispatched function call itself again with the new instance as argument:

    from __future__ import annotations
    from functools import singledispatch


    class ListOfStrs(list):
    def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)


    class ListOfInts(list):
    def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)


    @singledispatch
    def somefunc(value):
    print(f"Type {type(value).__qualname__!r} "
    f"is not registered for dispatch.")


    @somefunc.register
    def _(value: list):
    if value and all(isinstance(subval, int)
    for subval in value):
    somefunc(ListOfInts(value))

    elif value and all(isinstance(subval, str)
    for subval in value):
    somefunc(ListOfStrs(value))

    else:
    print(
    f"Dispatched a list whose elements ",
    f"are not all of a registered type."
    )


    @somefunc.register
    def _(value: ListOfStrs):
    print(f"Dispatched type 'list[str]'!")


    @somefunc.register
    def _(value: ListOfInts):
    print(f"Dispatched type 'list[int]'!")


    somefunc('123')
    somefunc([1, 2, 3])
    somefunc(list('123'))
    somefunc([{1}, {2}, {3}])


    Which, as expected, results in:

    Type 'str' is not registered for dispatch.
    Dispatched type 'list[int]'!
    Dispatched type 'list[str]'!
    Dispatched a list whose elements are
    not all of a registered type.


    However, besides increasing extensibility, one of the reasons to use singledispatch in the first place is to circumvent verbose typechecks, which some consider an anti-pattern. And of course for this solution specifically, you'd need to define the wrapper classes which litter the code (there may be a better way of achieving this that I currently don't see, but the first point still stands).

    One could reason one typecheck for 'list' is avoided here, but that reduces complexity just by just one if/else clause.

    So I wouldn't actually use the last case.

    Does anyone know how to get this behaviour as elegantly as can be done with non-composite types?

    I suppose this could be done elegantly using pattern matching in 3.10. So maybe I should wait for its launch and maturation if this is currently not feasible?

    Perhaps with the introduction of variadic generics (PEP-646) in Python 3.11, dispatch with composite types may become possible.

    Continue reading...

Compartilhe esta Página