From 46dbf3c5c30c7d02ad5a3380b481ecfa65cd011c Mon Sep 17 00:00:00 2001 From: Ammar Askar Date: Tue, 14 Jul 2020 00:00:24 -0700 Subject: [PATCH 1/6] bpo-41122: Handle missing arguments to @singledispatchmethod gracefully --- Lib/functools.py | 4 ++++ Lib/test/test_functools.py | 12 ++++++++++++ .../Library/2020-07-13-23-59-42.bpo-41122.8P_Brh.rst | 3 +++ 3 files changed, 19 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2020-07-13-23-59-42.bpo-41122.8P_Brh.rst diff --git a/Lib/functools.py b/Lib/functools.py index b1f1fe8d9a6f27..804150a8a15462 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -883,6 +883,7 @@ def wrapper(*args, **kw): wrapper.dispatch = dispatch wrapper.registry = types.MappingProxyType(registry) wrapper._clear_cache = dispatch_cache.clear + wrapper._funcname = funcname update_wrapper(wrapper, func) return wrapper @@ -911,6 +912,9 @@ def register(self, cls, method=None): def __get__(self, obj, cls=None): def _method(*args, **kwargs): + if not args: + raise TypeError(f'{self.dispatcher._funcname} requires at least ' + '1 positional argument') method = self.dispatcher.dispatch(args[0].__class__) return method.__get__(obj, cls)(*args, **kwargs) diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index edd5773e13d549..d526791f08d964 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -2449,6 +2449,18 @@ def f(*args): with self.assertRaisesRegex(TypeError, msg): f() + def test_invalid_positional_argument_singlemethoddispatch(self): + class A: + @functools.singledispatchmethod + def t(self): + pass + @t.register + def _(self, arg: int): + return "int" + + msg = 't requires at least 1 positional argument' + with self.assertRaisesRegex(TypeError, msg): + A().t() class CachedCostItem: _cost = 1 diff --git a/Misc/NEWS.d/next/Library/2020-07-13-23-59-42.bpo-41122.8P_Brh.rst b/Misc/NEWS.d/next/Library/2020-07-13-23-59-42.bpo-41122.8P_Brh.rst new file mode 100644 index 00000000000000..236911ad7a2a42 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-07-13-23-59-42.bpo-41122.8P_Brh.rst @@ -0,0 +1,3 @@ +Failing to pass arguments properly to functools.singledispatchmethod +now throws a TypeError instead of hitting an index out of bounds +internally. From de12588fc3989a08932ced554200ad2ddce3d2d9 Mon Sep 17 00:00:00 2001 From: Ammar Askar Date: Fri, 17 Jul 2020 23:43:24 -0700 Subject: [PATCH 2/6] Fix test name Co-authored-by: nagarajan <68099336+nagarajan@users.noreply.github.com> --- Lib/test/test_functools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index d526791f08d964..3c4a86eeb9fb70 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -2449,7 +2449,7 @@ def f(*args): with self.assertRaisesRegex(TypeError, msg): f() - def test_invalid_positional_argument_singlemethoddispatch(self): + def test_invalid_positional_argument_singledispatchmethod(self): class A: @functools.singledispatchmethod def t(self): From 171f0a526b3e618e172446296d46e67c45ff15d3 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 16 Feb 2024 20:09:25 +0200 Subject: [PATCH 3/6] Remove _funcname attribute. --- Lib/functools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/functools.py b/Lib/functools.py index faaf632c7dbda3..5ce2d704c988cd 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -927,7 +927,6 @@ def wrapper(*args, **kw): wrapper.dispatch = dispatch wrapper.registry = types.MappingProxyType(registry) wrapper._clear_cache = dispatch_cache.clear - wrapper._funcname = funcname update_wrapper(wrapper, func) return wrapper @@ -969,9 +968,10 @@ def __get__(self, obj, cls=None): return _method dispatch = self.dispatcher.dispatch + funcname = getattr(self.func, '__name__', 'singledispatchmethod method') def _method(*args, **kwargs): if not args: - raise TypeError(f'{self.dispatcher._funcname} requires at least ' + raise TypeError(f'{funcname} requires at least ' '1 positional argument') return dispatch(args[0].__class__).__get__(obj, cls)(*args, **kwargs) From 1d48a93b6b71d1eac46fecb955ded6f39d8cd120 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 16 Feb 2024 20:15:36 +0200 Subject: [PATCH 4/6] Polishing. --- Lib/functools.py | 1 - Lib/test/test_functools.py | 14 ++++++++------ .../2020-07-13-23-59-42.bpo-41122.8P_Brh.rst | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Lib/functools.py b/Lib/functools.py index 5ce2d704c988cd..7045be551c8c49 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -918,7 +918,6 @@ def wrapper(*args, **kw): if not args: raise TypeError(f'{funcname} requires at least ' '1 positional argument') - return dispatch(args[0].__class__)(*args, **kw) funcname = getattr(func, '__name__', 'singledispatch function') diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 76e08d16473338..2c814d5e888840 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -2867,24 +2867,26 @@ def _(arg: typing.Union[int, typing.Iterable[str]]): def test_invalid_positional_argument(self): @functools.singledispatch - def f(*args): + def f(*args, **kwargs): pass msg = 'f requires at least 1 positional argument' with self.assertRaisesRegex(TypeError, msg): f() + msg = 'f requires at least 1 positional argument' + with self.assertRaisesRegex(TypeError, msg): + f(a=1) def test_invalid_positional_argument_singledispatchmethod(self): class A: @functools.singledispatchmethod - def t(self): + def t(self, *args, **kwargs): pass - @t.register - def _(self, arg: int): - return "int" - msg = 't requires at least 1 positional argument' with self.assertRaisesRegex(TypeError, msg): A().t() + msg = 't requires at least 1 positional argument' + with self.assertRaisesRegex(TypeError, msg): + A().t(a=1) def test_union(self): @functools.singledispatch diff --git a/Misc/NEWS.d/next/Library/2020-07-13-23-59-42.bpo-41122.8P_Brh.rst b/Misc/NEWS.d/next/Library/2020-07-13-23-59-42.bpo-41122.8P_Brh.rst index 236911ad7a2a42..76568d407449f5 100644 --- a/Misc/NEWS.d/next/Library/2020-07-13-23-59-42.bpo-41122.8P_Brh.rst +++ b/Misc/NEWS.d/next/Library/2020-07-13-23-59-42.bpo-41122.8P_Brh.rst @@ -1,3 +1,3 @@ -Failing to pass arguments properly to functools.singledispatchmethod +Failing to pass arguments properly to :func:`functools.singledispatchmethod` now throws a TypeError instead of hitting an index out of bounds internally. From f41eafb0abce7bb71253a6046ae759409d1786eb Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 16 Feb 2024 20:39:09 +0200 Subject: [PATCH 5/6] Update Lib/functools.py Co-authored-by: Alex Waygood --- Lib/functools.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Lib/functools.py b/Lib/functools.py index 7045be551c8c49..9d53e76e5d8ec1 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -969,10 +969,12 @@ def __get__(self, obj, cls=None): dispatch = self.dispatcher.dispatch funcname = getattr(self.func, '__name__', 'singledispatchmethod method') def _method(*args, **kwargs): - if not args: + try: + key = args[0].__class__ + except IndexError: raise TypeError(f'{funcname} requires at least ' - '1 positional argument') - return dispatch(args[0].__class__).__get__(obj, cls)(*args, **kwargs) + '1 positional argument') from None + return dispatch(key).__get__(obj, cls)(*args, **kwargs) _method.__isabstractmethod__ = self.__isabstractmethod__ _method.register = self.register From 0f0466e834c8eca422bd1299f7f13780e8a84f1f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 16 Feb 2024 21:04:49 +0200 Subject: [PATCH 6/6] Revert "Update Lib/functools.py" This reverts commit f41eafb0abce7bb71253a6046ae759409d1786eb. --- Lib/functools.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Lib/functools.py b/Lib/functools.py index 9d53e76e5d8ec1..7045be551c8c49 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -969,12 +969,10 @@ def __get__(self, obj, cls=None): dispatch = self.dispatcher.dispatch funcname = getattr(self.func, '__name__', 'singledispatchmethod method') def _method(*args, **kwargs): - try: - key = args[0].__class__ - except IndexError: + if not args: raise TypeError(f'{funcname} requires at least ' - '1 positional argument') from None - return dispatch(key).__get__(obj, cls)(*args, **kwargs) + '1 positional argument') + return dispatch(args[0].__class__).__get__(obj, cls)(*args, **kwargs) _method.__isabstractmethod__ = self.__isabstractmethod__ _method.register = self.register