Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Doc/c-api/code.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
36 changes: 19 additions & 17 deletions Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 4 additions & 2 deletions Doc/whatsnew/3.8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 10 additions & 18 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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')

Expand Down Expand Up @@ -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__
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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))
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@

>>> dump(posonly_args.__code__)
name: posonly_args
argcount: 1
argcount: 3
posonlyargcount: 2
kwonlyargcount: 0
names: ()
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_positional_only_arg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 4 additions & 4 deletions Objects/call.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
11 changes: 5 additions & 6 deletions Objects/codeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) ||
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
46 changes: 15 additions & 31 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand Down
Loading