diff options
author | Nick Coghlan <ncoghlan@gmail.com> | 2013-11-03 06:41:46 (GMT) |
---|---|---|
committer | Nick Coghlan <ncoghlan@gmail.com> | 2013-11-03 06:41:46 (GMT) |
commit | f4cb48a72b20b2d166d5303426ab4098243c35e1 (patch) | |
tree | 030f58e9648df6a8a92a4f28842dd2393f4b1079 /Lib/functools.py | |
parent | b19ff4174183e1e519c99243cff0e3c30e9ac258 (diff) | |
download | cpython-f4cb48a72b20b2d166d5303426ab4098243c35e1.zip cpython-f4cb48a72b20b2d166d5303426ab4098243c35e1.tar.gz cpython-f4cb48a72b20b2d166d5303426ab4098243c35e1.tar.bz2 |
Issue #4331: Added functools.partialmethod
Initial patch by Alon Horev
Diffstat (limited to 'Lib/functools.py')
-rw-r--r-- | Lib/functools.py | 78 |
1 files changed, 76 insertions, 2 deletions
diff --git a/Lib/functools.py b/Lib/functools.py index 6a6974f..8989361 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -19,7 +19,7 @@ except ImportError: pass from abc import get_cache_token from collections import namedtuple -from types import MappingProxyType +from types import MappingProxyType, MethodType from weakref import WeakKeyDictionary try: from _thread import RLock @@ -223,8 +223,9 @@ except ImportError: ### partial() argument application ################################################################################ +# Purely functional, no descriptor behaviour def partial(func, *args, **keywords): - """new function with partial application of the given arguments + """New function with partial application of the given arguments and keywords. """ def newfunc(*fargs, **fkeywords): @@ -241,6 +242,79 @@ try: except ImportError: pass +# Descriptor version +class partialmethod(object): + """Method descriptor with partial application of the given arguments + and keywords. + + Supports wrapping existing descriptors and handles non-descriptor + callables as instance methods. + """ + + def __init__(self, func, *args, **keywords): + if not callable(func) and not hasattr(func, "__get__"): + raise TypeError("{!r} is not callable or a descriptor" + .format(func)) + + # func could be a descriptor like classmethod which isn't callable, + # so we can't inherit from partial (it verifies func is callable) + if isinstance(func, partialmethod): + # flattening is mandatory in order to place cls/self before all + # other arguments + # it's also more efficient since only one function will be called + self.func = func.func + self.args = func.args + args + self.keywords = func.keywords.copy() + self.keywords.update(keywords) + else: + self.func = func + self.args = args + self.keywords = keywords + + def __repr__(self): + args = ", ".join(map(repr, self.args)) + keywords = ", ".join("{}={!r}".format(k, v) + for k, v in self.keywords.items()) + format_string = "{module}.{cls}({func}, {args}, {keywords})" + return format_string.format(module=self.__class__.__module__, + cls=self.__class__.__name__, + func=self.func, + args=args, + keywords=keywords) + + def _make_unbound_method(self): + def _method(*args, **keywords): + call_keywords = self.keywords.copy() + call_keywords.update(keywords) + cls_or_self, *rest = args + call_args = (cls_or_self,) + self.args + tuple(rest) + return self.func(*call_args, **call_keywords) + _method.__isabstractmethod__ = self.__isabstractmethod__ + return _method + + def __get__(self, obj, cls): + get = getattr(self.func, "__get__", None) + result = None + if get is not None: + new_func = get(obj, cls) + if new_func is not self.func: + # Assume __get__ returning something new indicates the + # creation of an appropriate callable + result = partial(new_func, *self.args, **self.keywords) + try: + result.__self__ = new_func.__self__ + except AttributeError: + pass + if result is None: + # If the underlying descriptor didn't do anything, treat this + # like an instance method + result = self._make_unbound_method().__get__(obj, cls) + return result + + @property + def __isabstractmethod__(self): + return getattr(self.func, "__isabstractmethod__", False) + ################################################################################ ### LRU Cache function decorator |