-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
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
gh-120381: Fix inspect.ismethoddescriptor() #120383
Conversation
The inspect.ismethoddescriptor() function did not check for the lack of __delete__() and, consequently, erroneously returned True when applied to *data* descriptors with __get__() and __delete__().
Seems important: As I noted in gh-120381, a care is needed when deciding to which Python versions this fix should be applied. IMHO, the fix can be safely applied to the branches 3.14 (main) and 3.13 (still in the beta phase), yet backporting it to any of the earlier versions might be too disruptive. Obviously, the decision will be made by a core developer, not me. :) |
@@ -0,0 +1,2 @@ | |||
Correct ``inspect.ismethoddescriptor`` to check also for the lack of |
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.
Correct ``inspect.ismethoddescriptor`` to check also for the lack of | |
Correct :meth:`inspect.ismethoddescriptor` to check also for the lack of |
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.
OK, fixed both references (actually, the first one is a :func:
one, and the second is a :meth:
one).
# excludes them): | ||
self.assertFalse(inspect.ismethoddescriptor(Owner().instance_method)) | ||
self.assertFalse(inspect.ismethoddescriptor(Owner().class_method)) | ||
self.assertFalse(inspect.ismethoddescriptor(Owner().static_method)) |
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.
self.assertFalse(inspect.ismethoddescriptor(Owner().static_method)) | |
self.assertFalse(inspect.ismethoddescriptor(Owner().static_method)) | |
self.assertFalse(inspect.ismethoddescriptor(Owner.instance_method)) | |
self.assertFalse(inspect.ismethoddescriptor(Owner.class_method)) | |
self.assertFalse(inspect.ismethoddescriptor(Owner.static_method)) |
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.
Frankly, I'd rather prefer to reduce the number of these cases...
I'd keep:
Owner().instance_method
(method object – bound to an instance),Owner().class_method
(method object – bound to a class),Owner().static_method
(just a function object)
I'd remove: function
and a_lambda
completely – each of them is also just a function object, so they are redundant.
When it comes to the ones you proposed, please note that Owner.instance_method
and Owner.static_method
are also just function objects, and Owner.class_method
is a method object bound to a class (identical to the Owner().class_method
one).
This test class should focus on the behavior of inspect.ismethoddescriptor()
. Various ways of obtaining objects of the same type are out of scope of it.
(In the first version, I wanted to mimic some elements of the already existing test class TestIsDataDescriptor
. But now I believe that that one should be improved instead – though rather not as a part of this PR).
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.
Do you agree with my reasoning, or would you prefer these new cases to be added anyway? Could you please decide, I have no strong feelings about this particular detail. :)
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 prefer having all possible cases. One reason is that it shows how robust the inspect
is and for me (at Sphinx), it's quite important to have a robust inspect
module. So, if there are some possibilities in the future where something could go wrong (hello Murphy), it's better to have a wide coverage.
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.
OK, added :)
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.
Thank you! (it also implicitly helps in asserting that a function and a lambda function have the same type)
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.
Thanks for the extensive tests in the PR. I left two minor comments, but otherwise this looks good.
has a :meth:`~object.__get__` method but not a :meth:`~object.__set__` | ||
method, but beyond that the set of attributes varies. A | ||
:attr:`~definition.__name__` attribute is usually | ||
has a :meth:`~object.__get__` method, but not a :meth:`~object.__set__` |
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.
Maybe this part of the text requires a .. versionadded:: 3.14
, but I am not sure on that
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 believe .. versionchanged:: .....
would be more approproate, as here we change the behavior of an existing entity, rather than add a new entity.
Also – here we come back to the question I asked at the beginning – shouldn't it be applied also to Python 3.13? (considering that Python 3.13 is still in the beta phase, and here we have a bugfix, rather than a feature, even if it seems too disruptive to be backported to the 3.12 and earlier Python versions...)
What do you think?
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.
Yes, versionchanged
would be the right docs notation.
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 note in the __set__
documentation (along with the interpreter's actual behaviour) makes it clear that this is a bugfix (i.e. adding __delete__
is enough to make a descriptor a data descriptor), so backporting to 3.13 makes sense to me.
I also agree that the risk/reward for backporting further isn't there, so 3.13 is as far back as the change should go.
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.
OK, added. :)
Lib/inspect.py
Outdated
if hasattr(tp, "__set__") or hasattr(tp, "__delete__"): | ||
return False | ||
return hasattr(tp, "__get__") |
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 would check __get__
first. It looks more natural.
if hasattr(tp, "__set__") or hasattr(tp, "__delete__"): | |
return False | |
return hasattr(tp, "__get__") | |
return hasattr(tp, "__get__") and not (hasattr(tp, "__set__") or hasattr(tp, "__delete__")) |
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.
OK, changed. :)
Doc/library/inspect.rst
Outdated
For objects with :meth:`~object.__get__` and :meth:`~object.__delete__` | ||
but without :meth:`~object.__set__`, ``False`` is now returned | ||
(previously, ``True`` was returned for such objects, which was | ||
incorrect). |
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.
For objects with :meth:`~object.__get__` and :meth:`~object.__delete__` | |
but without :meth:`~object.__set__`, ``False`` is now returned | |
(previously, ``True`` was returned for such objects, which was | |
incorrect). | |
Objects with :meth:`~object.__get__` and :meth:`~object.__delete__` | |
but without :meth:`~object.__set__` are no more considered as method | |
descriptors. In previous versions, they were incorrectly deemed as such. |
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.
@picnixz OK, I committed your suggestion. However, doesn't the fragment are no more considered as method descriptors sound too general? I mean, someone could misunderstand it – that before Python 3.13 such objects were generally considered as method descriptors; whereas our intent is to inform that just this particular function incorrectly considered them as such.
What do you think about such wording:
Objects with
__get__()
and__delete__()
but without__set__()
are no more considered by this function as method descriptors. In previous versions, they incorrectly were.
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.
(Or some other wording that would emphasize the focus on the behavior of this particular function...)
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 just committed as slightly different variant:
Objects with :meth:`~object.__get__` and :meth:`~object.__delete__`
but without :meth:`~object.__set__` are no more considered by this
function as method descriptors (in previous versions, they were
incorrectly deemed as such).
Could you please decide whether it is OK. :)
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've added a suggestion to tighten this up and make it clear it is describing a bug fix in this function rather than a change to the language itself.
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.
Thank you! :) I applied it.
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.
Sure thanks! my suggestions are in the end only... suggestions. I appreciate when someone likes it verbatim but I don't mind when it is reformulated!
Co-authored-by: Bénédikt Tran <[email protected]>
Co-authored-by: Alyssa Coghlan <[email protected]>
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.
LGTM.
Hypothesis test failure looked like an unrelated intermittent failure or false alarm (in |
The `inspect.ismethoddescriptor()` function did not check for the lack of `__delete__()` and, consequently, erroneously returned True when applied to *data* descriptors with only `__get__()` and `__delete__()` defined. (cherry picked from commit dacc5ac) Co-authored-by: Jan Kaliszewski <[email protected]> Co-authored-by: Bénédikt Tran <[email protected]> Co-authored-by: Alyssa Coghlan <[email protected]>
GH-120684 is a backport of this pull request to the 3.13 branch. |
The `inspect.ismethoddescriptor()` function did not check for the lack of `__delete__()` and, consequently, erroneously returned True when applied to *data* descriptors with only `__get__()` and `__delete__()` defined. Co-authored-by: Bénédikt Tran <[email protected]> Co-authored-by: Alyssa Coghlan <[email protected]>
The `inspect.ismethoddescriptor()` function did not check for the lack of `__delete__()` and, consequently, erroneously returned True when applied to *data* descriptors with only `__get__()` and `__delete__()` defined. Co-authored-by: Bénédikt Tran <[email protected]> Co-authored-by: Alyssa Coghlan <[email protected]>
The `inspect.ismethoddescriptor()` function did not check for the lack of `__delete__()` and, consequently, erroneously returned True when applied to *data* descriptors with only `__get__()` and `__delete__()` defined. Co-authored-by: Bénédikt Tran <[email protected]> Co-authored-by: Alyssa Coghlan <[email protected]>
The `inspect.ismethoddescriptor()` function did not check for the lack of `__delete__()` and, consequently, erroneously returned True when applied to *data* descriptors with only `__get__()` and `__delete__()` defined. Co-authored-by: Bénédikt Tran <[email protected]> Co-authored-by: Alyssa Coghlan <[email protected]>
The
inspect.ismethoddescriptor()
function did not check for the lack of__delete__()
and, consequently, erroneously returned True when applied to data descriptors that implemented just__get__()
and__delete__()
.Also, unit tests for
inspect.ismethoddescriptor()
(which were missing) have been added.📚 Documentation preview 📚: https://cpython-previews--120383.org.readthedocs.build/