Skip to content
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

Merged
merged 16 commits into from
Jun 18, 2024

Conversation

zuo
Copy link
Contributor

@zuo zuo commented Jun 12, 2024

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/

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__().
@zuo
Copy link
Contributor Author

zuo commented Jun 12, 2024

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Correct ``inspect.ismethoddescriptor`` to check also for the lack of
Correct :meth:`inspect.ismethoddescriptor` to check also for the lack of

Copy link
Contributor Author

@zuo zuo Jun 12, 2024

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).

@zuo zuo requested a review from eendebakpt June 12, 2024 10:01
# excludes them):
self.assertFalse(inspect.ismethoddescriptor(Owner().instance_method))
self.assertFalse(inspect.ismethoddescriptor(Owner().class_method))
self.assertFalse(inspect.ismethoddescriptor(Owner().static_method))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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))

Copy link
Contributor Author

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).

Copy link
Contributor Author

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. :)

Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, added :)

Copy link
Member

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)

Copy link
Contributor

@eendebakpt eendebakpt left a 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__`
Copy link
Contributor

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

Copy link
Contributor Author

@zuo zuo Jun 15, 2024

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?

Copy link
Contributor

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.

Copy link
Contributor

@ncoghlan ncoghlan Jun 15, 2024

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.

Copy link
Contributor Author

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
Comment on lines 317 to 319
if hasattr(tp, "__set__") or hasattr(tp, "__delete__"):
return False
return hasattr(tp, "__get__")
Copy link
Member

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.

Suggested change
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__"))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, changed. :)

@zuo zuo requested review from ncoghlan and serhiy-storchaka June 15, 2024 10:17
@zuo zuo requested a review from picnixz June 15, 2024 10:22
Comment on lines 519 to 522
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).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

Copy link
Contributor Author

@zuo zuo Jun 15, 2024

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.

Copy link
Contributor Author

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...)

Copy link
Contributor Author

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. :)

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Member

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]>
@zuo zuo requested a review from picnixz June 15, 2024 17:02
Doc/library/inspect.rst Outdated Show resolved Hide resolved
@ncoghlan ncoghlan added needs backport to 3.13 bugs and security fixes docs Documentation in the Doc dir labels Jun 16, 2024
@zuo zuo requested a review from ncoghlan June 16, 2024 23:47
Copy link
Member

@serhiy-storchaka serhiy-storchaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@ncoghlan
Copy link
Contributor

ncoghlan commented Jun 18, 2024

Hypothesis test failure looked like an unrelated intermittent failure or false alarm (in test_glob), so I merged in main to trigger a CI rerun.

@ncoghlan ncoghlan enabled auto-merge (squash) June 18, 2024 11:59
@ncoghlan ncoghlan merged commit dacc5ac into python:main Jun 18, 2024
33 checks passed
@miss-islington-app
Copy link

Thanks @zuo for the PR, and @ncoghlan for merging it 🌮🎉.. I'm working now to backport this PR to: 3.13.
🐍🍒⛏🤖

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Jun 18, 2024
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]>
@bedevere-app
Copy link

bedevere-app bot commented Jun 18, 2024

GH-120684 is a backport of this pull request to the 3.13 branch.

@bedevere-app bedevere-app bot removed the needs backport to 3.13 bugs and security fixes label Jun 18, 2024
picnixz added a commit to picnixz/cpython that referenced this pull request Jun 19, 2024
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]>
mrahtz pushed a commit to mrahtz/cpython that referenced this pull request Jun 30, 2024
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]>
noahbkim pushed a commit to hudson-trading/cpython that referenced this pull request Jul 11, 2024
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]>
estyxx pushed a commit to estyxx/cpython that referenced this pull request Jul 17, 2024
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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Documentation in the Doc dir
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants