Skip to content

Commit f36ba12

Browse files
bpo-32697: Definition order of kwonly params is now guaranteed preserved. (#5391)
Definition order of kwonly params is now guaranteed preserved.
1 parent bec2372 commit f36ba12

File tree

3 files changed

+63
-2
lines changed

3 files changed

+63
-2
lines changed

Doc/library/inspect.rst

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,13 @@ function.
602602
.. attribute:: Signature.parameters
603603

604604
An ordered mapping of parameters' names to the corresponding
605-
:class:`Parameter` objects.
605+
:class:`Parameter` objects. Parameters appear in strict definition
606+
order, including keyword-only parameters.
607+
608+
.. versionchanged:: 3.7
609+
Python only explicitly guaranteed that it preserved the declaration
610+
order of keyword-only parameters as of version 3.7, although in practice
611+
this order had always been preserved in Python 3.
606612

607613
.. attribute:: Signature.return_annotation
608614

@@ -895,7 +901,7 @@ Classes and functions
895901
*defaults* is an *n*-tuple of default argument values corresponding to the
896902
last *n* positional parameters, or ``None`` if there are no such defaults
897903
defined.
898-
*kwonlyargs* is a list of keyword-only parameter names.
904+
*kwonlyargs* is a list of keyword-only parameter names in declaration order.
899905
*kwonlydefaults* is a dictionary mapping parameter names from *kwonlyargs*
900906
to the default values used if no argument is supplied.
901907
*annotations* is a dictionary mapping parameter names to annotations.
@@ -921,6 +927,11 @@ Classes and functions
921927
single-source Python 2/3 code migrating away from the legacy
922928
:func:`getargspec` API.
923929

930+
.. versionchanged:: 3.7
931+
Python only explicitly guaranteed that it preserved the declaration
932+
order of keyword-only parameters as of version 3.7, although in practice
933+
this order had always been preserved in Python 3.
934+
924935

925936
.. function:: getargvalues(frame)
926937

Lib/test/test_inspect.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,31 @@ def revise(filename, *args):
5757

5858
git = mod.StupidGit()
5959

60+
61+
def signatures_with_lexicographic_keyword_only_parameters():
62+
"""
63+
Yields a whole bunch of functions with only keyword-only parameters,
64+
where those parameters are always in lexicographically sorted order.
65+
"""
66+
parameters = ['a', 'bar', 'c', 'delta', 'ephraim', 'magical', 'yoyo', 'z']
67+
for i in range(1, 2**len(parameters)):
68+
p = []
69+
bit = 1
70+
for j in range(len(parameters)):
71+
if i & (bit << j):
72+
p.append(parameters[j])
73+
fn_text = "def foo(*, " + ", ".join(p) + "): pass"
74+
symbols = {}
75+
exec(fn_text, symbols, symbols)
76+
yield symbols['foo']
77+
78+
79+
def unsorted_keyword_only_parameters_fn(*, throw, out, the, baby, with_,
80+
the_, bathwater):
81+
pass
82+
83+
unsorted_keyword_only_parameters = 'throw out the baby with_ the_ bathwater'.split()
84+
6085
class IsTestBase(unittest.TestCase):
6186
predicates = set([inspect.isbuiltin, inspect.isclass, inspect.iscode,
6287
inspect.isframe, inspect.isfunction, inspect.ismethod,
@@ -829,6 +854,17 @@ def test_getfullagrspec_builtin_func_no_signature(self):
829854
with self.assertRaises(TypeError):
830855
inspect.getfullargspec(builtin)
831856

857+
def test_getfullargspec_definition_order_preserved_on_kwonly(self):
858+
for fn in signatures_with_lexicographic_keyword_only_parameters():
859+
signature = inspect.getfullargspec(fn)
860+
l = list(signature.kwonlyargs)
861+
sorted_l = sorted(l)
862+
self.assertTrue(l)
863+
self.assertEqual(l, sorted_l)
864+
signature = inspect.getfullargspec(unsorted_keyword_only_parameters_fn)
865+
l = list(signature.kwonlyargs)
866+
self.assertEqual(l, unsorted_keyword_only_parameters)
867+
832868
def test_getargspec_method(self):
833869
class A(object):
834870
def m(self):
@@ -2969,6 +3005,17 @@ class MySignature(inspect.Signature): pass
29693005
sig = MySignature.from_callable(_pickle.Pickler)
29703006
self.assertTrue(isinstance(sig, MySignature))
29713007

3008+
def test_signature_definition_order_preserved_on_kwonly(self):
3009+
for fn in signatures_with_lexicographic_keyword_only_parameters():
3010+
signature = inspect.signature(fn)
3011+
l = list(signature.parameters)
3012+
sorted_l = sorted(l)
3013+
self.assertTrue(l)
3014+
self.assertEqual(l, sorted_l)
3015+
signature = inspect.signature(unsorted_keyword_only_parameters_fn)
3016+
l = list(signature.parameters)
3017+
self.assertEqual(l, unsorted_keyword_only_parameters)
3018+
29723019

29733020
class TestParameterObject(unittest.TestCase):
29743021
def test_signature_parameter_kinds(self):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Python now explicitly preserves the definition order of keyword-only
2+
parameters. It's always preserved their order, but this behavior was never
3+
guaranteed before; this behavior is now guaranteed and tested.

0 commit comments

Comments
 (0)