diff options
author | Nick Coghlan <ncoghlan@gmail.com> | 2016-09-10 10:00:02 (GMT) |
---|---|---|
committer | Nick Coghlan <ncoghlan@gmail.com> | 2016-09-10 10:00:02 (GMT) |
commit | 457fc9a69e7918e3c10319604903915c2f2ecd9a (patch) | |
tree | a5c07e94a0442b8e7ac303ebf3643295a1c16888 /Lib/functools.py | |
parent | eddc4b7272c310fc5008c8c826ed70fa936cd661 (diff) | |
download | cpython-457fc9a69e7918e3c10319604903915c2f2ecd9a.zip cpython-457fc9a69e7918e3c10319604903915c2f2ecd9a.tar.gz cpython-457fc9a69e7918e3c10319604903915c2f2ecd9a.tar.bz2 |
Issue #27137: align Python & C implementations of functools.partial
The pure Python fallback implementation of functools.partial
now matches the behaviour of its accelerated C counterpart for
subclassing, pickling and text representation purposes.
Patch by Emanuel Barry and Serhiy Storchaka.
Diffstat (limited to 'Lib/functools.py')
-rw-r--r-- | Lib/functools.py | 92 |
1 files changed, 75 insertions, 17 deletions
diff --git a/Lib/functools.py b/Lib/functools.py index 214523c..9845df2 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -21,6 +21,7 @@ from abc import get_cache_token from collections import namedtuple from types import MappingProxyType from weakref import WeakKeyDictionary +from reprlib import recursive_repr try: from _thread import RLock except ImportError: @@ -237,26 +238,83 @@ except ImportError: ################################################################################ # Purely functional, no descriptor behaviour -def partial(func, *args, **keywords): +class partial: """New function with partial application of the given arguments and keywords. """ - if hasattr(func, 'func'): - args = func.args + args - tmpkw = func.keywords.copy() - tmpkw.update(keywords) - keywords = tmpkw - del tmpkw - func = func.func - - def newfunc(*fargs, **fkeywords): - newkeywords = keywords.copy() - newkeywords.update(fkeywords) - return func(*(args + fargs), **newkeywords) - newfunc.func = func - newfunc.args = args - newfunc.keywords = keywords - return newfunc + + __slots__ = "func", "args", "keywords", "__dict__", "__weakref__" + + def __new__(*args, **keywords): + if not args: + raise TypeError("descriptor '__new__' of partial needs an argument") + if len(args) < 2: + raise TypeError("type 'partial' takes at least one argument") + cls, func, *args = args + if not callable(func): + raise TypeError("the first argument must be callable") + args = tuple(args) + + if hasattr(func, "func"): + args = func.args + args + tmpkw = func.keywords.copy() + tmpkw.update(keywords) + keywords = tmpkw + del tmpkw + func = func.func + + self = super(partial, cls).__new__(cls) + + self.func = func + self.args = args + self.keywords = keywords + return self + + def __call__(*args, **keywords): + if not args: + raise TypeError("descriptor '__call__' of partial needs an argument") + self, *args = args + newkeywords = self.keywords.copy() + newkeywords.update(keywords) + return self.func(*self.args, *args, **newkeywords) + + @recursive_repr() + def __repr__(self): + qualname = type(self).__qualname__ + args = [repr(self.func)] + args.extend(repr(x) for x in self.args) + args.extend(f"{k}={v!r}" for (k, v) in self.keywords.items()) + if type(self).__module__ == "functools": + return f"functools.{qualname}({', '.join(args)})" + return f"{qualname}({', '.join(args)})" + + def __reduce__(self): + return type(self), (self.func,), (self.func, self.args, + self.keywords or None, self.__dict__ or None) + + def __setstate__(self, state): + if not isinstance(state, tuple): + raise TypeError("argument to __setstate__ must be a tuple") + if len(state) != 4: + raise TypeError(f"expected 4 items in state, got {len(state)}") + func, args, kwds, namespace = state + if (not callable(func) or not isinstance(args, tuple) or + (kwds is not None and not isinstance(kwds, dict)) or + (namespace is not None and not isinstance(namespace, dict))): + raise TypeError("invalid partial state") + + args = tuple(args) # just in case it's a subclass + if kwds is None: + kwds = {} + elif type(kwds) is not dict: # XXX does it need to be *exactly* dict? + kwds = dict(kwds) + if namespace is None: + namespace = {} + + self.__dict__ = namespace + self.func = func + self.args = args + self.keywords = kwds try: from _functools import partial |