From 2d1e09e90ec8bd40db3e7069e78f17c72add666e Mon Sep 17 00:00:00 2001 From: Larry Hastings Date: Sun, 28 Jan 2018 09:52:20 -0800 Subject: [PATCH 1/2] Definition order of kwonly params is now preserved. --- Doc/library/inspect.rst | 14 ++++++-- Lib/test/test_inspect.py | 35 +++++++++++++++++++ .../2018-01-28-09-52-12.bpo-32697.RHlu6k.rst | 3 ++ 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2018-01-28-09-52-12.bpo-32697.RHlu6k.rst diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 147e802cac4be6..eb8c89faabf10c 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.6 + Preservation of declaration order was only explicitly guaranteed + as of Python 3.6, although it was preserved in all previous versions + of 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. @@ -919,7 +925,9 @@ Classes and functions :func:`signature` in Python 3.5, but that decision has been reversed in order to restore a clearly supported standard interface for single-source Python 2/3 code migrating away from the legacy - :func:`getargspec` API. + :func:`getargspec` API. Also, preservation of declaration order + was only explicitly guaranteed as of Python 3.6, although it was + preserved in all previous versions of Python 3. .. function:: getargvalues(frame) diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index cb51f8aff29079..bca7f7d8cd2a9b 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -57,6 +57,25 @@ def revise(filename, *args): git = mod.StupidGit() + +def signatures_with_lexigraphic_keyword_only_parameters(): + """ + Yields a whole bunch of functions with only keyword-only parameters, + where those parameters are always in lexigraphically 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'] + + class IsTestBase(unittest.TestCase): predicates = set([inspect.isbuiltin, inspect.isclass, inspect.iscode, inspect.isframe, inspect.isfunction, inspect.ismethod, @@ -829,6 +848,14 @@ def test_getfullagrspec_builtin_func_no_signature(self): with self.assertRaises(TypeError): inspect.getfullargspec(builtin) + def test_getfullargspec_definition_order_preserved_on_kwonly(self): + for fn in signatures_with_lexigraphic_keyword_only_parameters(): + signature = inspect.getfullargspec(fn) + l = list(signature.kwonlyargs) + sorted_l = sorted(l) + self.assertTrue(l) + self.assertEqual(l, sorted_l) + def test_getargspec_method(self): class A(object): def m(self): @@ -2969,6 +2996,14 @@ class MySignature(inspect.Signature): pass 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_lexigraphic_keyword_only_parameters(): + signature = inspect.signature(fn) + l = list(signature.parameters) + sorted_l = sorted(l) + self.assertTrue(l) + self.assertEqual(l, sorted_l) + 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 00000000000000..97bc310683c312 --- /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. From d91b711edd879110a8433c96c20538318d81adf9 Mon Sep 17 00:00:00 2001 From: Larry Hastings Date: Sun, 28 Jan 2018 11:02:10 -0800 Subject: [PATCH 2/2] Incorporate suggestions from GH-5391 reviews! --- Doc/library/inspect.rst | 17 ++++++++++------- Lib/test/test_inspect.py | 20 ++++++++++++++++---- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index eb8c89faabf10c..bc4316fabaee58 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -605,10 +605,10 @@ function. :class:`Parameter` objects. Parameters appear in strict definition order, including keyword-only parameters. - .. versionchanged:: 3.6 - Preservation of declaration order was only explicitly guaranteed - as of Python 3.6, although it was preserved in all previous versions - of Python 3. + .. 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 @@ -925,9 +925,12 @@ Classes and functions :func:`signature` in Python 3.5, but that decision has been reversed in order to restore a clearly supported standard interface for single-source Python 2/3 code migrating away from the legacy - :func:`getargspec` API. Also, preservation of declaration order - was only explicitly guaranteed as of Python 3.6, although it was - preserved in all previous versions of Python 3. + :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 bca7f7d8cd2a9b..1a856f6387e8ad 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -58,10 +58,10 @@ def revise(filename, *args): git = mod.StupidGit() -def signatures_with_lexigraphic_keyword_only_parameters(): +def signatures_with_lexicographic_keyword_only_parameters(): """ Yields a whole bunch of functions with only keyword-only parameters, - where those parameters are always in lexigraphically sorted order. + 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)): @@ -76,6 +76,12 @@ def signatures_with_lexigraphic_keyword_only_parameters(): 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, @@ -849,12 +855,15 @@ def test_getfullagrspec_builtin_func_no_signature(self): inspect.getfullargspec(builtin) def test_getfullargspec_definition_order_preserved_on_kwonly(self): - for fn in signatures_with_lexigraphic_keyword_only_parameters(): + 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): @@ -2997,12 +3006,15 @@ class MySignature(inspect.Signature): pass self.assertTrue(isinstance(sig, MySignature)) def test_signature_definition_order_preserved_on_kwonly(self): - for fn in signatures_with_lexigraphic_keyword_only_parameters(): + 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):