Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions Doc/library/inspect.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand All @@ -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)

Expand Down
47 changes: 47 additions & 0 deletions Lib/test/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -829,6 +854,17 @@ 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_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):
Expand Down Expand Up @@ -2969,6 +3005,17 @@ 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_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):
Expand Down
Original file line number Diff line number Diff line change
@@ -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.