almost complete in-place implementation of Python's ParseTupleAndKeywords()
authorStefan Behnel <scoder@users.berlios.de>
Fri, 22 Aug 2008 07:12:17 +0000 (09:12 +0200)
committerStefan Behnel <scoder@users.berlios.de>
Fri, 22 Aug 2008 07:12:17 +0000 (09:12 +0200)
use argument exception messages from Python 2.6
TODO: handling of keyword-only arguments seems to require more work
TODO: clean up helper functions (currently somewhat redundant)

Cython/Compiler/Nodes.py
tests/errors/callargs.pyx [new file with mode: 0644]
tests/run/callargs.pyx [new file with mode: 0644]
tests/run/classkwonlyargs.pyx
tests/run/extkwonlyargs.pyx
tests/run/extstarargs.pyx
tests/run/kwonlyargs.pyx
tests/run/kwonlyargscall.pyx
tests/run/starargs.pyx

index 7eb3ede8faf80dd77b0e570410a6bf7ca139057b..77fb89cdf7bdd683113c01c06ea74f0921cd38ef 100644 (file)
@@ -1336,17 +1336,18 @@ class DefNode(FuncDefNode):
         self.declare_pyfunction(env)
         self.analyse_signature(env)
         self.return_type = self.entry.signature.return_type()
+        env.use_utility_code(raise_argtuple_too_long_utility_code)
+        env.use_utility_code(keyword_check_utility_code)
+        env.use_utility_code(check_double_keywords_utility_code)
         if self.signature_has_generic_args():
             if self.star_arg:
                 env.use_utility_code(get_stararg_utility_code)
-            elif self.signature_has_generic_args():
-                env.use_utility_code(raise_argtuple_too_long_utility_code)
             if not self.signature_has_nongeneric_args():
-                env.use_utility_code(get_keyword_string_check_utility_code)
+                env.use_utility_code(keyword_string_check_utility_code)
             elif self.starstar_arg:
-                env.use_utility_code(get_splitkeywords_utility_code)
+                env.use_utility_code(split_keywords_utility_code)
         if self.num_required_kw_args:
-            env.use_utility_code(get_checkkeywords_utility_code)
+            env.use_utility_code(check_keywords_utility_code)
 
     def analyse_signature(self, env):
         any_type_tests_needed = 0
@@ -1644,7 +1645,7 @@ class DefNode(FuncDefNode):
             if has_star_or_kw_args:
                 self.generate_stararg_getting_code(code)
 
-            self.generate_argument_tuple_parsing_code(
+            self.generate_tuple_and_keyword_parsing_code(
                 positional_args, arg_formats, arg_addrs, code)
 
         code.error_label = old_error_label
@@ -1663,7 +1664,7 @@ class DefNode(FuncDefNode):
             code.putln("return %s;" % self.error_value())
             code.put_label(end_label)
 
-    def generate_argument_tuple_parsing_code(self, positional_args,
+    def _generate_argument_tuple_parsing_code(self, positional_args,
                                              arg_formats, arg_addrs, code):
         # Unpack inplace if it's simple
         if not self.num_required_kw_args:
@@ -1689,20 +1690,7 @@ class DefNode(FuncDefNode):
                     code.putln('if (PyTuple_GET_SIZE(%s) > %s) {' % (Naming.args_cname, i))
                     closing += 1
                 item = "PyTuple_GET_ITEM(%s, %s)" % (Naming.args_cname, i)
-                if arg.type.is_pyobject:
-                    if arg.is_generic:
-                        item = PyrexTypes.typecast(arg.type, PyrexTypes.py_object_type, item)
-                    code.putln("%s = %s;" % (arg.entry.cname, item))
-                else:
-                    func = arg.type.from_py_function
-                    if func:
-                        code.putln("%s = %s(%s); %s" % (
-                            arg.entry.cname,
-                            func,
-                            item,
-                            code.error_goto_if(arg.type.error_condition(arg.entry.cname), arg.pos)))
-                    else:
-                        error(arg.pos, "Cannot convert Python object argument to type '%s'" % arg.type)
+                self.generate_arg_assignment(arg, item, code)
                 i += 1
             for _ in range(closing):
                 code.putln('}')
@@ -1722,6 +1710,22 @@ class DefNode(FuncDefNode):
         if not self.num_required_kw_args:
             code.putln('}')
 
+    def generate_arg_assignment(self, arg, item, code):
+        if arg.type.is_pyobject:
+            if arg.is_generic:
+                item = PyrexTypes.typecast(arg.type, PyrexTypes.py_object_type, item)
+            code.putln("%s = %s;" % (arg.entry.cname, item))
+        else:
+            func = arg.type.from_py_function
+            if func:
+                code.putln("%s = %s(%s); %s" % (
+                    arg.entry.cname,
+                    func,
+                    item,
+                    code.error_goto_if(arg.type.error_condition(arg.entry.cname), arg.pos)))
+            else:
+                error(arg.pos, "Cannot convert Python object argument to type '%s'" % arg.type)
+
     def put_stararg_decrefs(self, code):
         if self.star_arg:
             code.put_decref(Naming.args_cname, py_object_type)
@@ -1778,8 +1782,7 @@ class DefNode(FuncDefNode):
             code.put_incref(Naming.args_cname, py_object_type)
             code.put("%s = %s; " % (star_arg_cname, Naming.empty_tuple))
             code.put_incref(Naming.empty_tuple, py_object_type)
-            code.putln("}")
-            code.putln("else {")
+            code.putln("} else {")
             code.putln(
                 "if (unlikely(__Pyx_SplitStarArg(&%s, %d, &%s) < 0)) return %s;" % (
                     Naming.args_cname,
@@ -1788,7 +1791,7 @@ class DefNode(FuncDefNode):
                     self.error_value()))
             code.putln("}")
             self.star_arg.entry.xdecref_cleanup = 0
-        elif self.signature_has_generic_args():
+        else:
             # make sure supernumerous positional arguments do not run
             # into keyword-only arguments and provide a more helpful
             # message than PyArg_ParseTupelAndKeywords()
@@ -1798,18 +1801,31 @@ class DefNode(FuncDefNode):
         if self.starstar_arg:
             handle_error = 1
             code.put(
-                "if (unlikely(__Pyx_SplitKeywords(&%s, %s, &%s, %s) < 0)) " % (
+                'if (unlikely(__Pyx_SplitKeywords(&%s, %s, &%s, %s, PyTuple_GET_SIZE(%s), "%s") < 0)) ' % (
                     Naming.kwds_cname,
                     Naming.kwdlist_cname,
                     self.starstar_arg.entry.cname,
-                    self.reqd_kw_flags_cname))
+                    self.reqd_kw_flags_cname,
+                    Naming.args_cname,
+                    self.name.utf8encode()))
             self.starstar_arg.entry.xdecref_cleanup = 0
         elif self.num_required_kw_args:
             handle_error = 1
-            code.put("if (unlikely(__Pyx_CheckRequiredKeywords(%s, %s, %s) < 0)) " % (
+            code.put('if (unlikely(__Pyx_CheckRequiredKeywords(%s, %s, %s, PyTuple_GET_SIZE(%s), "%s") < 0)) ' % (
+                    Naming.kwds_cname,
+                    Naming.kwdlist_cname,
+                    self.reqd_kw_flags_cname,
+                    Naming.args_cname,
+                    self.name.utf8encode()))
+        else:
+            # check that positional arguments are not passed as keywords also
+            handle_error = 1
+            code.put('if (unlikely(%s) && unlikely(__Pyx_CheckDoubleKeywords(%s, %s, PyTuple_GET_SIZE(%s), "%s") < 0)) ' % (
+                    Naming.kwds_cname,
                     Naming.kwds_cname,
                     Naming.kwdlist_cname,
-                    self.reqd_kw_flags_cname))
+                    Naming.args_cname,
+                    self.name.utf8encode()))
 
         if handle_error:
             if self.star_arg:
@@ -1821,11 +1837,109 @@ class DefNode(FuncDefNode):
             else:
                 code.putln(error_return)
 
+    def generate_tuple_and_keyword_parsing_code(self, positional_args,
+                                                arg_formats, arg_addrs, code):
+        min_positional_args = self.num_required_args - self.num_required_kw_args
+        if len(self.args) > 0 and self.args[0].is_self_arg:
+            min_positional_args -= 1
+        max_positional_args = len(positional_args)
+
+        # just a quick check to start with
+        if not self.star_arg:
+            code.putln("if (unlikely(PyTuple_GET_SIZE(%s) > %d)) {" % (
+                    Naming.args_cname, max_positional_args))
+            code.put('__Pyx_RaiseArgtupleInvalid("%s", %d, %d, PyTuple_GET_SIZE(%s)); ' % (
+                    self.name.utf8encode(), min_positional_args,
+                    max_positional_args, Naming.args_cname))
+            code.putln(code.error_goto(self.pos))
+            code.putln("}")
+
+        # --- optimised code when we receive keyword arguments
+        code.putln("if (unlikely(%s) && (PyDict_Size(%s) > 0)) {" % (
+                Naming.kwds_cname, Naming.kwds_cname))
+        code.putln("PyObject* values[%d];" % max_positional_args)
+        code.putln("Py_ssize_t kw_args = PyDict_Size(%s), arg;" %
+                   Naming.kwds_cname)
+
+        code.putln("for (arg=0; arg < PyTuple_GET_SIZE(%s); arg++) {" %
+                   Naming.args_cname)
+        code.putln("values[arg] = PyTuple_GET_ITEM(%s, arg);" %
+                   Naming.args_cname)
+        code.putln("if (unlikely(PyDict_GetItemString(%s, %s[arg]))) {" % (
+                Naming.kwds_cname, Naming.kwdlist_cname))
+        code.put(      '__Pyx_RaiseDoubleKeywordsError("%s", %s[arg]); ' % (
+                self.name.utf8encode(), Naming.kwdlist_cname))
+        code.putln(code.error_goto(self.pos))
+        code.putln('}')
+        code.putln('}')
+
+        code.putln("for (arg=PyTuple_GET_SIZE(%s); arg < %d; arg++) {" % (
+                Naming.args_cname, max_positional_args))
+        code.putln('values[arg] = PyDict_GetItemString(%s, %s[arg]);' % (
+                Naming.kwds_cname, Naming.kwdlist_cname))
+        code.putln('if (values[arg]) kw_args--;');
+        code.putln('}')
+
+        code.putln('if (unlikely(kw_args > 0)) {')
+        code.put('if (!__Pyx_CheckKeywords(%s, "%s", %s)) ' % (
+                Naming.kwds_cname, self.name.utf8encode(), Naming.kwdlist_cname))
+        code.putln(code.error_goto(self.pos))
+        code.putln('}')
+
+        default_seen = False
+        for i, arg in enumerate(positional_args):
+            if arg.default:
+                default_seen = True
+            if default_seen:
+                code.putln("if (values[%d]) {" % i)
+            self.generate_arg_assignment(arg, "values[%d]" % i, code)
+            if default_seen:
+                if not arg.default:
+                    code.putln('} else {')
+                    code.put('__Pyx_RaiseArgtupleInvalid("%s", %d, %d, %d); ' % (
+                            self.name.utf8encode(), min_positional_args,
+                            max_positional_args, i))
+                    code.putln(code.error_goto(self.pos))
+                code.putln('}')
+
+        # --- optimised code when we did not receive any keyword arguments
+        code.putln('} else if (unlikely(PyTuple_GET_SIZE(%s) < %d)) {' % (
+                Naming.args_cname, min_positional_args))
+        code.put('__Pyx_RaiseArgtupleInvalid("%s", %d, %d, PyTuple_GET_SIZE(%s)); ' % (
+                self.name.utf8encode(), min_positional_args,
+                max_positional_args, Naming.args_cname))
+        code.putln(code.error_goto(self.pos))
+
+        code.putln('} else {')
+        closing = 0
+        for i, arg in enumerate(positional_args):
+            if arg.default:
+                code.putln('if (PyTuple_GET_SIZE(%s) > %s) {' % (Naming.args_cname, i))
+                closing += 1
+            item = "PyTuple_GET_ITEM(%s, %s)" % (Naming.args_cname, i)
+            self.generate_arg_assignment(arg, item, code)
+        for _ in range(closing):
+            code.putln('}')
+
+        code.putln('}')
+        return
+
+        argformat = '"%s"' % string.join(arg_formats, "")
+        pt_arglist = [Naming.args_cname, Naming.kwds_cname, argformat, Naming.kwdlist_cname] + arg_addrs
+        pt_argstring = string.join(pt_arglist, ", ")
+        code.putln(
+            'if (unlikely(!PyArg_ParseTupleAndKeywords(%s))) %s' % (
+                pt_argstring,
+                code.error_goto(self.pos)))
+        self.generate_argument_conversion_code(code)
+
+        code.putln('}')
+
     def generate_positional_args_check(self, code, nargs):
         code.putln("if (unlikely(PyTuple_GET_SIZE(%s) > %d)) {" % (
                 Naming.args_cname, nargs))
-        code.putln("__Pyx_RaiseArgtupleTooLong(%d, PyTuple_GET_SIZE(%s));" % (
-                nargs, Naming.args_cname))
+        code.putln('__Pyx_RaiseArgtupleInvalid("%s", 0, %d, PyTuple_GET_SIZE(%s));' % (
+                self.name.utf8encode(), nargs, Naming.args_cname))
         code.putln("return %s;" % self.error_value())
         code.putln("}")
 
@@ -4267,25 +4381,42 @@ static INLINE int __Pyx_SplitStarArg(
 
 #------------------------------------------------------------------------------------
 #
-#  __Pyx_RaiseArgtupleTooLong raises the correct exception when too
-#  many positional arguments were found.  This handles Py_ssize_t
-#  formatting correctly.
+#  __Pyx_RaiseArgtupleInvalid raises the correct exception when too
+#  many or too few positional arguments were found.  This handles
+#  Py_ssize_t formatting correctly.
 
 raise_argtuple_too_long_utility_code = [
 """
-static INLINE void __Pyx_RaiseArgtupleTooLong(Py_ssize_t num_expected, Py_ssize_t num_found); /*proto*/
+static INLINE void __Pyx_RaiseArgtupleInvalid(char* func_name,
+    Py_ssize_t num_min, Py_ssize_t num_max, Py_ssize_t num_found); /*proto*/
 ""","""
-static INLINE void __Pyx_RaiseArgtupleTooLong(
-    Py_ssize_t num_expected,
+static INLINE void __Pyx_RaiseArgtupleInvalid(
+    char* func_name,
+    Py_ssize_t num_min,
+    Py_ssize_t num_max,
     Py_ssize_t num_found)
 {
-    const char* error_message =
-    #if PY_VERSION_HEX < 0x02050000
-        "function takes at most %d positional arguments (%d given)";
-    #else
-        "function takes at most %zd positional arguments (%zd given)";
-    #endif
-    PyErr_Format(PyExc_TypeError, error_message, num_expected, num_found);
+    if (num_found < num_min) {
+        PyErr_Format(PyExc_TypeError,
+        #if PY_VERSION_HEX < 0x02050000
+            "%s() takes at least %d positional arguments (%d given)",
+        #elif PY_MAJOR_VERSION >= 3
+            "%U() takes at most %zd positional arguments (%zd given)",
+        #else
+            "%s() takes at least %zd positional arguments (%zd given)",
+        #endif
+            func_name, num_min, num_found);
+    } else {
+        PyErr_Format(PyExc_TypeError,
+        #if PY_VERSION_HEX < 0x02050000
+            "%s() takes at most %d positional arguments (%d given)",
+        #elif PY_MAJOR_VERSION >= 3
+            "%U() takes at most %zd positional arguments (%zd given)",
+        #else
+            "%s() takes at most %zd positional arguments (%zd given)",
+        #endif
+            func_name, num_max, num_found);
+    }
 }
 """]
 
@@ -4295,7 +4426,7 @@ static INLINE void __Pyx_RaiseArgtupleTooLong(
 #  were passed to a function, or if any keywords were passed to a
 #  function that does not accept them.
 
-get_keyword_string_check_utility_code = [
+keyword_string_check_utility_code = [
 """
 static int __Pyx_CheckKeywordStrings(PyObject *kwdict, const char* function_name, int kw_allowed); /*proto*/
 ""","""
@@ -4332,6 +4463,68 @@ static int __Pyx_CheckKeywordStrings(
 }
 """]
 
+#------------------------------------------------------------------------------------
+#
+#  __Pyx_CheckKeywords raises an error if non-string keywords were
+#  passed to a function, or if any unsupported keywords were passed to
+#  a function.
+
+keyword_check_utility_code = [
+"""
+static int __Pyx_CheckKeywords(PyObject *kwdict, const char* function_name,
+    char** argnames); /*proto*/
+""","""
+static int __Pyx_CheckKeywords(
+    PyObject *kwdict,
+    const char* function_name,
+    char** argnames)
+{
+    PyObject* key = 0;
+    Py_ssize_t pos = 0;
+    char** name;
+    while (PyDict_Next(kwdict, &pos, &key, 0)) {
+        #if PY_MAJOR_VERSION < 3
+        if (unlikely(!PyString_Check(key))) {
+        #else
+        if (unlikely(!PyUnicode_Check(key))) {
+        #endif
+            PyErr_Format(PyExc_TypeError,
+                         "%s() keywords must be strings", function_name);
+            return 0;
+        } else if (argnames) {
+            name = argnames;
+            while (*name) {
+                #if PY_MAJOR_VERSION >= 3
+                PyObject* utf8_key = PyUnicode_AsUTF8String(key);
+                if (!utf8_key) return -1;
+                if (strcmp(*name, PyString_AS_STRING(utf8_key)) == 0) {
+                     Py_DECREF(utf8_key);
+                     break;
+                }
+                Py_DECREF(utf8_key);
+                #else
+                if (strcmp(*name, PyString_AS_STRING(key)) == 0)
+                    break;
+                #endif
+                name++;
+            }
+            if (!*name) {
+                PyErr_Format(PyExc_TypeError,
+                #if PY_MAJOR_VERSION < 3
+                             "'%s' is an invalid keyword argument for this function",
+                             PyString_AS_STRING(key));
+                #else
+                             "'%U' is an invalid keyword argument for this function",
+                             key);
+                #endif
+                return 0;
+            }
+        }
+    }
+    return 1;
+}
+"""]
+
 #------------------------------------------------------------------------------------
 #
 #  __Pyx_SplitKeywords splits the kwds dict into two parts one part
@@ -4349,17 +4542,24 @@ static int __Pyx_CheckKeywordStrings(
 #  the names in kwd_list, indicating required keyword arguments. If
 #  any of these are not present in kwds, an exception is raised.
 #
+#  If num_posargs is greater 0, it denotes the number of positional
+#  arguments that were passed and that must therefore not get passed
+#  as keyword arguments as well.
+#
 
-get_splitkeywords_utility_code = [
+split_keywords_utility_code = [
 """
 static int __Pyx_SplitKeywords(PyObject **kwds, char *kwd_list[], \
-    PyObject **kwds2, char rqd_kwds[]); /*proto*/
+    PyObject **kwds2, char rqd_kwds[],
+    Py_ssize_t num_posargs, char* func_name); /*proto*/
 ""","""
 static int __Pyx_SplitKeywords(
     PyObject **kwds,
     char *kwd_list[], 
     PyObject **kwds2,
-    char rqd_kwds[])
+    char rqd_kwds[],
+    Py_ssize_t num_posargs,
+    char* func_name)
 {
     PyObject *s = 0, *x = 0, *kwds1 = 0;
     int i;
@@ -4380,6 +4580,8 @@ static int __Pyx_SplitKeywords(
             #endif
             x = PyDict_GetItem(*kwds, s);
             if (x) {
+                if (i < num_posargs)
+                    goto arg_passed_twice;
                 if (PyDict_SetItem(kwds1, s, x) < 0)
                     goto bad;
                 if (PyDict_DelItem(*kwds2, s) < 0)
@@ -4404,6 +4606,10 @@ static int __Pyx_SplitKeywords(
 
     *kwds = kwds1;
     return 0;
+arg_passed_twice:
+    PyErr_Format(PyExc_TypeError,
+        "%s() got multiple values for keyword argument '%s'", func_name, *p);
+    goto bad;
 missing_kwarg:
     PyErr_Format(PyExc_TypeError,
         "required keyword argument '%s' is missing", *p);
@@ -4415,23 +4621,32 @@ bad:
 }
 """]
 
-get_checkkeywords_utility_code = [
+check_keywords_utility_code = [
 """
 static INLINE int __Pyx_CheckRequiredKeywords(PyObject *kwds, char *kwd_list[],
-    char rqd_kwds[]); /*proto*/
+    char rqd_kwds[], Py_ssize_t num_posargs, char* func_name); /*proto*/
 ""","""
 static INLINE int __Pyx_CheckRequiredKeywords(
     PyObject *kwds,
     char *kwd_list[],
-    char rqd_kwds[])
+    char rqd_kwds[],
+    Py_ssize_t num_posargs,
+    char* func_name)
 {
     int i;
     char **p;
 
     if (kwds) {
-        for (i = 0, p = kwd_list; *p; i++, p++)
+        p = kwd_list;
+        for (i=0; i < num_posargs && *p; i++, p++) {
+            if (PyDict_GetItemString(kwds, *p))
+                goto arg_passed_twice;
+        }
+        while (*p) {
             if (rqd_kwds[i] && !PyDict_GetItemString(kwds, *p))
                 goto missing_kwarg;
+            i++; p++;
+        }
     }
     else {
         for (i = 0, p = kwd_list; *p; i++, p++)
@@ -4440,6 +4655,10 @@ static INLINE int __Pyx_CheckRequiredKeywords(
     }
 
     return 0;
+arg_passed_twice:
+    PyErr_Format(PyExc_TypeError,
+        "%s() got multiple values for keyword argument '%s'", func_name, *p);
+    return -1;
 missing_kwarg:
     PyErr_Format(PyExc_TypeError,
         "required keyword argument '%s' is missing", *p);
@@ -4447,6 +4666,36 @@ missing_kwarg:
 }
 """]
 
+check_double_keywords_utility_code = [
+"""
+static INLINE int __Pyx_CheckDoubleKeywords(PyObject *kwds, char *kwd_list[],
+    Py_ssize_t num_posargs, const char* func_name); /*proto*/
+static int __Pyx_RaiseDoubleKeywordsError(const char* func_name,
+                                          const char* kw_name); /*proto*/
+""","""
+static INLINE int __Pyx_CheckDoubleKeywords(
+    PyObject *kwds,
+    char *kwd_list[],
+    Py_ssize_t num_posargs,
+    const char* func_name)
+{
+    int i;
+    char** p = kwd_list;
+    for (i=0; i < num_posargs && *p; i++, p++) {
+        if (PyDict_GetItemString(kwds, *p))
+            return __Pyx_RaiseDoubleKeywordsError(func_name, *p);
+    }
+
+    return 0;
+}
+
+static int __Pyx_RaiseDoubleKeywordsError(const char* func_name, const char* kw_name) {
+    PyErr_Format(PyExc_TypeError,
+        "%s() got multiple values for keyword argument '%s'", func_name, kw_name);
+    return -1;
+}
+"""]
+
 #------------------------------------------------------------------------------------
 
 unraisable_exception_utility_code = [
diff --git a/tests/errors/callargs.pyx b/tests/errors/callargs.pyx
new file mode 100644 (file)
index 0000000..e9c1b36
--- /dev/null
@@ -0,0 +1,20 @@
+def f(*args, **kwargs):
+    pass
+
+args   = (1,2,3)
+kwargs = {u"test" : "toast"}
+
+def test():
+    f(*args, 1, 2, 3)
+    f(**kwargs, 1, 2, c=3)
+    f(*args, **kwargs, *args)
+    f(1, 2, c=3, *args, **kwargs, *args)
+    f(1, 2, c=3, *args, d=5, **kwargs, **kwargs)
+    f(1, 2, c=3, *args, d=5, **kwargs, x=6)
+    f(1=2)
+
+# too bad we don't get more errors here ...
+
+_ERRORS = u"""
+8:13: Non-keyword arg following star-arg
+"""
diff --git a/tests/run/callargs.pyx b/tests/run/callargs.pyx
new file mode 100644 (file)
index 0000000..27d46ca
--- /dev/null
@@ -0,0 +1,31 @@
+__doc__ = u"""
+  >>> test()
+  1 2 3 * 0 0
+  1 2 3 * 0 0
+  1 2 9 * 2 1
+  1 2 7 * 2 1
+  1 2 9 * 2 2
+  1 2 9 * 2 2
+  1 2 9 * 2 3
+"""
+
+def f(a, b, c, *args, **kwargs):
+    print a, b, c, u'*', len(args), len(kwargs)
+
+args = (9,8,7)
+
+import sys
+if sys.version_info[0] >= 3:
+    kwargs = {u"test" : u"toast"}
+else:
+    kwargs = {"test" : u"toast"}
+
+
+def test():
+    f(1,2,3)
+    f(1,2, c=3)
+    f(1,2, d=3, *args)
+    f(1,2, d=3, *(7,8,9))
+    f(1,2, d=3, *args, **kwargs)
+    f(1,2, d=3, *args, e=5)
+    f(1,2, d=3, *args, e=5, **kwargs)
index 961c78ec3793b35ef6b6e0a7f9f6bf3bec4ef86d..2c2b1e2b020c0880b124aeae5a2d5ca7b0755240 100644 (file)
@@ -5,20 +5,20 @@ __doc__ = u"""
     >>> b(1,2,3)
     >>> b(1,2,3,4)
     Traceback (most recent call last):
-    TypeError: function takes exactly 4 arguments (5 given)
+    TypeError: b() takes at most 4 positional arguments (5 given)
 
     >>> c(1,2)
     >>> c(1,2,3)
     >>> c(1,2,3,4)
     Traceback (most recent call last):
-    TypeError: function takes at most 4 arguments (5 given)
+    TypeError: c() takes at most 4 positional arguments (5 given)
 
     >>> d(1,2)
     >>> d(1,2, c=1)
 
     >>> d(1,2,3)
     Traceback (most recent call last):
-    TypeError: function takes at most 3 positional arguments (4 given)
+    TypeError: d() takes at most 3 positional arguments (4 given)
     >>> d(1,2, d=1)
     Traceback (most recent call last):
     TypeError: 'd' is an invalid keyword argument for this function
@@ -30,14 +30,14 @@ __doc__ = u"""
     >>> e(1,2,3)
     >>> e(1,2,3,4)
     Traceback (most recent call last):
-    TypeError: function takes at most 4 positional arguments (5 given)
+    TypeError: e() takes at most 4 positional arguments (5 given)
 
     >>> f(1,2, c=1)
     >>> f(1,2, c=1, d=2)
 
     >>> f(1,2,3)
     Traceback (most recent call last):
-    TypeError: function takes at most 3 positional arguments (4 given)
+    TypeError: f() takes at most 3 positional arguments (4 given)
     >>> f(1,2)
     Traceback (most recent call last):
     TypeError: required keyword argument 'c' is missing
@@ -51,7 +51,7 @@ __doc__ = u"""
 
     >>> g(1,2,3) #doctest: +ELLIPSIS
     Traceback (most recent call last):
-    TypeError: function takes at most 3 positional arguments (4 given)
+    TypeError: g() takes at most 3 positional arguments (4 given)
     >>> g(1,2)
     Traceback (most recent call last):
     TypeError: required keyword argument 'c' is missing
index 5dc8306c357b515c74e061637e407a309c3d7cef..b2d343ee738807f3806b05ad557847e03060bce2 100644 (file)
@@ -5,20 +5,20 @@ __doc__ = u"""
     >>> b(1,2,3)
     >>> b(1,2,3,4)
     Traceback (most recent call last):
-    TypeError: function takes exactly 3 arguments (4 given)
+    TypeError: b() takes at most 3 positional arguments (4 given)
 
     >>> c(1,2)
     >>> c(1,2,3)
     >>> c(1,2,3,4)
     Traceback (most recent call last):
-    TypeError: function takes at most 3 arguments (4 given)
+    TypeError: c() takes at most 3 positional arguments (4 given)
 
     >>> d(1,2)
     >>> d(1,2, c=1)
 
     >>> d(1,2,3)
     Traceback (most recent call last):
-    TypeError: function takes at most 2 positional arguments (3 given)
+    TypeError: d() takes at most 2 positional arguments (3 given)
     >>> d(1,2, d=1)
     Traceback (most recent call last):
     TypeError: 'd' is an invalid keyword argument for this function
@@ -30,14 +30,14 @@ __doc__ = u"""
     >>> e(1,2,3)
     >>> e(1,2,3,4)
     Traceback (most recent call last):
-    TypeError: function takes at most 3 positional arguments (4 given)
+    TypeError: e() takes at most 3 positional arguments (4 given)
 
     >>> f(1,2, c=1)
     >>> f(1,2, c=1, d=2)
 
     >>> f(1,2,3)
     Traceback (most recent call last):
-    TypeError: function takes at most 2 positional arguments (3 given)
+    TypeError: f() takes at most 2 positional arguments (3 given)
     >>> f(1,2)
     Traceback (most recent call last):
     TypeError: required keyword argument 'c' is missing
@@ -51,7 +51,7 @@ __doc__ = u"""
 
     >>> g(1,2,3)
     Traceback (most recent call last):
-    TypeError: function takes at most 2 positional arguments (3 given)
+    TypeError: g() takes at most 2 positional arguments (3 given)
     >>> g(1,2)
     Traceback (most recent call last):
     TypeError: required keyword argument 'c' is missing
@@ -84,10 +84,6 @@ __doc__ = u"""
     TypeError: required keyword argument 'f' is missing
 """
 
-import sys, re
-if sys.version_info >= (2,6):
-    __doc__ = re.sub(u"Error: (.*)exactly(.*)", u"Error: \\1at most\\2", __doc__)
-
 cdef class Ext:
     def b(self, a, b, c):
         pass
index 454e6bde2b6cc64da07bc2bf1a759c3309b7d0af..0b785999dd5c76e1541dc1b9552728c889ec2e15 100644 (file)
@@ -7,10 +7,10 @@ __doc__ = u"""
     (1, 2, 3)
     >>> spam(1,2) #doctest: +ELLIPSIS
     Traceback (most recent call last):
-    TypeError: function takes exactly 3 arguments (2 given)
+    TypeError: spam() takes at least 3 positional arguments (2 given)
     >>> spam(1,2,3,4)
     Traceback (most recent call last):
-    TypeError: function takes exactly 3 arguments (4 given)
+    TypeError: spam() takes at most 3 positional arguments (4 given)
     >>> spam(1,2,3, a=1) #doctest: +ELLIPSIS
     Traceback (most recent call last):
     TypeError: 'a' is an invalid keyword argument for this function
@@ -23,7 +23,7 @@ __doc__ = u"""
     (1, 2, 3, (4, 5, 6, 7, 8, 9))
     >>> grail(1,2) #doctest: +ELLIPSIS
     Traceback (most recent call last):
-    TypeError: function takes exactly 3 arguments (2 given)
+    TypeError: grail() takes exactly 3 arguments (2 given)
     >>> grail(1,2,3, a=1) #doctest: +ELLIPSIS
     Traceback (most recent call last):
     TypeError: 'a' is an invalid keyword argument for this function
@@ -32,7 +32,7 @@ __doc__ = u"""
     (1, 2, 3, ())
     >>> swallow(1,2,3,4)
     Traceback (most recent call last):
-    TypeError: function takes at most 3 positional arguments (4 given)
+    TypeError: swallow() takes at most 3 positional arguments (4 given)
     >>> swallow(1,2,3, a=1, b=2)
     (1, 2, 3, (('a', 1), ('b', 2)))
     >>> swallow(1,2,3, x=1) #doctest: +ELLIPSIS
@@ -68,13 +68,13 @@ __doc__ = u"""
     (('a', 1), ('b', 2))
     >>> onlyk(1)
     Traceback (most recent call last):
-    TypeError: function takes at most 0 positional arguments (1 given)
+    TypeError: onlyk() takes at most 0 positional arguments (1 given)
     >>> onlyk(1, 2)
     Traceback (most recent call last):
-    TypeError: function takes at most 0 positional arguments (2 given)
+    TypeError: onlyk() takes at most 0 positional arguments (2 given)
     >>> onlyk(1, a=1, b=2)
     Traceback (most recent call last):
-    TypeError: function takes at most 0 positional arguments (1 given)
+    TypeError: onlyk() takes at most 0 positional arguments (1 given)
 
     >>> tk(a=1)
     (('a', 1),)
@@ -88,10 +88,6 @@ __doc__ = u"""
     (1, ('a', 1), ('b', 2))
 """
 
-import sys, re
-if sys.version_info >= (2,6):
-    __doc__ = re.sub(u"Error: (.*)exactly(.*)", u"Error: \\1at most\\2", __doc__)
-
 import sys, re
 if sys.version_info >= (2,6):
     __doc__ = re.sub(u"(ELLIPSIS[^>]*Error: )[^\n]*\n", u"\\1...\n", __doc__, re.M)
index 3bfd6e0f6dbe6aa099c1b66c8e4477f5d93a55cb..710dc476653408691fa1d6cac2570a6d83a675c4 100644 (file)
@@ -2,20 +2,20 @@ __doc__ = u"""
     >>> b(1,2,3)
     >>> b(1,2,3,4)
     Traceback (most recent call last):
-    TypeError: function takes exactly 3 arguments (4 given)
+    TypeError: b() takes at most 3 positional arguments (4 given)
 
     >>> c(1,2)
     >>> c(1,2,3)
     >>> c(1,2,3,4)
     Traceback (most recent call last):
-    TypeError: function takes at most 3 arguments (4 given)
+    TypeError: c() takes at most 3 positional arguments (4 given)
 
     >>> d(1,2)
     >>> d(1,2, c=1)
 
     >>> d(1,2,3)
     Traceback (most recent call last):
-    TypeError: function takes at most 2 positional arguments (3 given)
+    TypeError: d() takes at most 2 positional arguments (3 given)
     >>> d(1,2, d=1)
     Traceback (most recent call last):
     TypeError: 'd' is an invalid keyword argument for this function
@@ -27,14 +27,14 @@ __doc__ = u"""
     >>> e(1,2,3)
     >>> e(1,2,3,4)
     Traceback (most recent call last):
-    TypeError: function takes at most 3 positional arguments (4 given)
+    TypeError: e() takes at most 3 positional arguments (4 given)
 
     >>> f(1,2, c=1)
     >>> f(1,2, c=1, d=2)
 
     >>> f(1,2,3)
     Traceback (most recent call last):
-    TypeError: function takes at most 2 positional arguments (3 given)
+    TypeError: f() takes at most 2 positional arguments (3 given)
     >>> f(1,2)
     Traceback (most recent call last):
     TypeError: required keyword argument 'c' is missing
@@ -48,7 +48,7 @@ __doc__ = u"""
 
     >>> g(1,2,3)
     Traceback (most recent call last):
-    TypeError: function takes at most 2 positional arguments (3 given)
+    TypeError: g() takes at most 2 positional arguments (3 given)
     >>> g(1,2)
     Traceback (most recent call last):
     TypeError: required keyword argument 'c' is missing
@@ -81,10 +81,6 @@ __doc__ = u"""
     TypeError: required keyword argument 'f' is missing
 """
 
-import sys, re
-if sys.version_info >= (2,6):
-    __doc__ = re.sub(u"Error: (.*)exactly(.*)", u"Error: \\1at most\\2", __doc__)
-
 def b(a, b, c):
     pass
 
index b7da79305ac4198f41b729db20b2a279a0f14535..415ee88a71955b8e4235d72985b24f8e89c145b1 100644 (file)
@@ -2,20 +2,20 @@ __doc__ = u"""
     >>> call3(b)
     >>> call4(b)
     Traceback (most recent call last):
-    TypeError: function takes exactly 3 arguments (4 given)
+    TypeError: b() takes at most 3 positional arguments (4 given)
 
     >>> call2(c)
     >>> call3(c)
     >>> call4(c)
     Traceback (most recent call last):
-    TypeError: function takes at most 3 arguments (4 given)
+    TypeError: c() takes at most 3 positional arguments (4 given)
 
     >>> call2(d)
     >>> call2c(d)
 
     >>> call3(d)
     Traceback (most recent call last):
-    TypeError: function takes at most 2 positional arguments (3 given)
+    TypeError: d() takes at most 2 positional arguments (3 given)
     >>> call2d(d)
     Traceback (most recent call last):
     TypeError: 'd' is an invalid keyword argument for this function
@@ -27,14 +27,14 @@ __doc__ = u"""
     >>> call3(e)
     >>> call4(e)
     Traceback (most recent call last):
-    TypeError: function takes at most 3 positional arguments (4 given)
+    TypeError: e() takes at most 3 positional arguments (4 given)
 
     >>> call2c(f)
     >>> call2cd(f)
 
     >>> call3(f)
     Traceback (most recent call last):
-    TypeError: function takes at most 2 positional arguments (3 given)
+    TypeError: f() takes at most 2 positional arguments (3 given)
     >>> call2(f)
     Traceback (most recent call last):
     TypeError: required keyword argument 'c' is missing
@@ -48,7 +48,7 @@ __doc__ = u"""
 
     >>> call3(g)
     Traceback (most recent call last):
-    TypeError: function takes at most 2 positional arguments (3 given)
+    TypeError: g() takes at most 2 positional arguments (3 given)
     >>> call2(g)
     Traceback (most recent call last):
     TypeError: required keyword argument 'c' is missing
@@ -132,6 +132,10 @@ def call2cefd(f):
 def call2cfex(f):
     f(1,2, c=1, f=2, e=0, x=25)
 
+def call6argscfexy(f):
+    args = (1,2,3,4,5,6)
+    f(*args, c=1, f=2, e=3, x=25, y=11)
+
 def call6cfexy(f):
     f(1,2,3,4,5,6, c=1, f=2, e=3, x=25, y=11)
 
index 487d6b2f7de3e3944e438621e3efd1d9fae02da8..d16a2d97045651fdf44265a8c088c4b0265cab00 100644 (file)
@@ -3,10 +3,10 @@ __doc__ = u"""
     (1, 2, 3)
     >>> spam(1,2) #doctest: +ELLIPSIS
     Traceback (most recent call last):
-    TypeError: function takes exactly 3 arguments (2 given)
+    TypeError: spam() takes at least 3 positional arguments (2 given)
     >>> spam(1,2,3,4)
     Traceback (most recent call last):
-    TypeError: function takes exactly 3 arguments (4 given)
+    TypeError: spam() takes at most 3 positional arguments (4 given)
     >>> spam(1,2,3, a=1) #doctest: +ELLIPSIS
     Traceback (most recent call last):
     TypeError: 'a' is an invalid keyword argument for this function
@@ -19,7 +19,7 @@ __doc__ = u"""
     (1, 2, 3, (4, 5, 6, 7, 8, 9))
     >>> grail(1,2) #doctest: +ELLIPSIS
     Traceback (most recent call last):
-    TypeError: function takes exactly 3 arguments (2 given)
+    TypeError: grail() takes at least 3 positional arguments (2 given)
     >>> grail(1,2,3, a=1) #doctest: +ELLIPSIS
     Traceback (most recent call last):
     TypeError: 'a' is an invalid keyword argument for this function
@@ -28,7 +28,7 @@ __doc__ = u"""
     (1, 2, 3, ())
     >>> swallow(1,2,3,4)
     Traceback (most recent call last):
-    TypeError: function takes at most 3 positional arguments (4 given)
+    TypeError: swallow() takes at most 3 positional arguments (4 given)
     >>> swallow(1,2,3, a=1, b=2)
     (1, 2, 3, (('a', 1), ('b', 2)))
     >>> swallow(1,2,3, x=1) #doctest: +ELLIPSIS
@@ -64,13 +64,13 @@ __doc__ = u"""
     (('a', 1), ('b', 2))
     >>> onlyk(1)
     Traceback (most recent call last):
-    TypeError: function takes at most 0 positional arguments (1 given)
+    TypeError: onlyk() takes at most 0 positional arguments (1 given)
     >>> onlyk(1, 2)
     Traceback (most recent call last):
-    TypeError: function takes at most 0 positional arguments (2 given)
+    TypeError: onlyk() takes at most 0 positional arguments (2 given)
     >>> onlyk(1, a=1, b=2)
     Traceback (most recent call last):
-    TypeError: function takes at most 0 positional arguments (1 given)
+    TypeError: onlyk() takes at most 0 positional arguments (1 given)
 
     >>> tk(a=1)
     (('a', 1),)
@@ -84,10 +84,6 @@ __doc__ = u"""
     (1, ('a', 1), ('b', 2))
 """
 
-import sys, re
-if sys.version_info >= (2,6):
-    __doc__ = re.sub(u"Error: (.*)exactly(.*)", u"Error: \\1at most\\2", __doc__)
-
 import sys, re
 if sys.version_info >= (2,6):
     __doc__ = re.sub(u"(ELLIPSIS[^>]*Error: )[^\n]*\n", u"\\1...\n", __doc__, re.M)