From cd74e66a8c420be675fd2fbf3fe708ac02ee9f21 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Sat, 1 Jun 2019 18:08:04 +0100 Subject: bpo-37122: Make co->co_argcount represent the total number of positonal arguments in the code object (GH-13726) --- Doc/c-api/code.rst | 2 + Doc/reference/datamodel.rst | 36 +++++++++-------- Doc/whatsnew/3.8.rst | 6 ++- Lib/inspect.py | 28 +++++-------- Lib/pdb.py | 2 +- Lib/test/test_code.py | 2 +- Lib/test/test_dis.py | 2 +- Lib/test/test_positional_only_arg.py | 4 +- .../2019-06-01-16-53-41.bpo-37122.dZ3-NY.rst | 5 +++ Objects/call.c | 8 ++-- Objects/codeobject.c | 11 +++--- Objects/typeobject.c | 2 +- Python/ceval.c | 46 +++++++--------------- Python/compile.c | 8 ++-- 14 files changed, 74 insertions(+), 88 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-06-01-16-53-41.bpo-37122.dZ3-NY.rst diff --git a/Doc/c-api/code.rst b/Doc/c-api/code.rst index 7aa91ee..92baa4c 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 c566dfd..44017d8 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 2d52911..4c5a9bb 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 ca81a24..91d209d 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 13068ce..0e7609e 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -1132,7 +1132,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): """ 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 91008c0..0d80af4 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -112,7 +112,7 @@ consts: ('None',) >>> 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 1561021..652af45 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -640,7 +640,7 @@ def tricky(a, b, /, x, y, z=True, *args, c, d, e=[], **kwds): 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 0aaad84..59b0b8f 100644 --- a/Lib/test/test_positional_only_arg.py +++ b/Lib/test/test_positional_only_arg.py @@ -100,14 +100,14 @@ class PositionalOnlyTestCase(unittest.TestCase): 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 0000000..b9584b5 --- /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 acd1f26..c0d1456 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 886ce41..bf68e54 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 da249b5..b6d925c 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 f9ff4e0..d9a71e9 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 f6ec929..9e4a209 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, -- cgit v0.12