summaryrefslogtreecommitdiffstats
path: root/Lib/functools.py
diff options
context:
space:
mode:
authorNick Coghlan <ncoghlan@gmail.com>2016-09-10 10:00:02 (GMT)
committerNick Coghlan <ncoghlan@gmail.com>2016-09-10 10:00:02 (GMT)
commit457fc9a69e7918e3c10319604903915c2f2ecd9a (patch)
treea5c07e94a0442b8e7ac303ebf3643295a1c16888 /Lib/functools.py
parenteddc4b7272c310fc5008c8c826ed70fa936cd661 (diff)
downloadcpython-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.py92
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