diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 147e802cac4..bc4316fabae 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -602,7 +602,13 @@ function. .. attribute:: Signature.parameters An ordered mapping of parameters' names to the corresponding - :class:`Parameter` objects. + :class:`Parameter` objects. Parameters appear in strict definition + order, including keyword-only parameters. + + .. versionchanged:: 3.7 + Python only explicitly guaranteed that it preserved the declaration + order of keyword-only parameters as of version 3.7, although in practice + this order had always been preserved in Python 3. .. attribute:: Signature.return_annotation @@ -895,7 +901,7 @@ Classes and functions *defaults* is an *n*-tuple of default argument values corresponding to the last *n* positional parameters, or ``None`` if there are no such defaults defined. - *kwonlyargs* is a list of keyword-only parameter names. + *kwonlyargs* is a list of keyword-only parameter names in declaration order. *kwonlydefaults* is a dictionary mapping parameter names from *kwonlyargs* to the default values used if no argument is supplied. *annotations* is a dictionary mapping parameter names to annotations. @@ -921,6 +927,11 @@ Classes and functions single-source Python 2/3 code migrating away from the legacy :func:`getargspec` API. + .. versionchanged:: 3.7 + Python only explicitly guaranteed that it preserved the declaration + order of keyword-only parameters as of version 3.7, although in practice + this order had always been preserved in Python 3. + .. function:: getargvalues(frame) diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index cb51f8aff29..1a856f6387e 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -57,6 +57,31 @@ def revise(filename, *args): git = mod.StupidGit() + +def signatures_with_lexicographic_keyword_only_parameters(): + """ + Yields a whole bunch of functions with only keyword-only parameters, + where those parameters are always in lexicographically sorted order. + """ + parameters = ['a', 'bar', 'c', 'delta', 'ephraim', 'magical', 'yoyo', 'z'] + for i in range(1, 2**len(parameters)): + p = [] + bit = 1 + for j in range(len(parameters)): + if i & (bit << j): + p.append(parameters[j]) + fn_text = "def foo(*, " + ", ".join(p) + "): pass" + symbols = {} + exec(fn_text, symbols, symbols) + yield symbols['foo'] + + +def unsorted_keyword_only_parameters_fn(*, throw, out, the, baby, with_, + the_, bathwater): + pass + +unsorted_keyword_only_parameters = 'throw out the baby with_ the_ bathwater'.split() + class IsTestBase(unittest.TestCase): predicates = set([inspect.isbuiltin, inspect.isclass, inspect.iscode, inspect.isframe, inspect.isfunction, inspect.ismethod, @@ -829,6 +854,17 @@ class TestClassesAndFunctions(unittest.TestCase): with self.assertRaises(TypeError): inspect.getfullargspec(builtin) + def test_getfullargspec_definition_order_preserved_on_kwonly(self): + for fn in signatures_with_lexicographic_keyword_only_parameters(): + signature = inspect.getfullargspec(fn) + l = list(signature.kwonlyargs) + sorted_l = sorted(l) + self.assertTrue(l) + self.assertEqual(l, sorted_l) + signature = inspect.getfullargspec(unsorted_keyword_only_parameters_fn) + l = list(signature.kwonlyargs) + self.assertEqual(l, unsorted_keyword_only_parameters) + def test_getargspec_method(self): class A(object): def m(self): @@ -2969,6 +3005,17 @@ class TestSignatureObject(unittest.TestCase): sig = MySignature.from_callable(_pickle.Pickler) self.assertTrue(isinstance(sig, MySignature)) + def test_signature_definition_order_preserved_on_kwonly(self): + for fn in signatures_with_lexicographic_keyword_only_parameters(): + signature = inspect.signature(fn) + l = list(signature.parameters) + sorted_l = sorted(l) + self.assertTrue(l) + self.assertEqual(l, sorted_l) + signature = inspect.signature(unsorted_keyword_only_parameters_fn) + l = list(signature.parameters) + self.assertEqual(l, unsorted_keyword_only_parameters) + class TestParameterObject(unittest.TestCase): def test_signature_parameter_kinds(self): diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-01-28-09-52-12.bpo-32697.RHlu6k.rst b/Misc/NEWS.d/next/Core and Builtins/2018-01-28-09-52-12.bpo-32697.RHlu6k.rst new file mode 100644 index 00000000000..97bc310683c --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-01-28-09-52-12.bpo-32697.RHlu6k.rst @@ -0,0 +1,3 @@ +Python now explicitly preserves the definition order of keyword-only +parameters. It's always preserved their order, but this behavior was never +guaranteed before; this behavior is now guaranteed and tested.