From 7bccaf6dcee8ddf3ed48628e0abb08479eded9aa Mon Sep 17 00:00:00 2001 From: Carey Metcalfe Date: Wed, 14 Jan 2026 09:52:56 -0500 Subject: [PATCH 1/2] Ensure function annotations are returned in order of definition Previously, when getting type annotations of a function, normal arguments were returned before positional-only ones in the dictionary. Since `functools.singledispatch` relies on this ordering being correct to dispatch based on the type of the first argument, this issue was causing incorrect registrations for functions with positional-only arguments. This commit updates how annotations are generated so that positional-only arguments are generated and added to the dictionary before normal arguments. --- Lib/test/test_functools.py | 15 +++++++++++++++ Lib/test/test_typing.py | 7 +++++++ ...2026-01-15-13-37-21.gh-issue-143886.2gk5QC.rst | 3 +++ Python/codegen.c | 4 ++-- 4 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-01-15-13-37-21.gh-issue-143886.2gk5QC.rst diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 94b469397139c7..6826859e5bb1a2 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -3236,6 +3236,21 @@ def t(self, *args, **kwargs): with self.assertRaisesRegex(TypeError, msg): A().t(a=1) + def test_positional_only_argument(self): + @functools.singledispatch + def f(arg, /, extra): + return "base" + @f.register + def f_int(arg: int, /, extra: str): + return "int" + @f.register + def f_str(arg: str, /, extra: int): + return "str" + + self.assertEqual(f(None, "extra"), "base") + self.assertEqual(f(1, "extra"), "int") + self.assertEqual(f("s", "extra"), "str") + def test_union(self): @functools.singledispatch def f(arg): diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index e896df518447c5..97f8be32b336c6 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -7204,6 +7204,13 @@ class TD[UniqueT](TypedDict): self.assertEqual(TD.__annotations__, {'a': EqualToForwardRef('UniqueT', owner=TD, module=TD.__module__)}) self.assertEqual(get_type_hints(TD), {'a': TD.__type_params__[0]}) + def test_get_type_hints_order(self): + """Ensure that the order of function annotations matches the order they're defined""" + def f(positional: int, /, normal: str, *args: bytes, kwarg: list, **kwargs: bool) -> tuple: + pass + + self.assertEqual(list(gth(f)), ["positional", "normal", "args", "kwarg", "kwargs", "return"]) + class GetUtilitiesTestCase(TestCase): def test_get_origin(self): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-01-15-13-37-21.gh-issue-143886.2gk5QC.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-15-13-37-21.gh-issue-143886.2gk5QC.rst new file mode 100644 index 00000000000000..9a6a1bba7adf6b --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-15-13-37-21.gh-issue-143886.2gk5QC.rst @@ -0,0 +1,3 @@ +Reorder function annotations so positional-only arguments are returned +before other arguments. This fixes how :func:`functools.singledispatch`` +registers functions with positional-only arguments. diff --git a/Python/codegen.c b/Python/codegen.c index acd8d84bc9ec6d..43b3c3b4ad8601 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -1093,10 +1093,10 @@ codegen_annotations_in_scope(compiler *c, location loc, Py_ssize_t *annotations_len) { RETURN_IF_ERROR( - codegen_argannotations(c, args->args, annotations_len, loc)); + codegen_argannotations(c, args->posonlyargs, annotations_len, loc)); RETURN_IF_ERROR( - codegen_argannotations(c, args->posonlyargs, annotations_len, loc)); + codegen_argannotations(c, args->args, annotations_len, loc)); if (args->vararg && args->vararg->annotation) { RETURN_IF_ERROR( From 31d6239df3a2b02b59e204a82ed64487366b0366 Mon Sep 17 00:00:00 2001 From: Carey Metcalfe Date: Thu, 15 Jan 2026 14:02:43 -0500 Subject: [PATCH 2/2] Fix typo in news --- .../2026-01-15-13-37-21.gh-issue-143886.2gk5QC.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-01-15-13-37-21.gh-issue-143886.2gk5QC.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-15-13-37-21.gh-issue-143886.2gk5QC.rst index 9a6a1bba7adf6b..fe4835ec28cfd5 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-01-15-13-37-21.gh-issue-143886.2gk5QC.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-15-13-37-21.gh-issue-143886.2gk5QC.rst @@ -1,3 +1,3 @@ Reorder function annotations so positional-only arguments are returned -before other arguments. This fixes how :func:`functools.singledispatch`` +before other arguments. This fixes how :func:`functools.singledispatch` registers functions with positional-only arguments.