... print(arg)
To add overloaded implementations to the function, use the :func:`register`
- attribute of the generic function. It is a decorator, taking a type
- parameter and decorating a function implementing the operation for that
- type::
+ attribute of the generic function. It is a decorator. For functions
+ annotated with types, the decorator will infer the type of the first
+ argument automatically::
- >>> @fun.register(int)
- ... def _(arg, verbose=False):
+ >>> @fun.register
+ ... def _(arg: int, verbose=False):
... if verbose:
... print("Strength in numbers, eh?", end=" ")
... print(arg)
...
- >>> @fun.register(list)
- ... def _(arg, verbose=False):
+ >>> @fun.register
+ ... def _(arg: list, verbose=False):
... if verbose:
... print("Enumerate this:")
... for i, elem in enumerate(arg):
... print(i, elem)
+ For code which doesn't use type annotations, the appropriate type
+ argument can be passed explicitly to the decorator itself::
+
+ >>> @fun.register(complex)
+ ... def _(arg, verbose=False):
+ ... if verbose:
+ ... print("Better than complicated.", end=" ")
+ ... print(arg.real, arg.imag)
+ ...
+
+
To enable registering lambdas and pre-existing functions, the
:func:`register` attribute can be used in a functional form::
.. versionadded:: 3.4
+ .. versionchanged:: 3.7
+ The :func:`register` attribute supports using type annotations.
+
.. function:: update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
"""
nonlocal cache_token
if func is None:
- return lambda f: register(cls, f)
+ if isinstance(cls, type):
+ return lambda f: register(cls, f)
+ ann = getattr(cls, '__annotations__', {})
+ if not ann:
+ raise TypeError(
+ f"Invalid first argument to `register()`: {cls!r}. "
+ f"Use either `@register(some_class)` or plain `@register` "
+ f"on an annotated function."
+ )
+ func = cls
+
+ # only import typing if annotation parsing is necessary
+ from typing import get_type_hints
+ argname, cls = next(iter(get_type_hints(func).items()))
+ assert isinstance(cls, type), (
+ f"Invalid annotation for {argname!r}. {cls!r} is not a class."
+ )
registry[cls] = func
if cache_token is None and hasattr(cls, '__abstractmethods__'):
cache_token = get_cache_token()
from test import support
import threading
import time
+import typing
import unittest
import unittest.mock
from weakref import proxy
g._clear_cache()
self.assertEqual(len(td), 0)
+ def test_annotations(self):
+ @functools.singledispatch
+ def i(arg):
+ return "base"
+ @i.register
+ def _(arg: collections.abc.Mapping):
+ return "mapping"
+ @i.register
+ def _(arg: "collections.abc.Sequence"):
+ return "sequence"
+ self.assertEqual(i(None), "base")
+ self.assertEqual(i({"a": 1}), "mapping")
+ self.assertEqual(i([1, 2, 3]), "sequence")
+ self.assertEqual(i((1, 2, 3)), "sequence")
+ self.assertEqual(i("str"), "sequence")
+
+ # Registering classes as callables doesn't work with annotations,
+ # you need to pass the type explicitly.
+ @i.register(str)
+ class _:
+ def __init__(self, arg):
+ self.arg = arg
+
+ def __eq__(self, other):
+ return self.arg == other
+ self.assertEqual(i("str"), "str")
+
+ def test_invalid_registrations(self):
+ msg_prefix = "Invalid first argument to `register()`: "
+ msg_suffix = (
+ ". Use either `@register(some_class)` or plain `@register` on an "
+ "annotated function."
+ )
+ @functools.singledispatch
+ def i(arg):
+ return "base"
+ with self.assertRaises(TypeError) as exc:
+ @i.register(42)
+ def _(arg):
+ return "I annotated with a non-type"
+ self.assertTrue(str(exc.exception).startswith(msg_prefix + "42"))
+ self.assertTrue(str(exc.exception).endswith(msg_suffix))
+ with self.assertRaises(TypeError) as exc:
+ @i.register
+ def _(arg):
+ return "I forgot to annotate"
+ self.assertTrue(str(exc.exception).startswith(msg_prefix +
+ "<function TestSingleDispatch.test_invalid_registrations.<locals>._"
+ ))
+ self.assertTrue(str(exc.exception).endswith(msg_suffix))
+
+ # FIXME: The following will only work after PEP 560 is implemented.
+ return
+
+ with self.assertRaises(TypeError) as exc:
+ @i.register
+ def _(arg: typing.Iterable[str]):
+ # At runtime, dispatching on generics is impossible.
+ # When registering implementations with singledispatch, avoid
+ # types from `typing`. Instead, annotate with regular types
+ # or ABCs.
+ return "I annotated with a generic collection"
+ self.assertTrue(str(exc.exception).startswith(msg_prefix +
+ "<function TestSingleDispatch.test_invalid_registrations.<locals>._"
+ ))
+ self.assertTrue(str(exc.exception).endswith(msg_suffix))
+
if __name__ == '__main__':
unittest.main()
--- /dev/null
+``functools.singledispatch`` now supports registering implementations using
+type annotations.