Skip to content

Make singledispatch only evaluate annotation for dispatched arg #110373

Description

@hauntsaninja

In the example below, we have no real need to evaluate the type annotations of the non-dispatched args.

Skipping this might improve performance / import time a little.
While a little late for some nice features like X | Y syntax, this is useful in case we introduce new typing syntax in the future, since it allows users to use it on non-dispatched args.

from __future__ import annotations
from functools import singledispatch

@singledispatch
def fun(arg, verbose):
    print("arg", type(arg))

@fun.register
def _(arg: int, verbose: 1/0):
    print("arg", type(arg))

@fun.register
def _(arg: list, verbose: 1/0):
    print("arg", type(arg))

Activity

  1. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Oct 5, 2023
  2. JelleZijlstra commented on Sep 17, 2024

    @JelleZijlstra
    Member

    @AlexWaygood suggested in #119891 (comment) that the PEP 649 changes could fix this issue too. Unfortunately, PEP 649 does not allow the example above to pass, because when you call an __annotate__ function, all the code is always evaluated, so 1/0 gets run and throws an error. The tricks that we add to support forward references work if you use undefined names, but not for other runtime errors.

    The good news is that something like this (more realistic than 1/0 in an annotation) does now work on main:

    from typing import TYPE_CHECKING
    if TYPE_CHECKING:
        from expensive import SomeType
    
    from functools import singledispatch
    
    @singledispatch
    def fun(arg, verbose):
        return "fun"
    
    @fun.register
    def _(arg: int, verbose: SomeType):
        return "int fun"
    
    fun("x", None)  # fun
    fun(1, None)  # int fun

    But the OP's example above will still fail. We could fix this by a solution like making __annotate__ support an optional parameter saying it should only evaluate some of the annotations. I personally feel that's not worth the effort, but I'm open to other opinions.

  3. johnslavik commented on Jan 20, 2026

    @johnslavik
    Member

    This is a nuanced one. I personally don't think it's worth it as well -- it's a special case.

    A potential time of revisit is when the lazy imports feature gets merged.

  4. flying-sheep commented on Aug 13, 2026

    @flying-sheep
    Contributor

    We use singledispatch a lot with dummy classes:

    if TYPE_CHECKING or find_spec("optional-dep"):
        from optional_dep import Thing
    else:
        Thing = type("Thing", (), dict(__module__="optional_dep"))
    
    @singledispatch
    def f(obj): ...
    
    @f.register(Thing)
    def _(obj): ...

    For this to be replaced with lazy imports, we really do need something new.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytopic-typingtype-featureA feature request or enhancement

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions