-
-
Notifications
You must be signed in to change notification settings - Fork 30k
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
bpo-32380: Create functools.singledispatchmethod #6306
Conversation
functools.singledispatch interacts poorly with methods and descriptors, thus functools.singledispatchmethod is created to handles these.
Hm, I'm not sure what is going on with the unexpected indent here. It seems valid. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks promising, thanks for working on it.
@@ -2147,6 +2147,73 @@ def __eq__(self, other): | |||
return self.arg == other | |||
self.assertEqual(i("str"), "str") | |||
|
|||
def test_method_register(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see tests that show that registration of arbitrary callables from outside of the class definition also works.
Something like:
class C:
@singledispatchmethod
def g(self, arg):
...
@C.g.register(int)
def _(self, arg):
...
Doc/library/functools.rst
Outdated
raise NotImplementedError("Cannot negate a") | ||
|
||
@neg.register(int): | ||
def _(self, arg): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please document using type annotations as the default way to register types, just like the singledispatch documentation in 3.7+ does.
Lib/functools.py
Outdated
def __init__(self, func): | ||
if not callable(func) and not hasattr(func, "__get__"): | ||
raise TypeError("{!r} is not callable or a descriptor" | ||
.format(func)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this formatting is ugly. Consider:
raise TypeError(
f"{func!r} is not callable or a descriptor"
)
Might even fit in a single line with the f-string.
A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated. Once you have made the requested changes, please leave a comment on this pull request containing the phrase |
Doc/library/functools.rst
Outdated
like :func:`singledispatch` except that it is designed to be used as a | ||
method definition rather than being directly callable. | ||
|
||
:class:`singledispatchmethod` can be used as follows: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The unexpected indent is likely because you didn't use double colon (::
) here. But I don't know if the empty line doesn't denote an end of the code block in RST. Look for other examples where stuff like this is used in the docs, they will probably hint at a good solution.
I have made the requested changes; please review again Thanks for the review Łukasz! |
Thanks for making the requested changes! @ambv: please review the changes made to this pull request. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some documentation suggests inline, but I otherwise don't have anything to add to @ambv's comments.
Nice work on remembering to test the interactions with other standard library decorators!
Doc/library/functools.rst
Outdated
@@ -383,6 +383,28 @@ The :mod:`functools` module defines the following functions: | |||
The :func:`register` attribute supports using type annotations. | |||
|
|||
|
|||
.. class:: singledispatchmethod(func) | |||
|
|||
Return a new :class:`singledispatchmethod` descriptor which behaves |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While this is implemented as a class, I'd suggest borrowing the markup and introductory text from singledispatch
and reusing an adjusted version of that text here - from an end user's perspective, the important point is that they can use singledispatchmethod
to decorate a method definition and have it dispatch on the first non-self argument.
@neg.register | ||
def _(self, arg: bool): | ||
return not arg | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having an example stacked with @classmethod
would be good here. It's unfortunate that the order of application will need to be different from that when stacking @abstractmethod
(see https://docs.python.org/3/library/abc.html#abc.abstractmethod), but we need "register" to be accessible on the resulting descriptor. So something like:
class Negator:
@singledispatchmethod
@classmethod
def neg(cls, arg):
...
Given the @classmethod
example, you can then note that the same principle applies for @staticmethod
and other decorators - to be useful, @singledispatchmethod
typically needs to be the outermost decorator so that the register
method is accessible.
@ncoghlan thanks for the suggestions! I amended the docs as you asked. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is almost there. Two remaining requests:
- in the Negator example in the docs, use cls, not self
- in the tests, use registered implementations which actually use the relevant cls or self to prove they bind correctly; especially in the arbitrary callable case.
Done! Let me know if you have further feedback. |
I'll wait a few days before merging in case @rhettinger has any input. |
What is the status here? This seems ready to be merged. (Or maybe it should also support annotations like |
@ilevkivskyi waiting on naming approval. Also, It does support annotations see https://github.com/python/cpython/pull/6306/files#diff-09cc1e5ad87c6bd130e5b417193ed883R2251 |
@ambv: Please replace |
Thanks! ✨ 🍰 ✨ |
I finally found time to follow up after #4987. In review: functools.singledispatch interacts poorly with methods and descriptors, thus I created functools.singledispatchmethod to handles these.
This implementation is based on that of
functools.partialmethod
.https://bugs.python.org/issue32380