-
-
Notifications
You must be signed in to change notification settings - Fork 32k
functools.partial placeholders #119127
New issue
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
Comments
Note, this has been discussed and rejected multiple times in the past. Please specifically address these comments:
Also see:
Leaving this open for further comment and reconsideration by other developers. |
[quote="Raymond Hettinger, post:20, topic:19163, username:rhettinger"] In https://discuss.python.org/t/functools-partial-placeholder-arguments/53487 I have laid out several examples and use cases for this. But the main gain here is speed improvement by being able to efficiently utilise functions from [quote="Raymond Hettinger, post:9, topic:19163, username:rhettinger"]
p3 = partial2(opr.sub, _, 1)
print(inspect.signature(p3.func)) # <Signature (a, b, /)>
print(p3.args) # (<partial2.Placeholder object at 0x111e95ad0>, 1) Placeholder sentinel still needs work, it would have much nicer representation. E.g: Personally, I would make use of this functionality in algorithm recipes, iterator recipes, utility functions and similar places of lower level code where performance is important. And I don't see this as Also, Why I think this deserves reconsideration is that this hasn't been implemented and benchmarked before. All the previous attempts were in python, where instead of performance gain which has led me to this point, the result was a significant performance loss - up to 20x slower than current standard library implementation. So naturally, those are not used, because While before supporting reasoning was convenience, Linters, MyPy issues and similar, my main argument is performance. |
I've left this open for comment and no one else has stepped in to opine, so I'm going to go ahead and approve it. The API is a minimal addition that gives more flexibility without venturing into creating a mini-DSL. It is unclear whether it will present any typing challenges beyond what The PR needs a lot of work but the concept is sound. I've been trying it out for a while and it looks reasonable in real code. It supports multiple placeholders but in the examples I've found only one is needed. I'm dubious about the motivation as an optimization (PyPy doesn't need this and CPython may yet optimize a plain |
FWIW, the example I most enjoyed is |
Personally, most of the time I need a placeholder for is when I want right partialization, not left partialization. While there are times where partialization in arbitrary order makes sense, an alternative would to provide |
Although, there is a case which is supported by |
I like functional programming a lot (proof: https://github.com/dry-python/returns), but I don't think that this API is nice. Here are my thoughts:
It is nice as a separate package, but I don't feel like I would like to use it in the stdlib. Or teach how to use it in the stdlib. Sorry :( |
This is either not correct or I am failing to understand what you mean. |
There is a question: how interpret |
Yeah, I don't see any need for this. And it would indeed be difficult, I think there is enough of complexity for now to settle. I think there is no harm in literal treatment of such. At least I can not see any potential issues here. It just gets treated as any other object. I tried to come up with potential benefits. E.g. passing So I am generally neutral, but slightly positive leaving it as it is due to performance. |
It is easier to make valid something that was previously invalid than to change semantic of already valid expression. |
So I am still unsure about this. If it was free, it would be natural. However as I suspected it is quite costly. Effect on instantiation for both versions given the number of keyword arguments:
For someone who mostly cares about Is the chance to make use of Also, is there any significant risk that someone is going to pass |
Btw, python wrapper for your mentioned functionality: import functools as ftl
def partial(func, *args, **kwds):
"""keyword Placeholders will be filled with priority by positionals
Examples:
>>> def foo(a, b, *, c=0, d=0):
... return a, b, c, d
>>> p = partial_kwph(foo, Placeholder, 2, c=Placeholder, d=4)
>>> p(3, 1)
(1, 2, 3, 4)
"""
kw_keys = [k for k, v in kwds.items() if v is ftl.Placeholder]
if not kw_keys:
return ftl.partial(func, *args, **kwds)
else:
kw_enum = list(enumerate(kw_keys))
n_kwph = len(kw_enum)
fprtl = ftl.partial(func, *args)
def wrapped(*call_args, **call_kwds):
total_kw = kwds
for i, k in kw_enum:
total_kw[k] = call_args[i]
if call_kwds:
total_kw = {**total_kw, **call_kwds}
return fprtl(*call_args[n_kwph:], **total_kw)
return wrapped
def foo(a, b, *, c, d):
return a, b, c, d
p = partial(foo, ftl.Placeholder, 2, c=ftl.Placeholder, d=4)
print(p(3, 1)) # (1, 2, 3, 4) |
This is slightly less than 2x slower, but given the rarity of such need I think it is good enough: def foo(a, b, *, c, d):
return a, b, c, d
p0 = ftl.partial(foo, 1, c=3, d=4)
p1 = ftl.partial(foo, ftl.Placeholder, 2, c=3, d=4)
p2 = partial(foo, ftl.Placeholder, 2, c=ftl.Placeholder, d=4)
lembdas = {
'without_PH': lambda: without_PH(2),
'with_PH': lambda: with_PH(1),
'with_kwPH': lambda: with_kwPH(3, 1)
}
# Note, all functions return: (1, 2, 3, 4)
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ macOS-11.7.10-x86_64-i386-64bit-Mach-O | CPython: 3.14.0a0 ┃
┃ 5 repeats, 100,000 times ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ Units: ns 0 ┃
┃ ┏━━━━━━━━━━━━━━━━━━━━┫
┃ without_PH ┃ 304 ┃
┃ with_PH ┃ 298 ┃
┃ with_kwPH ┃ 587 ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━┛ |
Well, this is not critical. If the use of Placeholder is always documented only for positional arguments, we can leave the use as a keyword argument unspecified. We can add additional checks before 3.14 release. The only thing I have concern about right now is unnecessary complication of |
Apologies for delay, but this, although might seem trivial, to me is the hardest part of this PR. Mostly because there is little to grab on to as there are no clear guidelines/protocols/examples to follow for this yet.
Another reason is that advantages/disadvantages of different options are very small in absolute sense. Both are valid and neither has any significant issues. So the question is the following a) or b)?:
Apart from import typing
type(typing.NoDefault)() is typing.NoDefault # True Note, it's type is not serializable: import pickle
pickle.dumps(type(typing.NoDefault))
# _pickle.PicklingError: Can't pickle <class 'NoDefaultType'>: attribute lookup NoDefaultType on builtins failed
If Thus, in terms of technical complexity, a) is slightly simpler as it breaks fewer standard protocols (in case the type is serializable). In terms of conceptual complexity, I would say it is ties. Special exception to raise error, which breaks standard instantiation protocol is as complicated as returning singleton instance instead.
It is not uncommon to have a pattern, where default is lazy function. E.g.: def get(self, key, lazy_default=None):
if key not in self and default is not None:
return lazy_default()
return self[key] In this case, there is an option to: There are possibly other similar small benefits of object and its type mimicking standard type/object instantiation protocol instead of raising an error. So in this respect, I would say a) has a slight advantage. |
There is 1 point which is in favour of b). There is less chance that the user will depend on However, I don't think this is the main consideration as I think choosing more appropriate option from the very beginning would be better so that the probability for future need of reversion is minimised. After all, the cost function for reverting is not simply a) vs b), but rather:
Where So I would rather pick more appropriate option from the beginning. Maybe someone who knows the rationale for making To me So currently my position is that A is more appropriate. |
I still prefer option b. We should not add feature without a clear use case. Actually, we should discourage any use of Waiting on @rhettinger. |
Edits for improving the clarity and writing of the docs for partial.
* The module state now stores a strong reference to the Placeholder singleton. * Use a regular dealloc function. * Add Py_TPFLAGS_HAVE_GC flag and traverse function to help the GC to collect the type when _functools extension is unloaded.
* The module state now stores a strong reference to the Placeholder singleton. * Use a regular dealloc function. * Add Py_TPFLAGS_HAVE_GC flag and traverse function to help the GC to collect the type when _functools extension is unloaded.
* The module state now stores a strong reference to the Placeholder singleton. * Use a regular dealloc function. * Add Py_TPFLAGS_HAVE_GC flag and traverse function to help the GC to collect the type when _functools extension is unloaded.
* The module state now stores a strong reference to the Placeholder singleton. * Use a regular dealloc function. * Add Py_TPFLAGS_HAVE_GC flag and a traverse function to help the GC to collect the type when a _functools extension is unloaded.
3 follow-up PRs.
|
FutureWarning: functools.partial will be a method descriptor in future Python versions; wrap it in staticmethod() if you want to preserve the old behavior Context: - python/cpython#121027 - python/cpython#119127
FutureWarning: functools.partial will be a method descriptor in future Python versions; wrap it in staticmethod() if you want to preserve the old behavior Context: - python/cpython#121027 - python/cpython#119127
FutureWarning: functools.partial will be a method descriptor in future Python versions; wrap it in staticmethod() if you want to preserve the old behavior Context: - python/cpython#121027 - python/cpython#119127
FutureWarning: functools.partial will be a method descriptor in future Python versions; wrap it in staticmethod() if you want to preserve the old behavior Context: - python/cpython#121027 - python/cpython#119127
Uh oh!
There was an error while loading. Please reload this page.
Feature or enhancement
Proposal:
I know that this has been brought up in the past, but I would appreciate if this was reconsidered. I would be happy to bring this up to a nice level.
The logic is as follows:
vectorcall
change looks like:Has this already been discussed elsewhere?
I have already discussed this feature proposal on Discourse
Links to previous discussion of this feature:
https://discuss.python.org/t/functools-partial-placeholder-arguments/53487
Linked PRs
The text was updated successfully, but these errors were encountered: