Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
57d240ef
Commit
57d240ef
authored
Feb 19, 2014
by
Yury Selivanov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
inspect: Fix getfullargspec() to not to follow __wrapped__ chains
Initial patch by Nick Coghlan.
parent
4ac30f17
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
109 additions
and
46 deletions
+109
-46
Lib/inspect.py
Lib/inspect.py
+65
-46
Lib/test/test_inspect.py
Lib/test/test_inspect.py
+40
-0
Misc/NEWS
Misc/NEWS
+4
-0
No files found.
Lib/inspect.py
View file @
57d240ef
...
@@ -949,9 +949,9 @@ def getfullargspec(func):
...
@@ -949,9 +949,9 @@ def getfullargspec(func):
The first four items in the tuple correspond to getargspec().
The first four items in the tuple correspond to getargspec().
"""
"""
builtin_method_param
=
None
try
:
# Re: `skip_bound_arg=False`
if
ismethod
(
func
):
#
# There is a notable difference in behaviour between getfullargspec
# There is a notable difference in behaviour between getfullargspec
# and Signature: the former always returns 'self' parameter for bound
# and Signature: the former always returns 'self' parameter for bound
# methods, whereas the Signature always shows the actual calling
# methods, whereas the Signature always shows the actual calling
...
@@ -960,20 +960,15 @@ def getfullargspec(func):
...
@@ -960,20 +960,15 @@ def getfullargspec(func):
# To simulate this behaviour, we "unbind" bound methods, to trick
# To simulate this behaviour, we "unbind" bound methods, to trick
# inspect.signature to always return their first parameter ("self",
# inspect.signature to always return their first parameter ("self",
# usually)
# usually)
func
=
func
.
__func__
elif
isbuiltin
(
func
):
# Re: `follow_wrapper_chains=False`
# We have a builtin function or method. For that, we check the
#
# special '__text_signature__' attribute, provided by the
# getfullargspec() historically ignored __wrapped__ attributes,
# Argument Clinic. If it's a method, we'll need to make sure
# so we ensure that remains the case in 3.3+
# that its first parameter (usually "self") is always returned
# (see the previous comment).
text_signature
=
getattr
(
func
,
'__text_signature__'
,
None
)
if
text_signature
and
text_signature
.
startswith
(
'($'
):
builtin_method_param
=
_signature_get_bound_param
(
text_signature
)
try
:
sig
=
_signature_internal
(
func
,
sig
=
signature
(
func
)
follow_wrapper_chains
=
False
,
skip_bound_arg
=
False
)
except
Exception
as
ex
:
except
Exception
as
ex
:
# Most of the times 'signature' will raise ValueError.
# Most of the times 'signature' will raise ValueError.
# But, it can also raise AttributeError, and, maybe something
# But, it can also raise AttributeError, and, maybe something
...
@@ -1023,13 +1018,6 @@ def getfullargspec(func):
...
@@ -1023,13 +1018,6 @@ def getfullargspec(func):
# compatibility with 'func.__defaults__'
# compatibility with 'func.__defaults__'
defaults
=
None
defaults
=
None
if
builtin_method_param
and
(
not
args
or
args
[
0
]
!=
builtin_method_param
):
# `func` is a method, and we always need to return its
# first parameter -- usually "self" (to be backwards
# compatible with the previous implementation of
# getfullargspec)
args
.
insert
(
0
,
builtin_method_param
)
return
FullArgSpec
(
args
,
varargs
,
varkw
,
defaults
,
return
FullArgSpec
(
args
,
varargs
,
varkw
,
defaults
,
kwonlyargs
,
kwdefaults
,
annotations
)
kwonlyargs
,
kwdefaults
,
annotations
)
...
@@ -1719,7 +1707,7 @@ def _signature_strip_non_python_syntax(signature):
...
@@ -1719,7 +1707,7 @@ def _signature_strip_non_python_syntax(signature):
return
clean_signature
,
self_parameter
,
last_positional_only
return
clean_signature
,
self_parameter
,
last_positional_only
def
_signature_fromstr
(
cls
,
obj
,
s
):
def
_signature_fromstr
(
cls
,
obj
,
s
,
skip_bound_arg
=
True
):
# Internal helper to parse content of '__text_signature__'
# Internal helper to parse content of '__text_signature__'
# and return a Signature based on it
# and return a Signature based on it
Parameter
=
cls
.
_parameter_cls
Parameter
=
cls
.
_parameter_cls
...
@@ -1840,7 +1828,7 @@ def _signature_fromstr(cls, obj, s):
...
@@ -1840,7 +1828,7 @@ def _signature_fromstr(cls, obj, s):
if
self_parameter
is
not
None
:
if
self_parameter
is
not
None
:
assert
parameters
assert
parameters
if
getattr
(
obj
,
'__self__'
,
None
):
if
getattr
(
obj
,
'__self__'
,
None
)
and
skip_bound_arg
:
# strip off self, it's already been bound
# strip off self, it's already been bound
parameters
.
pop
(
0
)
parameters
.
pop
(
0
)
else
:
else
:
...
@@ -1851,8 +1839,21 @@ def _signature_fromstr(cls, obj, s):
...
@@ -1851,8 +1839,21 @@ def _signature_fromstr(cls, obj, s):
return
cls
(
parameters
,
return_annotation
=
cls
.
empty
)
return
cls
(
parameters
,
return_annotation
=
cls
.
empty
)
def
signature
(
obj
):
def
_signature_from_builtin
(
cls
,
func
,
skip_bound_arg
=
True
):
'''Get a signature object for the passed callable.'''
# Internal helper function to get signature for
# builtin callables
if
not
_signature_is_builtin
(
func
):
raise
TypeError
(
"{!r} is not a Python builtin "
"function"
.
format
(
func
))
s
=
getattr
(
func
,
"__text_signature__"
,
None
)
if
not
s
:
raise
ValueError
(
"no signature found for builtin {!r}"
.
format
(
func
))
return
_signature_fromstr
(
cls
,
func
,
s
,
skip_bound_arg
)
def
_signature_internal
(
obj
,
follow_wrapper_chains
=
True
,
skip_bound_arg
=
True
):
if
not
callable
(
obj
):
if
not
callable
(
obj
):
raise
TypeError
(
'{!r} is not a callable object'
.
format
(
obj
))
raise
TypeError
(
'{!r} is not a callable object'
.
format
(
obj
))
...
@@ -1860,10 +1861,16 @@ def signature(obj):
...
@@ -1860,10 +1861,16 @@ def signature(obj):
if
isinstance
(
obj
,
types
.
MethodType
):
if
isinstance
(
obj
,
types
.
MethodType
):
# In this case we skip the first parameter of the underlying
# In this case we skip the first parameter of the underlying
# function (usually `self` or `cls`).
# function (usually `self` or `cls`).
sig
=
signature
(
obj
.
__func__
)
sig
=
_signature_internal
(
obj
.
__func__
,
follow_wrapper_chains
,
skip_bound_arg
)
if
skip_bound_arg
:
return
_signature_bound_method
(
sig
)
return
_signature_bound_method
(
sig
)
else
:
return
sig
# Was this function wrapped by a decorator?
# Was this function wrapped by a decorator?
if
follow_wrapper_chains
:
obj
=
unwrap
(
obj
,
stop
=
(
lambda
f
:
hasattr
(
f
,
"__signature__"
)))
obj
=
unwrap
(
obj
,
stop
=
(
lambda
f
:
hasattr
(
f
,
"__signature__"
)))
try
:
try
:
...
@@ -1887,7 +1894,9 @@ def signature(obj):
...
@@ -1887,7 +1894,9 @@ def signature(obj):
# (usually `self`, or `cls`) will not be passed
# (usually `self`, or `cls`) will not be passed
# automatically (as for boundmethods)
# automatically (as for boundmethods)
wrapped_sig
=
signature
(
partialmethod
.
func
)
wrapped_sig
=
_signature_internal
(
partialmethod
.
func
,
follow_wrapper_chains
,
skip_bound_arg
)
sig
=
_signature_get_partial
(
wrapped_sig
,
partialmethod
,
(
None
,))
sig
=
_signature_get_partial
(
wrapped_sig
,
partialmethod
,
(
None
,))
first_wrapped_param
=
tuple
(
wrapped_sig
.
parameters
.
values
())[
0
]
first_wrapped_param
=
tuple
(
wrapped_sig
.
parameters
.
values
())[
0
]
...
@@ -1896,7 +1905,8 @@ def signature(obj):
...
@@ -1896,7 +1905,8 @@ def signature(obj):
return
sig
.
replace
(
parameters
=
new_params
)
return
sig
.
replace
(
parameters
=
new_params
)
if
_signature_is_builtin
(
obj
):
if
_signature_is_builtin
(
obj
):
return
Signature
.
from_builtin
(
obj
)
return
_signature_from_builtin
(
Signature
,
obj
,
skip_bound_arg
=
skip_bound_arg
)
if
isfunction
(
obj
)
or
_signature_is_functionlike
(
obj
):
if
isfunction
(
obj
)
or
_signature_is_functionlike
(
obj
):
# If it's a pure Python function, or an object that is duck type
# If it's a pure Python function, or an object that is duck type
...
@@ -1904,7 +1914,9 @@ def signature(obj):
...
@@ -1904,7 +1914,9 @@ def signature(obj):
return
Signature
.
from_function
(
obj
)
return
Signature
.
from_function
(
obj
)
if
isinstance
(
obj
,
functools
.
partial
):
if
isinstance
(
obj
,
functools
.
partial
):
wrapped_sig
=
signature
(
obj
.
func
)
wrapped_sig
=
_signature_internal
(
obj
.
func
,
follow_wrapper_chains
,
skip_bound_arg
)
return
_signature_get_partial
(
wrapped_sig
,
obj
)
return
_signature_get_partial
(
wrapped_sig
,
obj
)
sig
=
None
sig
=
None
...
@@ -1915,17 +1927,23 @@ def signature(obj):
...
@@ -1915,17 +1927,23 @@ def signature(obj):
# in its metaclass
# in its metaclass
call
=
_signature_get_user_defined_method
(
type
(
obj
),
'__call__'
)
call
=
_signature_get_user_defined_method
(
type
(
obj
),
'__call__'
)
if
call
is
not
None
:
if
call
is
not
None
:
sig
=
signature
(
call
)
sig
=
_signature_internal
(
call
,
follow_wrapper_chains
,
skip_bound_arg
)
else
:
else
:
# Now we check if the 'obj' class has a '__new__' method
# Now we check if the 'obj' class has a '__new__' method
new
=
_signature_get_user_defined_method
(
obj
,
'__new__'
)
new
=
_signature_get_user_defined_method
(
obj
,
'__new__'
)
if
new
is
not
None
:
if
new
is
not
None
:
sig
=
signature
(
new
)
sig
=
_signature_internal
(
new
,
follow_wrapper_chains
,
skip_bound_arg
)
else
:
else
:
# Finally, we should have at least __init__ implemented
# Finally, we should have at least __init__ implemented
init
=
_signature_get_user_defined_method
(
obj
,
'__init__'
)
init
=
_signature_get_user_defined_method
(
obj
,
'__init__'
)
if
init
is
not
None
:
if
init
is
not
None
:
sig
=
signature
(
init
)
sig
=
_signature_internal
(
init
,
follow_wrapper_chains
,
skip_bound_arg
)
if
sig
is
None
:
if
sig
is
None
:
# At this point we know, that `obj` is a class, with no user-
# At this point we know, that `obj` is a class, with no user-
...
@@ -1967,7 +1985,9 @@ def signature(obj):
...
@@ -1967,7 +1985,9 @@ def signature(obj):
call
=
_signature_get_user_defined_method
(
type
(
obj
),
'__call__'
)
call
=
_signature_get_user_defined_method
(
type
(
obj
),
'__call__'
)
if
call
is
not
None
:
if
call
is
not
None
:
try
:
try
:
sig
=
signature
(
call
)
sig
=
_signature_internal
(
call
,
follow_wrapper_chains
,
skip_bound_arg
)
except
ValueError
as
ex
:
except
ValueError
as
ex
:
msg
=
'no signature found for {!r}'
.
format
(
obj
)
msg
=
'no signature found for {!r}'
.
format
(
obj
)
raise
ValueError
(
msg
)
from
ex
raise
ValueError
(
msg
)
from
ex
...
@@ -1975,7 +1995,10 @@ def signature(obj):
...
@@ -1975,7 +1995,10 @@ def signature(obj):
if
sig
is
not
None
:
if
sig
is
not
None
:
# For classes and objects we skip the first parameter of their
# For classes and objects we skip the first parameter of their
# __call__, __new__, or __init__ methods
# __call__, __new__, or __init__ methods
if
skip_bound_arg
:
return
_signature_bound_method
(
sig
)
return
_signature_bound_method
(
sig
)
else
:
return
sig
if
isinstance
(
obj
,
types
.
BuiltinFunctionType
):
if
isinstance
(
obj
,
types
.
BuiltinFunctionType
):
# Raise a nicer error message for builtins
# Raise a nicer error message for builtins
...
@@ -1984,6 +2007,10 @@ def signature(obj):
...
@@ -1984,6 +2007,10 @@ def signature(obj):
raise
ValueError
(
'callable {!r} is not supported by signature'
.
format
(
obj
))
raise
ValueError
(
'callable {!r} is not supported by signature'
.
format
(
obj
))
def
signature
(
obj
):
'''Get a signature object for the passed callable.'''
return
_signature_internal
(
obj
)
class
_void
:
class
_void
:
'''A private marker - used in Parameter & Signature'''
'''A private marker - used in Parameter & Signature'''
...
@@ -2417,15 +2444,7 @@ class Signature:
...
@@ -2417,15 +2444,7 @@ class Signature:
@
classmethod
@
classmethod
def
from_builtin
(
cls
,
func
):
def
from_builtin
(
cls
,
func
):
if
not
_signature_is_builtin
(
func
):
return
_signature_from_builtin
(
cls
,
func
)
raise
TypeError
(
"{!r} is not a Python builtin "
"function"
.
format
(
func
))
s
=
getattr
(
func
,
"__text_signature__"
,
None
)
if
not
s
:
raise
ValueError
(
"no signature found for builtin {!r}"
.
format
(
func
))
return
_signature_fromstr
(
cls
,
func
,
s
)
@
property
@
property
def
parameters
(
self
):
def
parameters
(
self
):
...
...
Lib/test/test_inspect.py
View file @
57d240ef
...
@@ -577,6 +577,46 @@ class TestClassesAndFunctions(unittest.TestCase):
...
@@ -577,6 +577,46 @@ class TestClassesAndFunctions(unittest.TestCase):
kwonlyargs_e
=
[
'arg'
],
kwonlyargs_e
=
[
'arg'
],
formatted
=
'(*, arg)'
)
formatted
=
'(*, arg)'
)
def
test_argspec_api_ignores_wrapped
(
self
):
# Issue 20684: low level introspection API must ignore __wrapped__
@
functools
.
wraps
(
mod
.
spam
)
def
ham
(
x
,
y
):
pass
# Basic check
self
.
assertArgSpecEquals
(
ham
,
[
'x'
,
'y'
],
formatted
=
'(x, y)'
)
self
.
assertFullArgSpecEquals
(
ham
,
[
'x'
,
'y'
],
formatted
=
'(x, y)'
)
self
.
assertFullArgSpecEquals
(
functools
.
partial
(
ham
),
[
'x'
,
'y'
],
formatted
=
'(x, y)'
)
# Other variants
def
check_method
(
f
):
self
.
assertArgSpecEquals
(
f
,
[
'self'
,
'x'
,
'y'
],
formatted
=
'(self, x, y)'
)
class
C
:
@
functools
.
wraps
(
mod
.
spam
)
def
ham
(
self
,
x
,
y
):
pass
pham
=
functools
.
partialmethod
(
ham
)
@
functools
.
wraps
(
mod
.
spam
)
def
__call__
(
self
,
x
,
y
):
pass
check_method
(
C
())
check_method
(
C
.
ham
)
check_method
(
C
().
ham
)
check_method
(
C
.
pham
)
check_method
(
C
().
pham
)
class
C_new
:
@
functools
.
wraps
(
mod
.
spam
)
def
__new__
(
self
,
x
,
y
):
pass
check_method
(
C_new
)
class
C_init
:
@
functools
.
wraps
(
mod
.
spam
)
def
__init__
(
self
,
x
,
y
):
pass
check_method
(
C_init
)
def
test_getfullargspec_signature_attr
(
self
):
def
test_getfullargspec_signature_attr
(
self
):
def
test
():
def
test
():
pass
pass
...
...
Misc/NEWS
View file @
57d240ef
...
@@ -66,6 +66,10 @@ Library
...
@@ -66,6 +66,10 @@ Library
loop
.
set_exception_handler
(),
loop
.
default_exception_handler
(),
and
loop
.
set_exception_handler
(),
loop
.
default_exception_handler
(),
and
loop
.
call_exception_handler
().
loop
.
call_exception_handler
().
-
Issue
#
20684
:
Fix
inspect
.
getfullargspec
()
to
not
to
follow
__wrapped__
chains
.
Make
its
behaviour
consistent
with
bound
methods
first
argument
.
Patch
by
Nick
Coghlan
and
Yury
Selivanov
.
Tests
Tests
-----
-----
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment