-
-
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
Inheriting from class that defines __new__ causes inspect.signature to always return (*args, **kwargs) for constructor #85074
Comments
Consider the following program:
I expect inspect.signature to return () as the signature of the constructor of this function. However, I get this:
Although it is true that one cannot generally rely on inspect.signature to always give the most accurate signature (because there may always be decorator or metaclass shenanigans getting in the way), in this particular case it seems especially undesirable because Python type annotations are supposed to be erased at runtime, and yet here inheriting from Generic (simply to add type annotations) causes a very clear change in runtime behavior. |
It is not special for Generic, but happens with every type implementing __new__. class A:
def __new__(cls, a=1, *args, **kwargs):
return object.__new__(cls)
class B(A):
def __init__(self, b):
pass
import inspect
print(inspect.signature(B)) The above example prints "(a=1, *args, **kwargs)" instead of "(b)". |
So does that make this "not a bug"? Or is there something to document? For technical reasons we can't just add a __init__ method to Generic, and I doubt that it's feasible to change inspect.signature(). |
I think that inspect.signature() could be made more smart. It should take into account signatures of both __new__ and __init__ and return the most specific compatible signature. |
Changing the topic to not point fingers at Generic. |
The following patch to inspect.py solves the issue that inspect.signature() returns the wrong signature on classes that inherit from Generic. Not 100% sure though if this implementation is the cleanest way possible. I've been looking into attaching a __wrapped__ to Generic as well, without success. I'm not very familiar with the inspect code. To me, this fix is pretty important. ptpython, a Python REPL, has the ability to show the function signature of what the user is currently typing, and with codebases that have lots of generics, there's nothing really useful we can show. $ diff inspect.old.py inspect.py -p
*** inspect.old.py 2021-02-17 11:35:50.787234264 +0100
--- inspect.py 2021-02-17 11:35:10.131407202 +0100
*************** import sys
*** 44,49 ****
--- 44,50 import tokenize
import token
import types
+ import typing
import warnings
import functools
import builtins
*************** def _signature_get_user_defined_method(c
*** 1715,1720 ****
--- 1716,1725 ----
except AttributeError:
return
else:
+ if meth in (typing.Generic.__new__, typing.Protocol.__new__):
+ # Exclude methods from the typing module.
+ return
+
if not isinstance(meth, _NonUserDefinedCallables):
# Once '__signature__' will be added to 'C'-level
# callables, this check won't be necessary For those interested, the following monkey-patch has the same effect: def monkey_patch_typing() -> None:
import inspect, typing
def _signature_get_user_defined_method(cls, method_name):
try:
meth = getattr(cls, method_name)
except AttributeError:
return
else:
if meth in (typing.Generic.__new__, typing.Protocol.__new__):
# Exclude methods from the typing module.
return
if not isinstance(meth, inspect._NonUserDefinedCallables):
# Once '__signature__' will be added to 'C'-level
# callables, this check won't be necessary
return meth
inspect._signature_get_user_defined_method = _signature_get_user_defined_method
monkey_patch_typing() |
I doubt that solution is correct, given that we already established that the problem is *not* specific to Generic. |
I think this is affecting me or it is a new similar issue. And it is not only for python 3.9, but also from 3.6 up. I am working on making code configurable based on signatures (see https://jsonargparse.readthedocs.io/en/stable/#classes-methods-and-functions). Now we need this to work for datetime.timedelta which defines parameters in __new__ instead of __init__. The following happens: >>> from datetime import timedelta
>>> import inspect
>>> inspect.signature(timedelta.__new__)
<Signature (*args, **kwargs)>
>>> inspect.signature(timedelta.__init__)
<Signature (self, /, *args, **kwargs)>
>>> inspect.signature(timedelta)
...
ValueError: no signature found for builtin type <class 'datetime.timedelta'> I am expecting to get parameters for days, seconds, microseconds, milliseconds, minutes, hours and weeks, see Lines 461 to 462 in bfe544d
Hopefully this gives some insight into what should be done. Independent from the fix, I would like to know if currently there is any way I can get the signature for __new__ that I could use until there is a proper fix for it. |
I created another issue since the problem appears to be a bit different: https://bugs.python.org/issue44618 |
>>> from datetime import timedelta as a
>>> from _datetime import timedelta as b
>>> a is b
True
>>>
But |
We won't be backporting this fix to 3.9 due to larger changes between versions. |
On second thought it's a bummer not to fix this in 3.9.x that will still be the only stable version until October. I'll refactor the relevant part of inspect.py in 3.9 to make the backport applicable. |
Thanks! ✨ 🍰 ✨ |
inspect.signature
#23336inspect.signature
#27177inspect.signature
(GH-27177) #27189inspect.signature
(GH-27177) #27209Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: