[Python-checkins] bpo-37122: Make co->co_argcount represent the total number of positonal arguments in the code object (GH-13726)

Pablo Galindo webhook-mailer at python.org
Sat Jun 1 13:08:08 EDT 2019


https://github.com/python/cpython/commit/cd74e66a8c420be675fd2fbf3fe708ac02ee9f21
commit: cd74e66a8c420be675fd2fbf3fe708ac02ee9f21
branch: master
author: Pablo Galindo <Pablogsal at gmail.com>
committer: GitHub <noreply at github.com>
date: 2019-06-01T18:08:04+01:00
summary:

bpo-37122: Make co->co_argcount represent the total number of positonal arguments in the code object (GH-13726)

files:
A Misc/NEWS.d/next/Core and Builtins/2019-06-01-16-53-41.bpo-37122.dZ3-NY.rst
M Doc/c-api/code.rst
M Doc/reference/datamodel.rst
M Doc/whatsnew/3.8.rst
M Lib/inspect.py
M Lib/pdb.py
M Lib/test/test_code.py
M Lib/test/test_dis.py
M Lib/test/test_positional_only_arg.py
M Objects/call.c
M Objects/codeobject.c
M Objects/typeobject.c
M Python/ceval.c
M Python/compile.c

diff --git a/Doc/c-api/code.rst b/Doc/c-api/code.rst
index 7aa91ee84d2e..92baa4c7df5c 100644
--- a/Doc/c-api/code.rst
+++ b/Doc/c-api/code.rst
@@ -42,6 +42,8 @@ bound into a function.
 
    .. versionchanged:: 3.8
       An extra parameter is required (*posonlyargcount*) to support :PEP:`570`.
+      The first parameter (*argcount*) now represents the total number of positional arguments,
+      including positional-only.
 
    .. audit-event:: code.__new__ "code filename name argcount kwonlyargcount nlocals stacksize flags"
 
diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst
index c566dfdf856d..44017d8a55df 100644
--- a/Doc/reference/datamodel.rst
+++ b/Doc/reference/datamodel.rst
@@ -907,24 +907,26 @@ Internal types
          single: co_freevars (code object attribute)
 
       Special read-only attributes: :attr:`co_name` gives the function name;
-      :attr:`co_argcount` is the number of positional arguments (including arguments
-      with default values); :attr:`co_posonlyargcount` is the number of
-      positional-only arguments (including arguments with default values);
-      :attr:`co_kwonlyargcount` is the number of keyword-only arguments (including
-      arguments with default values); :attr:`co_nlocals` is the number of local
-      variables used by the function (including arguments); :attr:`co_varnames` is a
-      tuple containing the names of the local variables (starting with the argument
-      names); :attr:`co_cellvars` is a tuple containing the names of local variables
+      :attr:`co_argcount` is the total number of positional arguments
+      (including positional-only arguments and arguments with default values);
+      :attr:`co_posonlyargcount` is the number of positional-only arguments
+      (including arguments with default values); :attr:`co_kwonlyargcount` is
+      the number of keyword-only arguments (including arguments with default
+      values); :attr:`co_nlocals` is the number of local variables used by the
+      function (including arguments); :attr:`co_varnames` is a tuple containing
+      the names of the local variables (starting with the argument names);
+      :attr:`co_cellvars` is a tuple containing the names of local variables
       that are referenced by nested functions; :attr:`co_freevars` is a tuple
-      containing the names of free variables; :attr:`co_code` is a string representing
-      the sequence of bytecode instructions; :attr:`co_consts` is a tuple containing
-      the literals used by the bytecode; :attr:`co_names` is a tuple containing the
-      names used by the bytecode; :attr:`co_filename` is the filename from which the
-      code was compiled; :attr:`co_firstlineno` is the first line number of the
-      function; :attr:`co_lnotab` is a string encoding the mapping from bytecode
-      offsets to line numbers (for details see the source code of the interpreter);
-      :attr:`co_stacksize` is the required stack size (including local variables);
-      :attr:`co_flags` is an integer encoding a number of flags for the interpreter.
+      containing the names of free variables; :attr:`co_code` is a string
+      representing the sequence of bytecode instructions; :attr:`co_consts` is
+      a tuple containing the literals used by the bytecode; :attr:`co_names` is
+      a tuple containing the names used by the bytecode; :attr:`co_filename` is
+      the filename from which the code was compiled; :attr:`co_firstlineno` is
+      the first line number of the function; :attr:`co_lnotab` is a string
+      encoding the mapping from bytecode offsets to line numbers (for details
+      see the source code of the interpreter); :attr:`co_stacksize` is the
+      required stack size (including local variables); :attr:`co_flags` is an
+      integer encoding a number of flags for the interpreter.
 
       .. index:: object: generator
 
diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst
index 2d5291102d2a..4c5a9bb0cdb9 100644
--- a/Doc/whatsnew/3.8.rst
+++ b/Doc/whatsnew/3.8.rst
@@ -1177,8 +1177,10 @@ Changes in the Python API
 
 * :class:`types.CodeType` has a new parameter in the second position of the
   constructor (*posonlyargcount*) to support positional-only arguments defined
-  in :pep:`570`. A new ``replace()`` method of :class:`types.CodeType` can be
-  used to make the code future-proof.
+  in :pep:`570`. The first argument (*argcount*) now represents the total
+  number of positional arguments (including positional-only arguments). A new
+  ``replace()`` method of :class:`types.CodeType` can be used to make the code
+  future-proof.
 
 
 Changes in the C API
diff --git a/Lib/inspect.py b/Lib/inspect.py
index ca81a24f0641..91d209dc64bc 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -1037,15 +1037,11 @@ def getargs(co):
 
     names = co.co_varnames
     nargs = co.co_argcount
-    nposonlyargs = co.co_posonlyargcount
     nkwargs = co.co_kwonlyargcount
-    nposargs = nargs + nposonlyargs
-    posonlyargs = list(names[:nposonlyargs])
-    args = list(names[nposonlyargs:nposonlyargs+nargs])
-    kwonlyargs = list(names[nposargs:nposargs+nkwargs])
+    args = list(names[:nargs])
+    kwonlyargs = list(names[nargs:nargs+nkwargs])
     step = 0
 
-    nargs += nposonlyargs
     nargs += nkwargs
     varargs = None
     if co.co_flags & CO_VARARGS:
@@ -1054,7 +1050,7 @@ def getargs(co):
     varkw = None
     if co.co_flags & CO_VARKEYWORDS:
         varkw = co.co_varnames[nargs]
-    return Arguments(posonlyargs + args + kwonlyargs, varargs, varkw)
+    return Arguments(args + kwonlyargs, varargs, varkw)
 
 ArgSpec = namedtuple('ArgSpec', 'args varargs keywords defaults')
 
@@ -2136,11 +2132,9 @@ def _signature_from_function(cls, func, skip_bound_arg=True):
     pos_count = func_code.co_argcount
     arg_names = func_code.co_varnames
     posonly_count = func_code.co_posonlyargcount
-    positional_count = posonly_count + pos_count
-    positional_only = tuple(arg_names[:posonly_count])
-    positional = tuple(arg_names[posonly_count:positional_count])
+    positional = arg_names[:pos_count]
     keyword_only_count = func_code.co_kwonlyargcount
-    keyword_only = arg_names[positional_count:(positional_count + keyword_only_count)]
+    keyword_only = arg_names[pos_count:pos_count + keyword_only_count]
     annotations = func.__annotations__
     defaults = func.__defaults__
     kwdefaults = func.__kwdefaults__
@@ -2152,13 +2146,11 @@ def _signature_from_function(cls, func, skip_bound_arg=True):
 
     parameters = []
 
-    non_default_count = positional_count - pos_default_count
-    all_positional = positional_only + positional
-
+    non_default_count = pos_count - pos_default_count
     posonly_left = posonly_count
 
     # Non-keyword-only parameters w/o defaults.
-    for name in all_positional[:non_default_count]:
+    for name in positional[:non_default_count]:
         kind = _POSITIONAL_ONLY if posonly_left else _POSITIONAL_OR_KEYWORD
         annotation = annotations.get(name, _empty)
         parameters.append(Parameter(name, annotation=annotation,
@@ -2167,7 +2159,7 @@ def _signature_from_function(cls, func, skip_bound_arg=True):
             posonly_left -= 1
 
     # ... w/ defaults.
-    for offset, name in enumerate(all_positional[non_default_count:]):
+    for offset, name in enumerate(positional[non_default_count:]):
         kind = _POSITIONAL_ONLY if posonly_left else _POSITIONAL_OR_KEYWORD
         annotation = annotations.get(name, _empty)
         parameters.append(Parameter(name, annotation=annotation,
@@ -2178,7 +2170,7 @@ def _signature_from_function(cls, func, skip_bound_arg=True):
 
     # *args
     if func_code.co_flags & CO_VARARGS:
-        name = arg_names[positional_count + keyword_only_count]
+        name = arg_names[pos_count + keyword_only_count]
         annotation = annotations.get(name, _empty)
         parameters.append(Parameter(name, annotation=annotation,
                                     kind=_VAR_POSITIONAL))
@@ -2195,7 +2187,7 @@ def _signature_from_function(cls, func, skip_bound_arg=True):
                                     default=default))
     # **kwargs
     if func_code.co_flags & CO_VARKEYWORDS:
-        index = positional_count + keyword_only_count
+        index = pos_count + keyword_only_count
         if func_code.co_flags & CO_VARARGS:
             index += 1
 
diff --git a/Lib/pdb.py b/Lib/pdb.py
index 13068ce27bd1..0e7609e43d4e 100755
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -1132,7 +1132,7 @@ def do_args(self, arg):
         """
         co = self.curframe.f_code
         dict = self.curframe_locals
-        n = co.co_argcount + co.co_posonlyargcount + co.co_kwonlyargcount
+        n = co.co_argcount + co.co_kwonlyargcount
         if co.co_flags & inspect.CO_VARARGS: n = n+1
         if co.co_flags & inspect.CO_VARKEYWORDS: n = n+1
         for i in range(n):
diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py
index 91008c04f7fd..0d80af44d9f1 100644
--- a/Lib/test/test_code.py
+++ b/Lib/test/test_code.py
@@ -112,7 +112,7 @@
 
 >>> dump(posonly_args.__code__)
 name: posonly_args
-argcount: 1
+argcount: 3
 posonlyargcount: 2
 kwonlyargcount: 0
 names: ()
diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py
index 1561021f5bc7..652af45d55ad 100644
--- a/Lib/test/test_dis.py
+++ b/Lib/test/test_dis.py
@@ -640,7 +640,7 @@ def f(c=c):
 code_info_tricky = """\
 Name:              tricky
 Filename:          (.*)
-Argument count:    3
+Argument count:    5
 Positional-only arguments: 2
 Kw-only arguments: 3
 Number of locals:  10
diff --git a/Lib/test/test_positional_only_arg.py b/Lib/test/test_positional_only_arg.py
index 0aaad84cb3bf..59b0b8fb5562 100644
--- a/Lib/test/test_positional_only_arg.py
+++ b/Lib/test/test_positional_only_arg.py
@@ -100,14 +100,14 @@ def test_pos_only_definition(self):
         def f(a, b, c, /, d, e=1, *, f, g=2):
             pass
 
-        self.assertEqual(2, f.__code__.co_argcount)  # 2 "standard args"
+        self.assertEqual(5, f.__code__.co_argcount)  # 3 posonly + 2 "standard args"
         self.assertEqual(3, f.__code__.co_posonlyargcount)
         self.assertEqual((1,), f.__defaults__)
 
         def f(a, b, c=1, /, d=2, e=3, *, f, g=4):
             pass
 
-        self.assertEqual(2, f.__code__.co_argcount)  # 2 "standard args"
+        self.assertEqual(5, f.__code__.co_argcount)  # 3 posonly + 2 "standard args"
         self.assertEqual(3, f.__code__.co_posonlyargcount)
         self.assertEqual((1, 2, 3), f.__defaults__)
 
diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-06-01-16-53-41.bpo-37122.dZ3-NY.rst b/Misc/NEWS.d/next/Core and Builtins/2019-06-01-16-53-41.bpo-37122.dZ3-NY.rst
new file mode 100644
index 000000000000..b9584b50e61d
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2019-06-01-16-53-41.bpo-37122.dZ3-NY.rst	
@@ -0,0 +1,5 @@
+Make the *co_argcount* attribute of code objects represent the total number
+of positional arguments (including positional-only arguments). The value of
+*co_posonlyargcount* can be used to distinguish which arguments are
+positional only, and the difference (*co_argcount* - *co_posonlyargcount*)
+is the number of positional-or-keyword arguments. Patch by Pablo Galindo.
diff --git a/Objects/call.c b/Objects/call.c
index acd1f26dcbb5..c0d14567e430 100644
--- a/Objects/call.c
+++ b/Objects/call.c
@@ -308,11 +308,11 @@ _PyFunction_FastCallDict(PyObject *func, PyObject *const *args, Py_ssize_t nargs
         (co->co_flags & ~PyCF_MASK) == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE))
     {
         /* Fast paths */
-        if (argdefs == NULL && co->co_argcount + co->co_posonlyargcount == nargs) {
+        if (argdefs == NULL && co->co_argcount == nargs) {
             return function_code_fastcall(co, args, nargs, globals);
         }
         else if (nargs == 0 && argdefs != NULL
-                 && co->co_argcount + co->co_posonlyargcount == PyTuple_GET_SIZE(argdefs)) {
+                 && co->co_argcount == PyTuple_GET_SIZE(argdefs)) {
             /* function called with no arguments, but all parameters have
                a default value: use default values as arguments .*/
             args = _PyTuple_ITEMS(argdefs);
@@ -396,11 +396,11 @@ _PyFunction_Vectorcall(PyObject *func, PyObject* const* stack,
     if (co->co_kwonlyargcount == 0 && nkwargs == 0 &&
         (co->co_flags & ~PyCF_MASK) == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE))
     {
-        if (argdefs == NULL && co->co_argcount + co->co_posonlyargcount== nargs) {
+        if (argdefs == NULL && co->co_argcount == nargs) {
             return function_code_fastcall(co, stack, nargs, globals);
         }
         else if (nargs == 0 && argdefs != NULL
-                 && co->co_argcount + co->co_posonlyargcount == PyTuple_GET_SIZE(argdefs)) {
+                 && co->co_argcount == PyTuple_GET_SIZE(argdefs)) {
             /* function called with no arguments, but all parameters have
                a default value: use default values as arguments .*/
             stack = _PyTuple_ITEMS(argdefs);
diff --git a/Objects/codeobject.c b/Objects/codeobject.c
index 886ce4194438..bf68e54f42ec 100644
--- a/Objects/codeobject.c
+++ b/Objects/codeobject.c
@@ -114,8 +114,9 @@ PyCode_New(int argcount, int posonlyargcount, int kwonlyargcount,
     Py_ssize_t i, n_cellvars, n_varnames, total_args;
 
     /* Check argument types */
-    if (argcount < 0 || posonlyargcount < 0 || kwonlyargcount < 0 ||
-        nlocals < 0 || stacksize < 0 || flags < 0 ||
+    if (argcount < posonlyargcount || posonlyargcount < 0 ||
+        kwonlyargcount < 0 || nlocals < 0 ||
+        stacksize < 0 || flags < 0 ||
         code == NULL || !PyBytes_Check(code) ||
         consts == NULL || !PyTuple_Check(consts) ||
         names == NULL || !PyTuple_Check(names) ||
@@ -152,11 +153,9 @@ PyCode_New(int argcount, int posonlyargcount, int kwonlyargcount,
     }
 
     n_varnames = PyTuple_GET_SIZE(varnames);
-    if (posonlyargcount + argcount <= n_varnames
-        && kwonlyargcount <= n_varnames) {
+    if (argcount <= n_varnames && kwonlyargcount <= n_varnames) {
         /* Never overflows. */
-        total_args = (Py_ssize_t)posonlyargcount + (Py_ssize_t)argcount
-                      + (Py_ssize_t)kwonlyargcount +
+        total_args = (Py_ssize_t)argcount + (Py_ssize_t)kwonlyargcount +
                       ((flags & CO_VARARGS) != 0) + ((flags & CO_VARKEYWORDS) != 0);
     }
     else {
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index da249b569ad2..b6d925c1442e 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -7807,7 +7807,7 @@ super_init(PyObject *self, PyObject *args, PyObject *kwds)
                             "super(): no code object");
             return -1;
         }
-        if (co->co_posonlyargcount + co->co_argcount == 0) {
+        if (co->co_argcount == 0) {
             PyErr_SetString(PyExc_RuntimeError,
                             "super(): no arguments");
             return -1;
diff --git a/Python/ceval.c b/Python/ceval.c
index f9ff4e09f17e..d9a71e942153 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -3757,10 +3757,10 @@ missing_arguments(PyThreadState *tstate, PyCodeObject *co,
         return;
     if (positional) {
         start = 0;
-        end = co->co_posonlyargcount + co->co_argcount - defcount;
+        end = co->co_argcount - defcount;
     }
     else {
-        start = co->co_posonlyargcount + co->co_argcount;
+        start = co->co_argcount;
         end = start + co->co_kwonlyargcount;
     }
     for (i = start; i < end; i++) {
@@ -3788,25 +3788,23 @@ too_many_positional(PyThreadState *tstate, PyCodeObject *co,
     Py_ssize_t kwonly_given = 0;
     Py_ssize_t i;
     PyObject *sig, *kwonly_sig;
-    Py_ssize_t co_posonlyargcount = co->co_posonlyargcount;
     Py_ssize_t co_argcount = co->co_argcount;
-    Py_ssize_t total_positional = co_argcount + co_posonlyargcount;
 
     assert((co->co_flags & CO_VARARGS) == 0);
     /* Count missing keyword-only args. */
-    for (i = total_positional; i < total_positional + co->co_kwonlyargcount; i++) {
+    for (i = co_argcount; i < co_argcount + co->co_kwonlyargcount; i++) {
         if (GETLOCAL(i) != NULL) {
             kwonly_given++;
         }
     }
     if (defcount) {
-        Py_ssize_t atleast = total_positional - defcount;
+        Py_ssize_t atleast = co_argcount - defcount;
         plural = 1;
-        sig = PyUnicode_FromFormat("from %zd to %zd", atleast, total_positional);
+        sig = PyUnicode_FromFormat("from %zd to %zd", atleast, co_argcount);
     }
     else {
-        plural = (total_positional != 1);
-        sig = PyUnicode_FromFormat("%zd", total_positional);
+        plural = (co_argcount != 1);
+        sig = PyUnicode_FromFormat("%zd", co_argcount);
     }
     if (sig == NULL)
         return;
@@ -3917,7 +3915,7 @@ _PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
     PyObject *retval = NULL;
     PyObject **fastlocals, **freevars;
     PyObject *x, *u;
-    const Py_ssize_t total_args = co->co_argcount + co->co_kwonlyargcount + co->co_posonlyargcount;
+    const Py_ssize_t total_args = co->co_argcount + co->co_kwonlyargcount;
     Py_ssize_t i, j, n;
     PyObject *kwdict;
 
@@ -3953,9 +3951,9 @@ _PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
         kwdict = NULL;
     }
 
-    /* Copy positional only arguments into local variables */
-    if (argcount > co->co_argcount + co->co_posonlyargcount) {
-        n = co->co_posonlyargcount;
+    /* Copy all positional arguments into local variables */
+    if (argcount > co->co_argcount) {
+        n = co->co_argcount;
     }
     else {
         n = argcount;
@@ -3966,20 +3964,6 @@ _PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
         SETLOCAL(j, x);
     }
 
-
-    /* Copy positional arguments into local variables */
-    if (argcount > co->co_argcount + co->co_posonlyargcount) {
-        n += co->co_argcount;
-    }
-    else {
-        n = argcount;
-    }
-    for (i = j; i < n; i++) {
-        x = args[i];
-        Py_INCREF(x);
-        SETLOCAL(i, x);
-    }
-
     /* Pack other positional arguments into the *args argument */
     if (co->co_flags & CO_VARARGS) {
         u = _PyTuple_FromArray(args + n, argcount - n);
@@ -4059,14 +4043,14 @@ _PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
     }
 
     /* Check the number of positional arguments */
-    if ((argcount > co->co_argcount + co->co_posonlyargcount) && !(co->co_flags & CO_VARARGS)) {
+    if ((argcount > co->co_argcount) && !(co->co_flags & CO_VARARGS)) {
         too_many_positional(tstate, co, argcount, defcount, fastlocals);
         goto fail;
     }
 
     /* Add missing positional arguments (copy default values from defs) */
-    if (argcount < co->co_posonlyargcount + co->co_argcount) {
-        Py_ssize_t m = co->co_posonlyargcount + co->co_argcount - defcount;
+    if (argcount < co->co_argcount) {
+        Py_ssize_t m = co->co_argcount - defcount;
         Py_ssize_t missing = 0;
         for (i = argcount; i < m; i++) {
             if (GETLOCAL(i) == NULL) {
@@ -4093,7 +4077,7 @@ _PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
     /* Add missing keyword arguments (copy default values from kwdefs) */
     if (co->co_kwonlyargcount > 0) {
         Py_ssize_t missing = 0;
-        for (i = co->co_posonlyargcount + co->co_argcount; i < total_args; i++) {
+        for (i = co->co_argcount; i < total_args; i++) {
             PyObject *name;
             if (GETLOCAL(i) != NULL)
                 continue;
diff --git a/Python/compile.c b/Python/compile.c
index f6ec929b3ca4..9e4a2094ac9b 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -5764,7 +5764,7 @@ makecode(struct compiler *c, struct assembler *a)
     Py_ssize_t nlocals;
     int nlocals_int;
     int flags;
-    int argcount, posonlyargcount, kwonlyargcount, maxdepth;
+    int posorkeywordargcount, posonlyargcount, kwonlyargcount, maxdepth;
 
     consts = consts_dict_keys_inorder(c->u->u_consts);
     names = dict_keys_inorder(c->u->u_names, 0);
@@ -5808,15 +5808,15 @@ makecode(struct compiler *c, struct assembler *a)
         goto error;
     }
 
-    argcount = Py_SAFE_DOWNCAST(c->u->u_argcount, Py_ssize_t, int);
     posonlyargcount = Py_SAFE_DOWNCAST(c->u->u_posonlyargcount, Py_ssize_t, int);
+    posorkeywordargcount = Py_SAFE_DOWNCAST(c->u->u_argcount, Py_ssize_t, int);
     kwonlyargcount = Py_SAFE_DOWNCAST(c->u->u_kwonlyargcount, Py_ssize_t, int);
     maxdepth = stackdepth(c);
     if (maxdepth < 0) {
         goto error;
     }
-    co = PyCode_New(argcount, posonlyargcount, kwonlyargcount,
-                    nlocals_int, maxdepth, flags,
+    co = PyCode_New(posonlyargcount+posorkeywordargcount, posonlyargcount,
+                    kwonlyargcount, nlocals_int, maxdepth, flags,
                     bytecode, consts, names, varnames,
                     freevars, cellvars,
                     c->c_filename, c->u->u_name,



More information about the Python-checkins mailing list