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
0fceaf45
Commit
0fceaf45
authored
Apr 08, 2014
by
Yury Selivanov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
inspect.signautre: Fix functools.partial support. Issue #21117
parent
7ddf3eba
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
146 additions
and
93 deletions
+146
-93
Lib/inspect.py
Lib/inspect.py
+62
-73
Lib/test/test_inspect.py
Lib/test/test_inspect.py
+79
-20
Misc/NEWS
Misc/NEWS
+5
-0
No files found.
Lib/inspect.py
View file @
0fceaf45
...
...
@@ -1511,7 +1511,8 @@ def _signature_get_partial(wrapped_sig, partial, extra_args=()):
# look like after applying a 'functools.partial' object (or alike)
# on it.
new_params
=
OrderedDict
(
wrapped_sig
.
parameters
.
items
())
old_params
=
wrapped_sig
.
parameters
new_params
=
OrderedDict
(
old_params
.
items
())
partial_args
=
partial
.
args
or
()
partial_keywords
=
partial
.
keywords
or
{}
...
...
@@ -1525,32 +1526,57 @@ def _signature_get_partial(wrapped_sig, partial, extra_args=()):
msg
=
'partial object {!r} has incorrect arguments'
.
format
(
partial
)
raise
ValueError
(
msg
)
from
ex
for
arg_name
,
arg_value
in
ba
.
arguments
.
items
():
param
=
new_params
[
arg_name
]
if
arg_name
in
partial_keywords
:
# We set a new default value, because the following code
# is correct:
#
# >>> def foo(a): print(a)
# >>> print(partial(partial(foo, a=10), a=20)())
# 20
# >>> print(partial(partial(foo, a=10), a=20)(a=30))
# 30
#
# So, with 'partial' objects, passing a keyword argument is
# like setting a new default value for the corresponding
# parameter
transform_to_kwonly
=
False
for
param_name
,
param
in
old_params
.
items
():
try
:
arg_value
=
ba
.
arguments
[
param_name
]
except
KeyError
:
pass
else
:
if
param
.
kind
is
_POSITIONAL_ONLY
:
# If positional-only parameter is bound by partial,
# it effectively disappears from the signature
new_params
.
pop
(
param_name
)
continue
if
param
.
kind
is
_POSITIONAL_OR_KEYWORD
:
if
param_name
in
partial_keywords
:
# This means that this parameter, and all parameters
# after it should be keyword-only (and var-positional
# should be removed). Here's why. Consider the following
# function:
# foo(a, b, *args, c):
# pass
#
# We also mark this parameter with '_partial_kwarg'
# flag. Later, in '_bind', the 'default' value of this
# parameter will be added to 'kwargs', to simulate
# the 'functools.partial' real call.
new_params
[
arg_name
]
=
param
.
replace
(
default
=
arg_value
,
_partial_kwarg
=
True
)
# "partial(foo, a='spam')" will have the following
# signature: "(*, a='spam', b, c)". Because attempting
# to call that partial with "(10, 20)" arguments will
# raise a TypeError, saying that "a" argument received
# multiple values.
transform_to_kwonly
=
True
# Set the new default value
new_params
[
param_name
]
=
param
.
replace
(
default
=
arg_value
)
else
:
# was passed as a positional argument
new_params
.
pop
(
param
.
name
)
continue
elif
(
param
.
kind
not
in
(
_VAR_KEYWORD
,
_VAR_POSITIONAL
)
and
not
param
.
_partial_kwarg
):
new_params
.
pop
(
arg_name
)
if
param
.
kind
is
_KEYWORD_ONLY
:
# Set the new default value
new_params
[
param_name
]
=
param
.
replace
(
default
=
arg_value
)
if
transform_to_kwonly
:
assert
param
.
kind
is
not
_POSITIONAL_ONLY
if
param
.
kind
is
_POSITIONAL_OR_KEYWORD
:
new_param
=
new_params
[
param_name
].
replace
(
kind
=
_KEYWORD_ONLY
)
new_params
[
param_name
]
=
new_param
new_params
.
move_to_end
(
param_name
)
elif
param
.
kind
in
(
_KEYWORD_ONLY
,
_VAR_KEYWORD
):
new_params
.
move_to_end
(
param_name
)
elif
param
.
kind
is
_VAR_POSITIONAL
:
new_params
.
pop
(
param
.
name
)
return
wrapped_sig
.
replace
(
parameters
=
new_params
.
values
())
...
...
@@ -2069,7 +2095,7 @@ class Parameter:
`Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`.
'''
__slots__
=
(
'_name'
,
'_kind'
,
'_default'
,
'_annotation'
,
'_partial_kwarg'
)
__slots__
=
(
'_name'
,
'_kind'
,
'_default'
,
'_annotation'
)
POSITIONAL_ONLY
=
_POSITIONAL_ONLY
POSITIONAL_OR_KEYWORD
=
_POSITIONAL_OR_KEYWORD
...
...
@@ -2079,8 +2105,7 @@ class Parameter:
empty
=
_empty
def
__init__
(
self
,
name
,
kind
,
*
,
default
=
_empty
,
annotation
=
_empty
,
_partial_kwarg
=
False
):
def
__init__
(
self
,
name
,
kind
,
*
,
default
=
_empty
,
annotation
=
_empty
):
if
kind
not
in
(
_POSITIONAL_ONLY
,
_POSITIONAL_OR_KEYWORD
,
_VAR_POSITIONAL
,
_KEYWORD_ONLY
,
_VAR_KEYWORD
):
...
...
@@ -2105,8 +2130,6 @@ class Parameter:
self
.
_name
=
name
self
.
_partial_kwarg
=
_partial_kwarg
@
property
def
name
(
self
):
return
self
.
_name
...
...
@@ -2123,8 +2146,8 @@ class Parameter:
def
kind
(
self
):
return
self
.
_kind
def
replace
(
self
,
*
,
name
=
_void
,
kind
=
_void
,
annotation
=
_void
,
default
=
_void
,
_partial_kwarg
=
_void
):
def
replace
(
self
,
*
,
name
=
_void
,
kind
=
_void
,
annotation
=
_void
,
default
=
_void
):
'''Creates a customized copy of the Parameter.'''
if
name
is
_void
:
...
...
@@ -2139,11 +2162,7 @@ class Parameter:
if
default
is
_void
:
default
=
self
.
_default
if
_partial_kwarg
is
_void
:
_partial_kwarg
=
self
.
_partial_kwarg
return
type
(
self
)(
name
,
kind
,
default
=
default
,
annotation
=
annotation
,
_partial_kwarg
=
_partial_kwarg
)
return
type
(
self
)(
name
,
kind
,
default
=
default
,
annotation
=
annotation
)
def
__str__
(
self
):
kind
=
self
.
kind
...
...
@@ -2169,17 +2188,6 @@ class Parameter:
id
(
self
),
self
.
name
)
def
__eq__
(
self
,
other
):
# NB: We deliberately do not compare '_partial_kwarg' attributes
# here. Imagine we have a following situation:
#
# def foo(a, b=1): pass
# def bar(a, b): pass
# bar2 = functools.partial(bar, b=1)
#
# For the above scenario, signatures for `foo` and `bar2` should
# be equal. '_partial_kwarg' attribute is an internal flag, to
# distinguish between keyword parameters with defaults and
# keyword parameters which got their defaults from functools.partial
return
(
issubclass
(
other
.
__class__
,
Parameter
)
and
self
.
_name
==
other
.
_name
and
self
.
_kind
==
other
.
_kind
and
...
...
@@ -2219,12 +2227,7 @@ class BoundArguments:
def
args
(
self
):
args
=
[]
for
param_name
,
param
in
self
.
_signature
.
parameters
.
items
():
if
(
param
.
kind
in
(
_VAR_KEYWORD
,
_KEYWORD_ONLY
)
or
param
.
_partial_kwarg
):
# Keyword arguments mapped by 'functools.partial'
# (Parameter._partial_kwarg is True) are mapped
# in 'BoundArguments.kwargs', along with VAR_KEYWORD &
# KEYWORD_ONLY
if
param
.
kind
in
(
_VAR_KEYWORD
,
_KEYWORD_ONLY
):
break
try
:
...
...
@@ -2249,8 +2252,7 @@ class BoundArguments:
kwargs_started
=
False
for
param_name
,
param
in
self
.
_signature
.
parameters
.
items
():
if
not
kwargs_started
:
if
(
param
.
kind
in
(
_VAR_KEYWORD
,
_KEYWORD_ONLY
)
or
param
.
_partial_kwarg
):
if
param
.
kind
in
(
_VAR_KEYWORD
,
_KEYWORD_ONLY
):
kwargs_started
=
True
else
:
if
param_name
not
in
self
.
arguments
:
...
...
@@ -2332,18 +2334,14 @@ class Signature:
name
=
param
.
name
if
kind
<
top_kind
:
msg
=
'wrong parameter order: {
} before {
}'
msg
=
'wrong parameter order: {
!r} before {!r
}'
msg
=
msg
.
format
(
top_kind
,
kind
)
raise
ValueError
(
msg
)
elif
kind
>
top_kind
:
kind_defaults
=
False
top_kind
=
kind
if
(
kind
in
(
_POSITIONAL_ONLY
,
_POSITIONAL_OR_KEYWORD
)
and
not
param
.
_partial_kwarg
):
# If we have a positional-only or positional-or-keyword
# parameter, that does not have its default value set
# by 'functools.partial' or other "partial" signature:
if
kind
in
(
_POSITIONAL_ONLY
,
_POSITIONAL_OR_KEYWORD
):
if
param
.
default
is
_empty
:
if
kind_defaults
:
# No default for this parameter, but the
...
...
@@ -2518,15 +2516,6 @@ class Signature:
parameters_ex
=
()
arg_vals
=
iter
(
args
)
if
partial
:
# Support for binding arguments to 'functools.partial' objects.
# See 'functools.partial' case in 'signature()' implementation
# for details.
for
param_name
,
param
in
self
.
parameters
.
items
():
if
(
param
.
_partial_kwarg
and
param_name
not
in
kwargs
):
# Simulating 'functools.partial' behavior
kwargs
[
param_name
]
=
param
.
default
while
True
:
# Let's iterate through the positional arguments and corresponding
# parameters
...
...
Lib/test/test_inspect.py
View file @
0fceaf45
...
...
@@ -1959,6 +1959,8 @@ class TestSignatureObject(unittest.TestCase):
def
test_signature_on_partial
(
self
):
from
functools
import
partial
Parameter
=
inspect
.
Parameter
def
test
():
pass
...
...
@@ -1994,15 +1996,22 @@ class TestSignatureObject(unittest.TestCase):
self
.
assertEqual
(
self
.
signature
(
partial
(
test
,
b
=
1
,
c
=
2
)),
(((
'a'
,
...,
...,
"positional_or_keyword"
),
(
'b'
,
1
,
...,
"
positional_or_keyword
"
),
(
'b'
,
1
,
...,
"
keyword_only
"
),
(
'c'
,
2
,
...,
"keyword_only"
),
(
'd'
,
...,
...,
"keyword_only"
)),
...))
self
.
assertEqual
(
self
.
signature
(
partial
(
test
,
0
,
b
=
1
,
c
=
2
)),
(((
'b'
,
1
,
...,
"
positional_or_keyword
"
),
(((
'b'
,
1
,
...,
"
keyword_only
"
),
(
'c'
,
2
,
...,
"keyword_only"
),
(
'd'
,
...,
...,
"keyword_only"
),),
(
'd'
,
...,
...,
"keyword_only"
)),
...))
self
.
assertEqual
(
self
.
signature
(
partial
(
test
,
a
=
1
)),
(((
'a'
,
1
,
...,
"keyword_only"
),
(
'b'
,
...,
...,
"keyword_only"
),
(
'c'
,
...,
...,
"keyword_only"
),
(
'd'
,
...,
...,
"keyword_only"
)),
...))
def
test
(
a
,
*
args
,
b
,
**
kwargs
):
...
...
@@ -2014,13 +2023,18 @@ class TestSignatureObject(unittest.TestCase):
(
'kwargs'
,
...,
...,
"var_keyword"
)),
...))
self
.
assertEqual
(
self
.
signature
(
partial
(
test
,
a
=
1
)),
(((
'a'
,
1
,
...,
"keyword_only"
),
(
'b'
,
...,
...,
"keyword_only"
),
(
'kwargs'
,
...,
...,
"var_keyword"
)),
...))
self
.
assertEqual
(
self
.
signature
(
partial
(
test
,
1
,
2
,
3
)),
(((
'args'
,
...,
...,
"var_positional"
),
(
'b'
,
...,
...,
"keyword_only"
),
(
'kwargs'
,
...,
...,
"var_keyword"
)),
...))
self
.
assertEqual
(
self
.
signature
(
partial
(
test
,
1
,
2
,
3
,
test
=
True
)),
(((
'args'
,
...,
...,
"var_positional"
),
(
'b'
,
...,
...,
"keyword_only"
),
...
...
@@ -2067,7 +2081,7 @@ class TestSignatureObject(unittest.TestCase):
return
a
_foo
=
partial
(
partial
(
foo
,
a
=
10
),
a
=
20
)
self
.
assertEqual
(
self
.
signature
(
_foo
),
(((
'a'
,
20
,
...,
"
positional_or_keyword
"
),),
(((
'a'
,
20
,
...,
"
keyword_only
"
),),
...))
# check that we don't have any side-effects in signature(),
# and the partial object is still functioning
...
...
@@ -2076,42 +2090,87 @@ class TestSignatureObject(unittest.TestCase):
def
foo
(
a
,
b
,
c
):
return
a
,
b
,
c
_foo
=
partial
(
partial
(
foo
,
1
,
b
=
20
),
b
=
30
)
self
.
assertEqual
(
self
.
signature
(
_foo
),
(((
'b'
,
30
,
...,
"
positional_or_keyword
"
),
(
'c'
,
...,
...,
"
positional_or_keyword
"
)),
(((
'b'
,
30
,
...,
"
keyword_only
"
),
(
'c'
,
...,
...,
"
keyword_only
"
)),
...))
self
.
assertEqual
(
_foo
(
c
=
10
),
(
1
,
30
,
10
))
_foo
=
partial
(
_foo
,
2
)
# now 'b' has two values -
# positional and keyword
with
self
.
assertRaisesRegex
(
ValueError
,
"has incorrect arguments"
):
inspect
.
signature
(
_foo
)
def
foo
(
a
,
b
,
c
,
*
,
d
):
return
a
,
b
,
c
,
d
_foo
=
partial
(
partial
(
foo
,
d
=
20
,
c
=
20
),
b
=
10
,
d
=
30
)
self
.
assertEqual
(
self
.
signature
(
_foo
),
(((
'a'
,
...,
...,
"positional_or_keyword"
),
(
'b'
,
10
,
...,
"positional_or_keyword"
),
(
'c'
,
20
,
...,
"positional_or_keyword"
),
(
'd'
,
30
,
...,
"keyword_only"
)),
(
'b'
,
10
,
...,
"keyword_only"
),
(
'c'
,
20
,
...,
"keyword_only"
),
(
'd'
,
30
,
...,
"keyword_only"
),
),
...))
ba
=
inspect
.
signature
(
_foo
).
bind
(
a
=
200
,
b
=
11
)
self
.
assertEqual
(
_foo
(
*
ba
.
args
,
**
ba
.
kwargs
),
(
200
,
11
,
20
,
30
))
def
foo
(
a
=
1
,
b
=
2
,
c
=
3
):
return
a
,
b
,
c
_foo
=
partial
(
foo
,
a
=
10
,
c
=
13
)
ba
=
inspect
.
signature
(
_foo
).
bind
(
11
)
_foo
=
partial
(
foo
,
c
=
13
)
# (a=1, b=2, *, c=13)
ba
=
inspect
.
signature
(
_foo
).
bind
(
a
=
11
)
self
.
assertEqual
(
_foo
(
*
ba
.
args
,
**
ba
.
kwargs
),
(
11
,
2
,
13
))
ba
=
inspect
.
signature
(
_foo
).
bind
(
11
,
12
)
self
.
assertEqual
(
_foo
(
*
ba
.
args
,
**
ba
.
kwargs
),
(
11
,
12
,
13
))
ba
=
inspect
.
signature
(
_foo
).
bind
(
11
,
b
=
12
)
self
.
assertEqual
(
_foo
(
*
ba
.
args
,
**
ba
.
kwargs
),
(
11
,
12
,
13
))
ba
=
inspect
.
signature
(
_foo
).
bind
(
b
=
12
)
self
.
assertEqual
(
_foo
(
*
ba
.
args
,
**
ba
.
kwargs
),
(
10
,
12
,
13
))
_foo
=
partial
(
_foo
,
b
=
10
)
ba
=
inspect
.
signature
(
_foo
).
bind
(
12
,
14
)
self
.
assertEqual
(
_foo
(
*
ba
.
args
,
**
ba
.
kwargs
),
(
12
,
14
,
13
))
self
.
assertEqual
(
_foo
(
*
ba
.
args
,
**
ba
.
kwargs
),
(
1
,
12
,
13
))
_foo
=
partial
(
_foo
,
b
=
10
,
c
=
20
)
ba
=
inspect
.
signature
(
_foo
).
bind
(
12
)
self
.
assertEqual
(
_foo
(
*
ba
.
args
,
**
ba
.
kwargs
),
(
12
,
10
,
20
))
def
foo
(
a
,
b
,
c
,
d
,
**
kwargs
):
pass
sig
=
inspect
.
signature
(
foo
)
params
=
sig
.
parameters
.
copy
()
params
[
'a'
]
=
params
[
'a'
].
replace
(
kind
=
Parameter
.
POSITIONAL_ONLY
)
params
[
'b'
]
=
params
[
'b'
].
replace
(
kind
=
Parameter
.
POSITIONAL_ONLY
)
foo
.
__signature__
=
inspect
.
Signature
(
params
.
values
())
sig
=
inspect
.
signature
(
foo
)
self
.
assertEqual
(
str
(
sig
),
'(a, b, /, c, d, **kwargs)'
)
self
.
assertEqual
(
self
.
signature
(
partial
(
foo
,
1
)),
(((
'b'
,
...,
...,
'positional_only'
),
(
'c'
,
...,
...,
'positional_or_keyword'
),
(
'd'
,
...,
...,
'positional_or_keyword'
),
(
'kwargs'
,
...,
...,
'var_keyword'
)),
...))
self
.
assertEqual
(
self
.
signature
(
partial
(
foo
,
1
,
2
)),
(((
'c'
,
...,
...,
'positional_or_keyword'
),
(
'd'
,
...,
...,
'positional_or_keyword'
),
(
'kwargs'
,
...,
...,
'var_keyword'
)),
...))
self
.
assertEqual
(
self
.
signature
(
partial
(
foo
,
1
,
2
,
3
)),
(((
'd'
,
...,
...,
'positional_or_keyword'
),
(
'kwargs'
,
...,
...,
'var_keyword'
)),
...))
self
.
assertEqual
(
self
.
signature
(
partial
(
foo
,
1
,
2
,
c
=
3
)),
(((
'c'
,
3
,
...,
'keyword_only'
),
(
'd'
,
...,
...,
'keyword_only'
),
(
'kwargs'
,
...,
...,
'var_keyword'
)),
...))
self
.
assertEqual
(
self
.
signature
(
partial
(
foo
,
1
,
c
=
3
)),
(((
'b'
,
...,
...,
'positional_only'
),
(
'c'
,
3
,
...,
'keyword_only'
),
(
'd'
,
...,
...,
'keyword_only'
),
(
'kwargs'
,
...,
...,
'var_keyword'
)),
...))
def
test_signature_on_partialmethod
(
self
):
from
functools
import
partialmethod
...
...
Misc/NEWS
View file @
0fceaf45
...
...
@@ -106,6 +106,11 @@ Library
(Original patches by Hirokazu Yamamoto and Amaury Forgeot d'
Arc
,
with
suggested
wording
by
David
Gutteridge
)
-
Issue
#
21117
:
Fix
inspect
.
signature
to
better
support
functools
.
partial
.
Due
to
the
specifics
of
functools
.
partial
implementation
,
positional
-
or
-
keyword
arguments
passed
as
keyword
arguments
become
keyword
-
only
.
IDLE
----
...
...
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