diff -r 4243df51fe43 Doc/library/functools.rst --- a/Doc/library/functools.rst Fri Feb 10 14:19:36 2017 +0100 +++ b/Doc/library/functools.rst Thu May 03 14:30:01 2018 -0400 @@ -167,10 +167,11 @@ .. function:: partial(func, *args, **keywords) - Return a new :class:`partial` object which when called will behave like *func* - called with the positional arguments *args* and keyword arguments *keywords*. If - more arguments are supplied to the call, they are appended to *args*. If - additional keyword arguments are supplied, they extend and override *keywords*. + Return a new "partial function application". When called, the + :class:`partial` object behaves like *func* called with the positional + arguments *args* and keyword arguments *keywords*. If more arguments are + supplied to the call, they are appended to *args*. If additional keyword + arguments are supplied, they extend and override *keywords*. Roughly equivalent to:: def partial(func, *args, **keywords): @@ -183,11 +184,10 @@ newfunc.keywords = keywords return newfunc - The :func:`partial` is used for partial function application which "freezes" - some portion of a function's arguments and/or keywords resulting in a new object - with a simplified signature. For example, :func:`partial` can be used to create - a callable that behaves like the :func:`int` function where the *base* argument - defaults to two: + :class:`partial` transforms a function into a callable object with a + simplified signature. For example, it can be used to create a callable that + behaves like the :func:`int` function where the *base* argument defaults to + two: >>> from functools import partial >>> basetwo = partial(int, base=2) @@ -198,18 +198,19 @@ .. class:: partialmethod(func, *args, **keywords) - Return a new :class:`partialmethod` descriptor which behaves - like :class:`partial` except that it is designed to be used as a method - definition rather than being directly callable. + Return a new "partial method application". This :class:`partialmethod` descriptor + behaves like :class:`partial` except that it is designed to be used as a + method definition rather than being directly callable. - *func* must be a :term:`descriptor` or a callable (objects which are both, - like normal functions, are handled as descriptors). + *func* must be a :term:`descriptor` or a callable. Objects that are both + descriptors and callable---like normal functions---are handled as + descriptors. When *func* is a descriptor (such as a normal Python function, :func:`classmethod`, :func:`staticmethod`, :func:`abstractmethod` or another instance of :class:`partialmethod`), calls to ``__get__`` are delegated to the underlying descriptor, and an appropriate - :class:`partial` object returned as the result. + :class:`partial` object is returned. When *func* is a non-descriptor callable, an appropriate bound method is created dynamically. This behaves like a normal Python function when @@ -239,6 +240,24 @@ .. versionadded:: 3.4 +.. class:: partialclass(cls, *args, **keywords) + + Return a new "partial class application" class that behaves like the + "partial function application" :class:`partial` applied to cls except that + it is a subclass of `cls`. + + Example:: + + >>> import collections + >>> dict_of_lists = partialclass(collections.defaultdict, list) + >>> issubclass(dict_of_lists, collections.defaultdict) + True + >>> d = dict_of_lists() + >>> d[1].append(2) + >>> d[1] + >>> [2] + + .. versionadded:: 3.7 .. function:: reduce(function, iterable[, initializer]) diff -r 4243df51fe43 Lib/functools.py --- a/Lib/functools.py Fri Feb 10 14:19:36 2017 +0100 +++ b/Lib/functools.py Thu May 03 14:30:01 2018 -0400 @@ -11,7 +11,7 @@ __all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES', 'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial', - 'partialmethod', 'singledispatch'] + 'partialmethod', 'partialclass', 'singledispatch'] try: from _functools import reduce @@ -396,6 +396,19 @@ return getattr(self.func, "__isabstractmethod__", False) +def partialclass(cls, *args, **kwargs): + """Partial class application of the given arguments and keywords. + + The returned object behaves like a "partial function application" on cls + except that it is a subclass of cls. + """ + + class PartialClass(cls): + __init__ = partialmethod(cls.__init__, *args, **kwargs) + + return PartialClass + + ################################################################################ ### LRU Cache function decorator ################################################################################ diff -r 4243df51fe43 Lib/test/test_functools.py --- a/Lib/test/test_functools.py Fri Feb 10 14:19:36 2017 +0100 +++ b/Lib/test/test_functools.py Thu May 03 14:30:01 2018 -0400 @@ -537,6 +537,26 @@ self.assertFalse(getattr(func, '__isabstractmethod__', False)) +class TestPartialClass(unittest.TestCase): + + def test_subclass(self): + dict_of_lists = functools.partialclass(collections.defaultdict, list) + self.assertTrue(issubclass(dict_of_lists, collections.defaultdict)) + + bad_dict_of_lists = functools.partial(collections.defaultdict, list) + with self.assertRaises(TypeError): + issubclass(bad_dict_of_lists, collections.defaultdict) + + def test_delegates(self): + dict_of_lists = functools.partialclass(collections.defaultdict, list) + d = dict_of_lists() + d[1].append(2) + d[3].extend([4, 6, 8]) + d[1].append(10) + self.assertEqual(d[1], [2, 10]) + self.assertEqual(d[3], [4, 6, 8]) + + class TestUpdateWrapper(unittest.TestCase): def check_wrapper(self, wrapper, wrapped,