1 | from functools import partial |
||
2 | |||
3 | |||
4 | # FIXME: Remove `partialmethod` when dropping Python 3.3 support. |
||
5 | # This code falls under the Python 3.4.3 license: |
||
6 | # https://www.python.org/download/releases/3.4.3/license/ |
||
7 | # Descriptor version |
||
8 | class partialmethod(object): # pragma: no cover |
||
9 | """Method descriptor with partial application of the given arguments |
||
10 | and keywords. |
||
11 | |||
12 | Supports wrapping existing descriptors and handles non-descriptor |
||
13 | callables as instance methods. |
||
14 | """ |
||
15 | |||
16 | def __init__(self, func, *args, **keywords): |
||
17 | if not callable(func) and not hasattr(func, "__get__"): |
||
18 | raise TypeError("{!r} is not callable or a descriptor" |
||
19 | .format(func)) |
||
20 | |||
21 | # func could be a descriptor like classmethod which isn't callable, |
||
22 | # so we can't inherit from partial (it verifies func is callable) |
||
23 | if isinstance(func, partialmethod): |
||
24 | # flattening is mandatory in order to place cls/self before all |
||
25 | # other arguments |
||
26 | # it's also more efficient since only one function will be called |
||
27 | self.func = func.func |
||
28 | self.args = func.args + args |
||
29 | self.keywords = func.keywords.copy() |
||
30 | self.keywords.update(keywords) |
||
31 | else: |
||
32 | self.func = func |
||
33 | self.args = args |
||
34 | self.keywords = keywords |
||
35 | |||
36 | def __repr__(self): |
||
37 | args = ", ".join(map(repr, self.args)) |
||
38 | keywords = ", ".join("{}={!r}".format(k, v) |
||
39 | for k, v in self.keywords.items()) |
||
40 | format_string = "{module}.{cls}({func}, {args}, {keywords})" |
||
41 | return format_string.format(module=self.__class__.__module__, |
||
42 | cls=self.__class__.__name__, |
||
43 | func=self.func, |
||
44 | args=args, |
||
45 | keywords=keywords) |
||
46 | |||
47 | def _make_unbound_method(self): |
||
48 | def _method(*args, **keywords): |
||
49 | call_keywords = self.keywords.copy() |
||
50 | call_keywords.update(keywords) |
||
51 | cls_or_self, *rest = args |
||
52 | call_args = (cls_or_self,) + self.args + tuple(rest) |
||
53 | return self.func(*call_args, **call_keywords) |
||
54 | _method.__isabstractmethod__ = self.__isabstractmethod__ |
||
55 | _method._partialmethod = self |
||
56 | return _method |
||
57 | |||
58 | def __get__(self, obj, cls): |
||
59 | get = getattr(self.func, "__get__", None) |
||
60 | result = None |
||
61 | if get is not None: |
||
62 | new_func = get(obj, cls) |
||
63 | if new_func is not self.func: |
||
64 | # Assume __get__ returning something new indicates the |
||
65 | # creation of an appropriate callable |
||
66 | result = partial(new_func, *self.args, **self.keywords) |
||
67 | try: |
||
68 | result.__self__ = new_func.__self__ |
||
69 | except AttributeError: |
||
70 | pass |
||
71 | if result is None: |
||
72 | # If the underlying descriptor didn't do anything, treat this |
||
73 | # like an instance method |
||
74 | result = self._make_unbound_method().__get__(obj, cls) |
||
75 | return result |
||
76 | |||
77 | @property |
||
78 | def __isabstractmethod__(self): |
||
79 | return getattr(self.func, "__isabstractmethod__", False) |
||
0 ignored issues
–
show
Coding Style
introduced
by
![]() |