inspect.Signature: ensure that non-default params don't follow default ones #20427
authorYury Selivanov <yselivanov@sprymix.com>
Wed, 29 Jan 2014 15:58:16 +0000 (10:58 -0500)
committerYury Selivanov <yselivanov@sprymix.com>
Wed, 29 Jan 2014 15:58:16 +0000 (10:58 -0500)
Lib/inspect.py
Lib/test/test_inspect.py

index f0c12479edb76d9c396fd9fe14cf544e1f886c36..f06138c7a880e7f7044d25dd8202dc0fd318d030 100644 (file)
@@ -1924,6 +1924,7 @@ class Signature:
             if __validate_parameters__:
                 params = OrderedDict()
                 top_kind = _POSITIONAL_ONLY
+                kind_defaults = False
 
                 for idx, param in enumerate(parameters):
                     kind = param.kind
@@ -1933,9 +1934,27 @@ class Signature:
                         msg = 'wrong parameter order: {} before {}'
                         msg = msg.format(top_kind, kind)
                         raise ValueError(msg)
-                    else:
+                    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 param.default is _empty:
+                            if kind_defaults:
+                                # No default for this parameter, but the
+                                # previous parameter of the same kind had
+                                # a default
+                                msg = 'non-default argument follows default ' \
+                                      'argument'
+                                raise ValueError(msg)
+                        else:
+                            # There is a default for this parameter.
+                            kind_defaults = True
+
                     if name in params:
                         msg = 'duplicate parameter name: {!r}'.format(name)
                         raise ValueError(msg)
index 4f19f29165d04a7bae06c84d06822e26dfcf3177..6ee2c3060d76b804e97eb1504ca3055fe38b051f 100644 (file)
@@ -1522,11 +1522,13 @@ class TestSignatureObject(unittest.TestCase):
 
         self.assertEqual(str(S()), '()')
 
-        def test(po, pk, *args, ko, **kwargs):
+        def test(po, pk, pod=42, pkd=100, *args, ko, **kwargs):
             pass
         sig = inspect.signature(test)
         po = sig.parameters['po'].replace(kind=P.POSITIONAL_ONLY)
+        pod = sig.parameters['pod'].replace(kind=P.POSITIONAL_ONLY)
         pk = sig.parameters['pk']
+        pkd = sig.parameters['pkd']
         args = sig.parameters['args']
         ko = sig.parameters['ko']
         kwargs = sig.parameters['kwargs']
@@ -1549,6 +1551,15 @@ class TestSignatureObject(unittest.TestCase):
         with self.assertRaisesRegex(ValueError, 'duplicate parameter name'):
             S((po, pk, args, kwargs2, ko))
 
+        with self.assertRaisesRegex(ValueError, 'follows default argument'):
+            S((pod, po))
+
+        with self.assertRaisesRegex(ValueError, 'follows default argument'):
+            S((po, pkd, pk))
+
+        with self.assertRaisesRegex(ValueError, 'follows default argument'):
+            S((pkd, pk))
+
     def test_signature_immutability(self):
         def test(a):
             pass