Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit766c5f7

Browse files
[3.13]gh-119605: Respectfollow_wrapped for__init__ and__new__ when getting class signature withinspect.signature (GH-132055) (#133277)
gh-119605: Respect `follow_wrapped` for `__init__` and `__new__` when getting class signature with `inspect.signature` (GH-132055)(cherry picked from commitb8633f9)Co-authored-by: Xuehai Pan <XuehaiPan@pku.edu.cn>
1 parentf7d1109 commit766c5f7

File tree

4 files changed

+136
-8
lines changed

4 files changed

+136
-8
lines changed

‎Lib/inspect.py

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2000,7 +2000,7 @@ def getasyncgenlocals(agen):
20002000
types.BuiltinFunctionType)
20012001

20022002

2003-
def_signature_get_user_defined_method(cls,method_name):
2003+
def_signature_get_user_defined_method(cls,method_name,*,follow_wrapper_chains=True):
20042004
"""Private helper. Checks if ``cls`` has an attribute
20052005
named ``method_name`` and returns it only if it is a
20062006
pure python function.
@@ -2009,12 +2009,20 @@ def _signature_get_user_defined_method(cls, method_name):
20092009
meth=getattr(cls,method_name,None)
20102010
else:
20112011
meth=getattr_static(cls,method_name,None)
2012-
ifmethisNoneorisinstance(meth,_NonUserDefinedCallables):
2012+
ifmethisNone:
2013+
returnNone
2014+
2015+
iffollow_wrapper_chains:
2016+
meth=unwrap(meth,stop=(lambdam:hasattr(m,"__signature__")
2017+
or_signature_is_builtin(m)))
2018+
ifisinstance(meth,_NonUserDefinedCallables):
20132019
# Once '__signature__' will be added to 'C'-level
20142020
# callables, this check won't be necessary
20152021
returnNone
20162022
ifmethod_name!='__new__':
20172023
meth=_descriptor_get(meth,cls)
2024+
iffollow_wrapper_chains:
2025+
meth=unwrap(meth,stop=lambdam:hasattr(m,"__signature__"))
20182026
returnmeth
20192027

20202028

@@ -2589,12 +2597,26 @@ def _signature_from_callable(obj, *,
25892597

25902598
# First, let's see if it has an overloaded __call__ defined
25912599
# in its metaclass
2592-
call=_signature_get_user_defined_method(type(obj),'__call__')
2600+
call=_signature_get_user_defined_method(
2601+
type(obj),
2602+
'__call__',
2603+
follow_wrapper_chains=follow_wrapper_chains,
2604+
)
25932605
ifcallisnotNone:
25942606
return_get_signature_of(call)
25952607

2596-
new=_signature_get_user_defined_method(obj,'__new__')
2597-
init=_signature_get_user_defined_method(obj,'__init__')
2608+
# NOTE: The user-defined method can be a function with a thin wrapper
2609+
# around object.__new__ (e.g., generated by `@warnings.deprecated`)
2610+
new=_signature_get_user_defined_method(
2611+
obj,
2612+
'__new__',
2613+
follow_wrapper_chains=follow_wrapper_chains,
2614+
)
2615+
init=_signature_get_user_defined_method(
2616+
obj,
2617+
'__init__',
2618+
follow_wrapper_chains=follow_wrapper_chains,
2619+
)
25982620

25992621
# Go through the MRO and see if any class has user-defined
26002622
# pure Python __new__ or __init__ method
@@ -2634,10 +2656,14 @@ def _signature_from_callable(obj, *,
26342656
# Last option is to check if its '__init__' is
26352657
# object.__init__ or type.__init__.
26362658
iftypenotinobj.__mro__:
2659+
obj_init=obj.__init__
2660+
obj_new=obj.__new__
2661+
iffollow_wrapper_chains:
2662+
obj_init=unwrap(obj_init)
2663+
obj_new=unwrap(obj_new)
26372664
# We have a class (not metaclass), but no user-defined
26382665
# __init__ or __new__ for it
2639-
if (obj.__init__isobject.__init__and
2640-
obj.__new__isobject.__new__):
2666+
ifobj_initisobject.__init__andobj_newisobject.__new__:
26412667
# Return a signature of 'object' builtin.
26422668
returnsigcls.from_callable(object)
26432669
else:

‎Lib/test/test_inspect/test_inspect.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3967,7 +3967,6 @@ def wrapped_foo_call():
39673967
('b', ..., ...,"positional_or_keyword")),
39683968
...))
39693969

3970-
39713970
deftest_signature_on_class(self):
39723971
classC:
39733972
def__init__(self,a):
@@ -4144,6 +4143,45 @@ def __init__(self, b):
41444143
('bar',2, ...,"keyword_only")),
41454144
...))
41464145

4146+
deftest_signature_on_class_with_decorated_new(self):
4147+
defidentity(func):
4148+
@functools.wraps(func)
4149+
defwrapped(*args,**kwargs):
4150+
returnfunc(*args,**kwargs)
4151+
returnwrapped
4152+
4153+
classFoo:
4154+
@identity
4155+
def__new__(cls,a,b):
4156+
pass
4157+
4158+
self.assertEqual(self.signature(Foo),
4159+
((('a', ..., ...,"positional_or_keyword"),
4160+
('b', ..., ...,"positional_or_keyword")),
4161+
...))
4162+
4163+
self.assertEqual(self.signature(Foo.__new__),
4164+
((('cls', ..., ...,"positional_or_keyword"),
4165+
('a', ..., ...,"positional_or_keyword"),
4166+
('b', ..., ...,"positional_or_keyword")),
4167+
...))
4168+
4169+
classBar:
4170+
__new__=identity(object.__new__)
4171+
4172+
varargs_signature= (
4173+
(('args', ..., ...,'var_positional'),
4174+
('kwargs', ..., ...,'var_keyword')),
4175+
...,
4176+
)
4177+
4178+
self.assertEqual(self.signature(Bar), ((), ...))
4179+
self.assertEqual(self.signature(Bar.__new__),varargs_signature)
4180+
self.assertEqual(self.signature(Bar,follow_wrapped=False),
4181+
varargs_signature)
4182+
self.assertEqual(self.signature(Bar.__new__,follow_wrapped=False),
4183+
varargs_signature)
4184+
41474185
deftest_signature_on_class_with_init(self):
41484186
classC:
41494187
def__init__(self,b):

‎Lib/test/test_warnings/__init__.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1826,10 +1826,70 @@ async def coro(self):
18261826
self.assertFalse(inspect.iscoroutinefunction(Cls.sync))
18271827
self.assertTrue(inspect.iscoroutinefunction(Cls.coro))
18281828

1829+
deftest_inspect_class_signature(self):
1830+
classCls1:# no __init__ or __new__
1831+
pass
1832+
1833+
classCls2:# __new__ only
1834+
def__new__(cls,x,y):
1835+
returnsuper().__new__(cls)
1836+
1837+
classCls3:# __init__ only
1838+
def__init__(self,x,y):
1839+
pass
1840+
1841+
classCls4:# __new__ and __init__
1842+
def__new__(cls,x,y):
1843+
returnsuper().__new__(cls)
1844+
1845+
def__init__(self,x,y):
1846+
pass
1847+
1848+
classCls5(Cls1):# inherits no __init__ or __new__
1849+
pass
1850+
1851+
classCls6(Cls2):# inherits __new__ only
1852+
pass
1853+
1854+
classCls7(Cls3):# inherits __init__ only
1855+
pass
1856+
1857+
classCls8(Cls4):# inherits __new__ and __init__
1858+
pass
1859+
1860+
# The `@deprecated` decorator will update the class in-place.
1861+
# Test the child classes first.
1862+
forclsinreversed((Cls1,Cls2,Cls3,Cls4,Cls5,Cls6,Cls7,Cls8)):
1863+
withself.subTest(f'class{cls.__name__} signature'):
1864+
try:
1865+
original_signature=inspect.signature(cls)
1866+
exceptValueError:
1867+
original_signature=None
1868+
try:
1869+
original_new_signature=inspect.signature(cls.__new__)
1870+
exceptValueError:
1871+
original_new_signature=None
1872+
1873+
deprecated_cls=deprecated("depr")(cls)
1874+
1875+
try:
1876+
deprecated_signature=inspect.signature(deprecated_cls)
1877+
exceptValueError:
1878+
deprecated_signature=None
1879+
self.assertEqual(original_signature,deprecated_signature)
1880+
1881+
try:
1882+
deprecated_new_signature=inspect.signature(deprecated_cls.__new__)
1883+
exceptValueError:
1884+
deprecated_new_signature=None
1885+
self.assertEqual(original_new_signature,deprecated_new_signature)
1886+
1887+
18291888
defsetUpModule():
18301889
py_warnings.onceregistry.clear()
18311890
c_warnings.onceregistry.clear()
18321891

1892+
18331893
tearDownModule=setUpModule
18341894

18351895
if__name__=="__main__":
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Respect ``follow_wrapped`` for:meth:`!__init__` and:meth:`!__new__` methods
2+
when getting the class signature for a class with:func:`inspect.signature`.
3+
Preserve class signature after wrapping with:func:`warnings.deprecated`.
4+
Patch by Xuehai Pan.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp