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

inspect.unwrap() does not work with types with the __wrapped__ data descriptor #112006

Closed
serhiy-storchaka opened this issue Nov 12, 2023 · 1 comment
Labels
3.11 only security fixes 3.12 bugs and security fixes 3.13 bugs and security fixes stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error

Comments

@serhiy-storchaka
Copy link
Member

serhiy-storchaka commented Nov 12, 2023

Bug report

inspect.unwrap() follows the chain by links __wrapped__ and returns the last item in a chain or the original object if it does not have a __wrapped__ attribute (there is also additional stop predicate and protection against loops, but it is unrelated). It works well in most cases, except with a type that has the __wrapped__ data descriptor.

For example the following code

class W:
    def __init__(self, x):
        self._wrapped = x
    @property
    def __wrapped__(self):
        return self._wrapped

import inspect
print(inspect.unwrap(W(chr)))
print(inspect.unwrap(W))

prints

<built-in function chr>
<property object at 0x7f334092dc50>

The former output is correct, W(chr) wraps chr. But the latter is wrong: the W type does not wrap a property object.

It is not hypothetical issue. staticmethod and classmethod have now (bpo-43682/#87848) the __wrapped__ attribute. inspect.signature() uses inspect.unwrap(), and it cannot support staticmethod and classmethod even if they get correct __text_signature__. inspect.getsourcelines() also uses inspect.unwrap() indirectly and can fail with Python classes with the __wrapped__ attribute.

inspect.unwrap() should stop before such attribute. But how to detect such case? There are several ways:

  • Stop if func is a class. pickle does it for its special methods, this is why classes are handled separately from instances. But it means that functools.wraps(), staticmethod and classmethod cannot be used to decorate classes. Although if they are currently used, the result can be weird, because instances will have the same __wrapped__ attribute as a class. I do not know how often wrapped classes are used in the real code, but there is a test for this. It may be the right way at the end, although it can break some questionable code.
  • Stop if func.__wrapped__ is a data descriptor. I afraid that it will affect multidecorated properties.
  • Stop if func.__wrapped__ is not callable. Do not know what can be consequences.

Maybe there are other ways?

Linked PRs

@serhiy-storchaka serhiy-storchaka added type-bug An unexpected behavior, bug, or error stdlib Python modules in the Lib dir 3.11 only security fixes 3.12 bugs and security fixes 3.13 bugs and security fixes labels Nov 12, 2023
serhiy-storchaka added a commit to serhiy-storchaka/cpython that referenced this issue Feb 15, 2024
…data descriptor

This also fixes inspect.Signature.from_callable() for builtins classmethod()
and staticmethod().
@AlexWaygood AlexWaygood changed the title inspect.unwrap() does not work with types with the __wrapper__ data descriptor inspect.unwrap() does not work with types with the __wrapped__ data descriptor Feb 15, 2024
@serhiy-storchaka
Copy link
Member Author

After more consideration I think that option 1 is the right one. A class should never be a wrapper with the __wrapper__ link, because this makes all instances wrappers of the same wrapped object. Even if this trick is used to make all instances the wrappers of the same wrapped object, the class is different from its instances.

serhiy-storchaka added a commit that referenced this issue Feb 26, 2024
… descriptor (GH-115540)

This also fixes inspect.Signature.from_callable() for builtins classmethod()
and staticmethod().
miss-islington pushed a commit to miss-islington/cpython that referenced this issue Feb 26, 2024
…a data descriptor (pythonGH-115540)

This also fixes inspect.Signature.from_callable() for builtins classmethod()
and staticmethod().
(cherry picked from commit 68c79d2)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
miss-islington pushed a commit to miss-islington/cpython that referenced this issue Feb 26, 2024
…a data descriptor (pythonGH-115540)

This also fixes inspect.Signature.from_callable() for builtins classmethod()
and staticmethod().
(cherry picked from commit 68c79d2)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
serhiy-storchaka added a commit that referenced this issue Feb 27, 2024
… a data descriptor (GH-115540) (GH-115966)

(cherry picked from commit 68c79d2)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
serhiy-storchaka added a commit that referenced this issue Feb 27, 2024
… a data descriptor (GH-115540) (GH-115965)

(cherry picked from commit 68c79d2)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
woodruffw pushed a commit to woodruffw-forks/cpython that referenced this issue Mar 4, 2024
…a data descriptor (pythonGH-115540)

This also fixes inspect.Signature.from_callable() for builtins classmethod()
and staticmethod().
adorilson pushed a commit to adorilson/cpython that referenced this issue Mar 25, 2024
…a data descriptor (pythonGH-115540)

This also fixes inspect.Signature.from_callable() for builtins classmethod()
and staticmethod().
diegorusso pushed a commit to diegorusso/cpython that referenced this issue Apr 17, 2024
…a data descriptor (pythonGH-115540)

This also fixes inspect.Signature.from_callable() for builtins classmethod()
and staticmethod().
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.11 only security fixes 3.12 bugs and security fixes 3.13 bugs and security fixes stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

1 participant