Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 34k
gh-89263: Add typing.get_overloads#31716
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
2ee377d831b565f03f8a9404668a7a5b0d1699825526bb908f52b757fc6a925b524244e95558e31fd72d7041ad3e26b0db1bf89fb83ac432dfdbdc7e16c8d0b3d22279727eee2e374b8ff03b1217f07102346970f2053a0b6131ad506bd66e9a21002b1a5cc103bfd4d453f7f450afebea62287905253cdebbf8a754c1341ad8224File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -2407,6 +2407,35 @@ Functions and decorators | ||||||
| See :pep:`484` for details and comparison with other typing semantics. | ||||||
| .. versionchanged:: 3.11 | ||||||
| Overloaded functions can now be introspected at runtime using | ||||||
| :func:`get_overloads`. | ||||||
| .. function:: get_overloads(func) | ||||||
| Return a sequence of :func:`@overload <overload>`-decorated definitions for | ||||||
| *func*. *func* is the function object for the implementation of the | ||||||
| overloaded function. For example, given the definition of ``process`` in | ||||||
Comment on lines +2418 to +2419 Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This sentence sounds quite strange to me: But I'm not sure I have a better suggestion off the top of my head :// MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, unfortunately the | ||||||
| the documentation for :func:`@overload <overload>`, | ||||||
| ``get_overloads(process)`` will return a sequence of three function objects | ||||||
| for the three defined overloads. If called on a function with no overloads, | ||||||
| ``get_overloads`` returns an empty sequence. | ||||||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggested change
Tiny nit -- this paragraph starts off using the imperative mood MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But the previous sentence is in the indicative, so I think this is fine? Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, it's not really a big deal either way :) | ||||||
| ``get_overloads`` can be used for introspecting an overloaded function at | ||||||
| runtime. | ||||||
JelleZijlstra marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||||||
| .. versionadded:: 3.11 | ||||||
| .. function:: clear_overloads() | ||||||
| Clear all registered overloads in the internal registry. This can be used | ||||||
| to reclaim the memory used by the registry. | ||||||
| .. versionadded:: 3.11 | ||||||
| .. decorator:: final | ||||||
| A decorator to indicate to type checkers that the decorated method | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,18 @@ | ||
| import contextlib | ||
| import collections | ||
| from collections import defaultdict | ||
| from functools import lru_cache | ||
| import inspect | ||
| import pickle | ||
| import re | ||
| import sys | ||
| import warnings | ||
| from unittest import TestCase, main, skipUnless, skip | ||
| from unittest.mock import patch | ||
| from copy import copy, deepcopy | ||
| from typing import Any, NoReturn, Never, assert_never | ||
| from typing import overload, get_overloads, clear_overloads | ||
| from typing import TypeVar, TypeVarTuple, Unpack, AnyStr | ||
| from typing import T, KT, VT # Not in __all__. | ||
| from typing import Union, Optional, Literal | ||
| @@ -3890,11 +3893,22 @@ def test_or(self): | ||
| self.assertEqual("x" | X, Union["x", X]) | ||
| @lru_cache() | ||
| def cached_func(x, y): | ||
| return 3 * x + y | ||
| class MethodHolder: | ||
| @classmethod | ||
| def clsmethod(cls): ... | ||
| @staticmethod | ||
| def stmethod(): ... | ||
| def method(self): ... | ||
| class OverloadTests(BaseTestCase): | ||
| def test_overload_fails(self): | ||
| from typing import overload | ||
| with self.assertRaises(RuntimeError): | ||
| @overload | ||
| @@ -3904,8 +3918,6 @@ def blah(): | ||
| blah() | ||
| def test_overload_succeeds(self): | ||
| from typing import overload | ||
| @overload | ||
| def blah(): | ||
| pass | ||
| @@ -3915,6 +3927,58 @@ def blah(): | ||
| blah() | ||
| def set_up_overloads(self): | ||
| def blah(): | ||
| pass | ||
| overload1 = blah | ||
| overload(blah) | ||
| def blah(): | ||
| pass | ||
| overload2 = blah | ||
| overload(blah) | ||
| def blah(): | ||
| pass | ||
| return blah, [overload1, overload2] | ||
| # Make sure we don't clear the global overload registry | ||
| @patch("typing._overload_registry", | ||
| defaultdict(lambda: defaultdict(dict))) | ||
| def test_overload_registry(self): | ||
JelleZijlstra marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| # The registry starts out empty | ||
| self.assertEqual(typing._overload_registry,{}) | ||
| impl, overloads = self.set_up_overloads() | ||
| self.assertNotEqual(typing._overload_registry,{}) | ||
| self.assertEqual(list(get_overloads(impl)), overloads) | ||
| def some_other_func(): pass | ||
| overload(some_other_func) | ||
| other_overload = some_other_func | ||
| def some_other_func(): pass | ||
| self.assertEqual(list(get_overloads(some_other_func)), [other_overload]) | ||
| # Make sure that after we clear all overloads, the registry is | ||
| # completely empty. | ||
| clear_overloads() | ||
| self.assertEqual(typing._overload_registry,{}) | ||
| self.assertEqual(get_overloads(impl), []) | ||
| # Querying a function with no overloads shouldn't change the registry. | ||
| def the_only_one(): pass | ||
| self.assertEqual(get_overloads(the_only_one), []) | ||
| self.assertEqual(typing._overload_registry,{}) | ||
| def test_overload_registry_repeated(self): | ||
| for _ in range(2): | ||
| impl, overloads = self.set_up_overloads() | ||
| self.assertEqual(list(get_overloads(impl)), overloads) | ||
| # Definitions needed for features introduced in Python 3.6 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -21,6 +21,7 @@ | ||
| from abc import abstractmethod, ABCMeta | ||
| import collections | ||
| from collections import defaultdict | ||
| import collections.abc | ||
| import contextlib | ||
| import functools | ||
| @@ -121,9 +122,11 @@ def _idfunc(_, x): | ||
| 'assert_type', | ||
| 'assert_never', | ||
| 'cast', | ||
| 'clear_overloads', | ||
| 'final', | ||
| 'get_args', | ||
| 'get_origin', | ||
| 'get_overloads', | ||
| 'get_type_hints', | ||
| 'is_typeddict', | ||
| 'LiteralString', | ||
| @@ -2436,6 +2439,10 @@ def _overload_dummy(*args, **kwds): | ||
| "by an implementation that is not @overload-ed.") | ||
| #{module:{qualname:{firstlineno: func}}} | ||
| _overload_registry = defaultdict(functools.partial(defaultdict, dict)) | ||
| def overload(func): | ||
| """Decorator for overloaded functions/methods. | ||
| @@ -2461,10 +2468,37 @@ def utf8(value: bytes) -> bytes: ... | ||
| def utf8(value: str) -> bytes: ... | ||
| def utf8(value): | ||
| # implementation goes here | ||
| The overloads for a function can be retrieved at runtime using the | ||
| get_overloads() function. | ||
| """ | ||
| # classmethod and staticmethod | ||
| f = getattr(func, "__func__", func) | ||
| try: | ||
| _overload_registry[f.__module__][f.__qualname__][f.__code__.co_firstlineno] = func | ||
| except AttributeError: | ||
| # Not a normal function; ignore. | ||
| pass | ||
| return _overload_dummy | ||
| def get_overloads(func): | ||
| """Return all defined overloads for *func* as a sequence.""" | ||
| # classmethod and staticmethod | ||
| f = getattr(func, "__func__", func) | ||
| if f.__module__ not in _overload_registry: | ||
| return [] | ||
| mod_dict = _overload_registry[f.__module__] | ||
| if f.__qualname__ not in mod_dict: | ||
| return [] | ||
| return list(mod_dict[f.__qualname__].values()) | ||
| def clear_overloads(): | ||
| """Clear all overloads in the registry.""" | ||
| _overload_registry.clear() | ||
JelleZijlstra marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| def final(f): | ||
| """A decorator to indicate final methods and final classes. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| Add :func:`typing.get_overloads` and :func:`typing.clear_overloads`. | ||
| Patch by Jelle Zijlstra. |
Uh oh!
There was an error while loading. Please reload this page.