diff options
| author | Ethan Smith <ethan@ethanhs.me> | 2018-05-26 16:38:33 -0400 |
|---|---|---|
| committer | Ćukasz Langa <lukasz@langa.pl> | 2018-05-26 13:38:33 -0700 |
| commit | c651275afe8515b2cf70b8152e19ce39df88f0dd (patch) | |
| tree | 3195af174b5acf5c44f906b6fc8f83648e3f56db /Lib | |
| parent | 09c4a7dee2eb39b515e5f499f184257cdbe9cb42 (diff) | |
| download | cpython-git-c651275afe8515b2cf70b8152e19ce39df88f0dd.tar.gz | |
bpo-32380: Create functools.singledispatchmethod (#6306)
Diffstat (limited to 'Lib')
| -rw-r--r-- | Lib/functools.py | 39 | ||||
| -rw-r--r-- | Lib/test/test_functools.py | 118 |
2 files changed, 156 insertions, 1 deletions
diff --git a/Lib/functools.py b/Lib/functools.py index c8b79c2a7c..d5f43935e6 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -11,7 +11,7 @@ __all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES', 'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial', - 'partialmethod', 'singledispatch'] + 'partialmethod', 'singledispatch', 'singledispatchmethod'] try: from _functools import reduce @@ -826,3 +826,40 @@ def singledispatch(func): wrapper._clear_cache = dispatch_cache.clear update_wrapper(wrapper, func) return wrapper + + +# Descriptor version +class singledispatchmethod: + """Single-dispatch generic method descriptor. + + Supports wrapping existing descriptors and handles non-descriptor + callables as instance methods. + """ + + def __init__(self, func): + if not callable(func) and not hasattr(func, "__get__"): + raise TypeError(f"{func!r} is not callable or a descriptor") + + self.dispatcher = singledispatch(func) + self.func = func + + def register(self, cls, method=None): + """generic_method.register(cls, func) -> func + + Registers a new implementation for the given *cls* on a *generic_method*. + """ + return self.dispatcher.register(cls, func=method) + + def __get__(self, obj, cls): + def _method(*args, **kwargs): + method = self.dispatcher.dispatch(args[0].__class__) + return method.__get__(obj, cls)(*args, **kwargs) + + _method.__isabstractmethod__ = self.__isabstractmethod__ + _method.register = self.register + update_wrapper(_method, self.func) + return _method + + @property + def __isabstractmethod__(self): + return getattr(self.func, '__isabstractmethod__', False) diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 2245b97433..7ffe000af0 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -2147,6 +2147,124 @@ class TestSingleDispatch(unittest.TestCase): return self.arg == other self.assertEqual(i("str"), "str") + def test_method_register(self): + class A: + @functools.singledispatchmethod + def t(self, arg): + self.arg = "base" + @t.register(int) + def _(self, arg): + self.arg = "int" + @t.register(str) + def _(self, arg): + self.arg = "str" + a = A() + + a.t(0) + self.assertEqual(a.arg, "int") + aa = A() + self.assertFalse(hasattr(aa, 'arg')) + a.t('') + self.assertEqual(a.arg, "str") + aa = A() + self.assertFalse(hasattr(aa, 'arg')) + a.t(0.0) + self.assertEqual(a.arg, "base") + aa = A() + self.assertFalse(hasattr(aa, 'arg')) + + def test_staticmethod_register(self): + class A: + @functools.singledispatchmethod + @staticmethod + def t(arg): + return arg + @t.register(int) + @staticmethod + def _(arg): + return isinstance(arg, int) + @t.register(str) + @staticmethod + def _(arg): + return isinstance(arg, str) + a = A() + + self.assertTrue(A.t(0)) + self.assertTrue(A.t('')) + self.assertEqual(A.t(0.0), 0.0) + + def test_classmethod_register(self): + class A: + def __init__(self, arg): + self.arg = arg + + @functools.singledispatchmethod + @classmethod + def t(cls, arg): + return cls("base") + @t.register(int) + @classmethod + def _(cls, arg): + return cls("int") + @t.register(str) + @classmethod + def _(cls, arg): + return cls("str") + + self.assertEqual(A.t(0).arg, "int") + self.assertEqual(A.t('').arg, "str") + self.assertEqual(A.t(0.0).arg, "base") + + def test_callable_register(self): + class A: + def __init__(self, arg): + self.arg = arg + + @functools.singledispatchmethod + @classmethod + def t(cls, arg): + return cls("base") + + @A.t.register(int) + @classmethod + def _(cls, arg): + return cls("int") + @A.t.register(str) + @classmethod + def _(cls, arg): + return cls("str") + + self.assertEqual(A.t(0).arg, "int") + self.assertEqual(A.t('').arg, "str") + self.assertEqual(A.t(0.0).arg, "base") + + def test_abstractmethod_register(self): + class Abstract(abc.ABCMeta): + + @functools.singledispatchmethod + @abc.abstractmethod + def add(self, x, y): + pass + + self.assertTrue(Abstract.add.__isabstractmethod__) + + def test_type_ann_register(self): + class A: + @functools.singledispatchmethod + def t(self, arg): + return "base" + @t.register + def _(self, arg: int): + return "int" + @t.register + def _(self, arg: str): + return "str" + a = A() + + self.assertEqual(a.t(0), "int") + self.assertEqual(a.t(''), "str") + self.assertEqual(a.t(0.0), "base") + def test_invalid_registrations(self): msg_prefix = "Invalid first argument to `register()`: " msg_suffix = ( |
