| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| Expand Up | @@ -6,17 +6,18 @@ | |
| # Written by Nick Coghlan <ncoghlan at gmail.com>, | ||
| # Raymond Hettinger <python at rcn.com>, | ||
| # and Łukasz Langa <lukasz at langa.pl>. | ||
| # Copyright (C) 2006-2013 Python Software Foundation. | ||
| # Copyright (C) 2006-2024 Python Software Foundation. | ||
| # See C source code for _functools credits/copyright | ||
|
|
||
| __all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES', | ||
| 'total_ordering', 'cache', 'cmp_to_key', 'lru_cache', 'reduce', | ||
| 'partial', 'partialmethod', 'singledispatch', 'singledispatchmethod', | ||
| 'cached_property'] | ||
| 'cached_property', 'Placeholder'] | ||
|
|
||
| from abc import get_cache_token | ||
| from collections import namedtuple | ||
| # import types, weakref # Deferred to single_dispatch() | ||
| from operator import itemgetter | ||
| from reprlib import recursive_repr | ||
| from types import MethodType | ||
| from _thread import RLock | ||
| Expand Down Expand Up | @@ -274,43 +275,125 @@ def reduce(function, sequence, initial=_initial_missing): | |
| ### partial() argument application | ||
| ################################################################################ | ||
|
|
||
| # Purely functional, no descriptor behaviour | ||
| class partial: | ||
| """New function with partial application of the given arguments | ||
| and keywords. | ||
|
|
||
| class _PlaceholderType: | ||
| """The type of the Placeholder singleton. | ||
|
|
||
| Used as a placeholder for partial arguments. | ||
| """ | ||
| __instance = None | ||
|
Comment thread
dg-pb marked this conversation as resolved.
|
||
| __slots__ = () | ||
|
|
||
| def __init_subclass__(cls, *args, **kwargs): | ||
| raise TypeError(f"type '{cls.__name__}' is not an acceptable base type") | ||
|
|
||
| __slots__ = "func", "args", "keywords", "__dict__", "__weakref__" | ||
| def __new__(cls): | ||
| if cls.__instance is None: | ||
| cls.__instance = object.__new__(cls) | ||
| return cls.__instance | ||
|
Comment thread
Comment on lines
+290
to
+293
Copy link
Copy Markdown
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityI think this is overcomplication. The user has no reasons to create an instance of private class _PlaceholderType. And if they need, they can do it anyway by using object.__new__(_PlaceholderType). If you want to add some guards here, just make __new__ always raising an exception and create the single instance as object.__new__(_PlaceholderType).
Sorry, something went wrong.
merwok reacted with thumbs up emoji
All reactions
Copy link
Copy Markdown
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityThis was to mimic C implementation, which has the behaviour of None. I think the question is: "Is it a good practice for a non-trivial sentinel to be singleton, i.e. type(None)() is None. If yes and this sentinel is considered non-trivial, then this is as good as it can get for now and protection issues can be sorted out together with further developments in this area. If no, then this needs to be changed for both C and Python. @rhettinger has suggested this initially and I like this behaviour (and adapted it to my own sentinels). It would be good if you together could come to agreement before I make any further changes here.
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityThis is not required for its main function, and this complicates both implementations. It is better to implement only necessary parts. If later we will find a need of this feature, it will be easier to add it than to remove it. Strictly speaking, making the Placeholder class non-inheritable and non-instantiable is not required. But it is easy to implement. I hope Raymond will change his opinion on this.
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityWhat you are saying makes sense, but at the same time I like current behaviour and if sentinels were standardised and their creation was made more convenient I think this elegant behaviour would be nice to get by default. I am neutral by now on this specific case. Well, slightly negative just because I put thought and effort into this and simply like it.
Sorry, something went wrong.
All reactions
|
||
|
|
||
| def __repr__(self): | ||
| return 'Placeholder' | ||
|
|
||
| def __new__(cls, func, /, *args, **keywords): | ||
| def __reduce__(self): | ||
| return 'Placeholder' | ||
|
|
||
| Placeholder = _PlaceholderType() | ||
|
|
||
| def _partial_prepare_merger(args): | ||
| if not args: | ||
| return 0, None | ||
| nargs = len(args) | ||
| order = [] | ||
| j = nargs | ||
| for i, a in enumerate(args): | ||
| if a is Placeholder: | ||
| order.append(j) | ||
| j += 1 | ||
| else: | ||
| order.append(i) | ||
| phcount = j - nargs | ||
| merger = itemgetter(*order) if phcount else None | ||
| return phcount, merger | ||
|
|
||
| def _partial_new(cls, func, /, *args, **keywords): | ||
| if issubclass(cls, partial): | ||
| base_cls = partial | ||
| if not callable(func): | ||
| raise TypeError("the first argument must be callable") | ||
| else: | ||
| base_cls = partialmethod | ||
| # func could be a descriptor like classmethod which isn't callable | ||
| if not callable(func) and not hasattr(func, "__get__"): | ||
| raise TypeError(f"the first argument {func!r} must be a callable " | ||
| "or a descriptor") | ||
| if args and args[-1] is Placeholder: | ||
| raise TypeError("trailing Placeholders are not allowed") | ||
|
Comment thread
dg-pb marked this conversation as resolved.
|
||
| if isinstance(func, base_cls): | ||
| pto_phcount = func._phcount | ||
| tot_args = func.args | ||
| if args: | ||
| tot_args += args | ||
| if pto_phcount: | ||
| # merge args with args of `func` which is `partial` | ||
| nargs = len(args) | ||
| if nargs < pto_phcount: | ||
| tot_args += (Placeholder,) * (pto_phcount - nargs) | ||
| tot_args = func._merger(tot_args) | ||
| if nargs > pto_phcount: | ||
| tot_args += args[pto_phcount:] | ||
| phcount, merger = _partial_prepare_merger(tot_args) | ||
| else: # works for both pto_phcount == 0 and != 0 | ||
| phcount, merger = pto_phcount, func._merger | ||
| keywords = {**func.keywords, **keywords} | ||
| func = func.func | ||
| else: | ||
| tot_args = args | ||
| phcount, merger = _partial_prepare_merger(tot_args) | ||
|
|
||
| self = object.__new__(cls) | ||
| self.func = func | ||
| self.args = tot_args | ||
| self.keywords = keywords | ||
| self._phcount = phcount | ||
| self._merger = merger | ||
| return self | ||
|
|
||
| def _partial_repr(self): | ||
| cls = type(self) | ||
| module = cls.__module__ | ||
| qualname = cls.__qualname__ | ||
| args = [repr(self.func)] | ||
| args.extend(map(repr, self.args)) | ||
| args.extend(f"{k}={v!r}" for k, v in self.keywords.items()) | ||
| return f"{module}.{qualname}({', '.join(args)})" | ||
|
|
||
| if isinstance(func, partial): | ||
| args = func.args + args | ||
| keywords = {**func.keywords, **keywords} | ||
| func = func.func | ||
| # Purely functional, no descriptor behaviour | ||
| class partial: | ||
| """New function with partial application of the given arguments | ||
| and keywords. | ||
| """ | ||
|
|
||
| self = super(partial, cls).__new__(cls) | ||
| __slots__ = ("func", "args", "keywords", "_phcount", "_merger", | ||
| "__dict__", "__weakref__") | ||
|
|
||
| self.func = func | ||
| self.args = args | ||
| self.keywords = keywords | ||
| return self | ||
| __new__ = _partial_new | ||
| __repr__ = recursive_repr()(_partial_repr) | ||
|
|
||
| def __call__(self, /, *args, **keywords): | ||
| phcount = self._phcount | ||
| if phcount: | ||
| try: | ||
| pto_args = self._merger(self.args + args) | ||
| args = args[phcount:] | ||
| except IndexError: | ||
| raise TypeError("missing positional arguments " | ||
| "in 'partial' call; expected " | ||
| f"at least {phcount}, got {len(args)}") | ||
| else: | ||
| pto_args = self.args | ||
| keywords = {**self.keywords, **keywords} | ||
| return self.func(*self.args, *args, **keywords) | ||
|
|
||
| @recursive_repr() | ||
| def __repr__(self): | ||
| cls = type(self) | ||
| qualname = cls.__qualname__ | ||
| module = cls.__module__ | ||
| 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()) | ||
| return f"{module}.{qualname}({', '.join(args)})" | ||
| return self.func(*pto_args, *args, **keywords) | ||
|
|
||
| def __get__(self, obj, objtype=None): | ||
| if obj is None: | ||
| Expand All | @@ -332,6 +415,10 @@ def __setstate__(self, state): | |
| (namespace is not None and not isinstance(namespace, dict))): | ||
| raise TypeError("invalid partial state") | ||
|
|
||
| if args and args[-1] is Placeholder: | ||
| raise TypeError("trailing Placeholders are not allowed") | ||
| phcount, merger = _partial_prepare_merger(args) | ||
|
|
||
| args = tuple(args) # just in case it's a subclass | ||
| if kwds is None: | ||
| kwds = {} | ||
| Expand All | @@ -344,53 +431,40 @@ def __setstate__(self, state): | |
| self.func = func | ||
| self.args = args | ||
| self.keywords = kwds | ||
| self._phcount = phcount | ||
| self._merger = merger | ||
|
|
||
| try: | ||
| from _functools import partial | ||
| from _functools import partial, Placeholder, _PlaceholderType | ||
| except ImportError: | ||
| pass | ||
|
|
||
| # Descriptor version | ||
| class partialmethod(object): | ||
| class partialmethod: | ||
| """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, **keywords} | ||
| else: | ||
| self.func = func | ||
| self.args = args | ||
| self.keywords = keywords | ||
|
|
||
| def __repr__(self): | ||
| cls = type(self) | ||
| module = cls.__module__ | ||
| qualname = cls.__qualname__ | ||
| args = [repr(self.func)] | ||
| args.extend(map(repr, self.args)) | ||
| args.extend(f"{k}={v!r}" for k, v in self.keywords.items()) | ||
| return f"{module}.{qualname}({', '.join(args)})" | ||
| __new__ = _partial_new | ||
| __repr__ = _partial_repr | ||
|
|
||
| def _make_unbound_method(self): | ||
| def _method(cls_or_self, /, *args, **keywords): | ||
| phcount = self._phcount | ||
| if phcount: | ||
| try: | ||
| pto_args = self._merger(self.args + args) | ||
| args = args[phcount:] | ||
| except IndexError: | ||
| raise TypeError("missing positional arguments " | ||
| "in 'partialmethod' call; expected " | ||
| f"at least {phcount}, got {len(args)}") | ||
| else: | ||
| pto_args = self.args | ||
| keywords = {**self.keywords, **keywords} | ||
| return self.func(cls_or_self, *self.args, *args, **keywords) | ||
| return self.func(cls_or_self, *pto_args, *args, **keywords) | ||
| _method.__isabstractmethod__ = self.__isabstractmethod__ | ||
| _method.__partialmethod__ = self | ||
| return _method | ||
| Expand Down | ||
| Back | FazBrowse Home | New Git URL |
Uh oh!
There was an error while loading. Please reload this page.