Skip to content

bpo-33419: Added functools.partialclass #6699

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 42 additions & 17 deletions Doc/library/functools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,15 @@ The :mod:`functools` module defines the following functions:

.. 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*.
Roughly equivalent to::
Return a new "partial function application".

A partial function application 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*.

:class:`partial` is roughly equivalent to::

def partial(func, *args, **keywords):
def newfunc(*fargs, **fkeywords):
Expand All @@ -183,11 +187,10 @@ The :mod:`functools` module defines the following functions:
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)
Expand All @@ -198,23 +201,26 @@ The :mod:`functools` module defines the following functions:

.. 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".

*func* must be a :term:`descriptor` or a callable (objects which are both,
like normal functions, are handled as descriptors).
A partial method application behaves like the "partial function
application" returned by :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 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
used as a method: the *self* argument will be inserted as the first
positional argument, even before the *args* and *keywords* supplied to
positional argument---even before the *args* and *keywords* supplied to
the :class:`partialmethod` constructor.

Example::
Expand All @@ -239,6 +245,25 @@ The :mod:`functools` module defines the following functions:

.. versionadded:: 3.4

.. class:: partialclass(cls, *args, **keywords)

Return a new "partial class application" class.

A partial class application 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.8

.. function:: reduce(function, iterable[, initializer])

Expand Down
15 changes: 14 additions & 1 deletion Lib/functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -389,6 +389,19 @@ def __isabstractmethod__(self):
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
################################################################################
Expand Down
20 changes: 20 additions & 0 deletions Lib/test/test_functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,26 @@ def add(self, x, y):
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,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added :class:`functools.partialclass`, which implements "partial class
application".