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

This commit is contained in:
Pablo Galindo 2019-06-01 18:08:04 +01:00 committed by GitHub
parent 059b9ea5ac
commit cd74e66a8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 74 additions and 88 deletions

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -112,7 +112,7 @@ consts: ('None',)
>>> dump(posonly_args.__code__)
name: posonly_args
argcount: 1
argcount: 3
posonlyargcount: 2
kwonlyargcount: 0
names: ()

View File

@ -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

View File

@ -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__)

View File

@ -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.

View File

@ -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);

View File

@ -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 {

View File

@ -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;

View File

@ -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;

View File

@ -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,