Skip to content
This repository was archived by the owner on Feb 13, 2025. It is now read-only.

Commit 8c77b8c

Browse files
authored
bpo-36540: PEP 570 -- Implementation (pythonGH-12701)
This commit contains the implementation of PEP570: Python positional-only parameters. * Update Grammar/Grammar with new typedarglist and varargslist * Regenerate grammar files * Update and regenerate AST related files * Update code object * Update marshal.c * Update compiler and symtable * Regenerate importlib files * Update callable objects * Implement positional-only args logic in ceval.c * Regenerate frozen data * Update standard library to account for positional-only args * Add test file for positional-only args * Update other test files to account for positional-only args * Add News entry * Update inspect module and related tests
1 parent 99fcc61 commit 8c77b8c

38 files changed

+5767
-4706
lines changed

Grammar/Grammar

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,55 @@ async_funcdef: ASYNC funcdef
2222
funcdef: 'def' NAME parameters ['->' test] ':' [TYPE_COMMENT] func_body_suite
2323

2424
parameters: '(' [typedargslist] ')'
25-
typedargslist: (tfpdef ['=' test] (',' [TYPE_COMMENT] tfpdef ['=' test])* (TYPE_COMMENT | [',' [TYPE_COMMENT] [
25+
26+
# The following definition for typedarglist is equivalent to this set of rules:
27+
#
28+
# arguments = argument (',' [TYPE_COMMENT] argument)*
29+
# argument = tfpdef ['=' test]
30+
# kwargs = '**' tfpdef [','] [TYPE_COMMENT]
31+
# args = '*' [tfpdef]
32+
# kwonly_kwargs = (',' [TYPE_COMMENT] argument)* (TYPE_COMMENT | [',' [TYPE_COMMENT] [kwargs]])
33+
# args_kwonly_kwargs = args kwonly_kwargs | kwargs
34+
# poskeyword_args_kwonly_kwargs = arguments ( TYPE_COMMENT | [',' [TYPE_COMMENT] [args_kwonly_kwargs]])
35+
# typedargslist_no_posonly = poskeyword_args_kwonly_kwargs | args_kwonly_kwargs
36+
# typedarglist = (arguments ',' [TYPE_COMMENT] '/' [',' [[TYPE_COMMENT] typedargslist_no_posonly]])|(typedargslist_no_posonly)"
37+
#
38+
# It needs to be fully expanded to allow our LL(1) parser to work on it.
39+
40+
typedargslist: (
41+
(tfpdef ['=' test] (',' [TYPE_COMMENT] tfpdef ['=' test])* ',' [TYPE_COMMENT] '/' [',' [ [TYPE_COMMENT] tfpdef ['=' test] (
42+
',' [TYPE_COMMENT] tfpdef ['=' test])* (TYPE_COMMENT | [',' [TYPE_COMMENT] [
2643
'*' [tfpdef] (',' [TYPE_COMMENT] tfpdef ['=' test])* (TYPE_COMMENT | [',' [TYPE_COMMENT] ['**' tfpdef [','] [TYPE_COMMENT]]])
2744
| '**' tfpdef [','] [TYPE_COMMENT]]])
2845
| '*' [tfpdef] (',' [TYPE_COMMENT] tfpdef ['=' test])* (TYPE_COMMENT | [',' [TYPE_COMMENT] ['**' tfpdef [','] [TYPE_COMMENT]]])
46+
| '**' tfpdef [','] [TYPE_COMMENT]]] )
47+
| (tfpdef ['=' test] (',' [TYPE_COMMENT] tfpdef ['=' test])* (TYPE_COMMENT | [',' [TYPE_COMMENT] [
48+
'*' [tfpdef] (',' [TYPE_COMMENT] tfpdef ['=' test])* (TYPE_COMMENT | [',' [TYPE_COMMENT] ['**' tfpdef [','] [TYPE_COMMENT]]])
49+
| '**' tfpdef [','] [TYPE_COMMENT]]])
50+
| '*' [tfpdef] (',' [TYPE_COMMENT] tfpdef ['=' test])* (TYPE_COMMENT | [',' [TYPE_COMMENT] ['**' tfpdef [','] [TYPE_COMMENT]]])
2951
| '**' tfpdef [','] [TYPE_COMMENT])
52+
)
3053
tfpdef: NAME [':' test]
31-
varargslist: (vfpdef ['=' test] (',' vfpdef ['=' test])* [',' [
54+
55+
# The following definition for varargslist is equivalent to this set of rules:
56+
#
57+
# arguments = argument (',' argument )*
58+
# argument = vfpdef ['=' test]
59+
# kwargs = '**' vfpdef [',']
60+
# args = '*' [vfpdef]
61+
# kwonly_kwargs = (',' argument )* [',' [kwargs]]
62+
# args_kwonly_kwargs = args kwonly_kwargs | kwargs
63+
# poskeyword_args_kwonly_kwargs = arguments [',' [args_kwonly_kwargs]]
64+
# vararglist_no_posonly = poskeyword_args_kwonly_kwargs | args_kwonly_kwargs
65+
# varargslist = arguments ',' '/' [','[(vararglist_no_posonly)]] | (vararglist_no_posonly)
66+
#
67+
# It needs to be fully expanded to allow our LL(1) parser to work on it.
68+
69+
varargslist: vfpdef ['=' test ](',' vfpdef ['=' test])* ',' '/' [',' [ (vfpdef ['=' test] (',' vfpdef ['=' test])* [',' [
70+
'*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
71+
| '**' vfpdef [',']]]
72+
| '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
73+
| '**' vfpdef [',']) ]] | (vfpdef ['=' test] (',' vfpdef ['=' test])* [',' [
3274
'*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
3375
| '**' vfpdef [',']]]
3476
| '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]

Include/Python-ast.h

Lines changed: 6 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/code.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ typedef uint16_t _Py_CODEUNIT;
2121
typedef struct {
2222
PyObject_HEAD
2323
int co_argcount; /* #arguments, except *args */
24+
int co_posonlyargcount; /* #positional only arguments */
2425
int co_kwonlyargcount; /* #keyword only arguments */
2526
int co_nlocals; /* #local variables */
2627
int co_stacksize; /* #entries needed for evaluation stack */
@@ -102,7 +103,7 @@ PyAPI_DATA(PyTypeObject) PyCode_Type;
102103

103104
/* Public interface */
104105
PyAPI_FUNC(PyCodeObject *) PyCode_New(
105-
int, int, int, int, int, PyObject *, PyObject *,
106+
int, int, int, int, int, int, PyObject *, PyObject *,
106107
PyObject *, PyObject *, PyObject *, PyObject *,
107108
PyObject *, PyObject *, int, PyObject *);
108109
/* same as struct above */

Lib/ctypes/test/test_values.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ class struct_frozen(Structure):
8080
continue
8181
items.append((entry.name.decode("ascii"), entry.size))
8282

83-
expected = [("__hello__", 139),
84-
("__phello__", -139),
85-
("__phello__.spam", 139),
83+
expected = [("__hello__", 141),
84+
("__phello__", -141),
85+
("__phello__.spam", 141),
8686
]
8787
self.assertEqual(items, expected, "PyImport_FrozenModules example "
8888
"in Doc/library/ctypes.rst may be out of date")

Lib/dis.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ def _format_code_info(co):
157157
lines.append("Name: %s" % co.co_name)
158158
lines.append("Filename: %s" % co.co_filename)
159159
lines.append("Argument count: %s" % co.co_argcount)
160+
lines.append("Positional-only arguments: %s" % co.co_posonlyargcount)
160161
lines.append("Kw-only arguments: %s" % co.co_kwonlyargcount)
161162
lines.append("Number of locals: %s" % co.co_nlocals)
162163
lines.append("Stack size: %s" % co.co_stacksize)

Lib/importlib/_bootstrap_external.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ def _write_atomic(path, data, mode=0o666):
265265
# this might affected the first line number #32911)
266266
# Python 3.8a1 3400 (move frame block handling to compiler #17611)
267267
# Python 3.8a1 3401 (add END_ASYNC_FOR #33041)
268+
# Python 3.8a1 3410 (PEP570 Python Positional-Only Parameters #36540)
268269
#
269270
# MAGIC must change whenever the bytecode emitted by the compiler may no
270271
# longer be understood by older implementations of the eval loop (usually
@@ -273,7 +274,7 @@ def _write_atomic(path, data, mode=0o666):
273274
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
274275
# in PC/launcher.c must also be updated.
275276

276-
MAGIC_NUMBER = (3401).to_bytes(2, 'little') + b'\r\n'
277+
MAGIC_NUMBER = (3410).to_bytes(2, 'little') + b'\r\n'
277278
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
278279

279280
_PYCACHE = '__pycache__'

Lib/inspect.py

Lines changed: 59 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ def iscode(object):
272272
| 16=nested | 32=generator | 64=nofree | 128=coroutine
273273
| 256=iterable_coroutine | 512=async_generator
274274
co_freevars tuple of names of free variables
275+
co_posonlyargcount number of positional only arguments
275276
co_kwonlyargcount number of keyword only arguments (not including ** arg)
276277
co_lnotab encoded mapping of line numbers to bytecode indices
277278
co_name name with which this code object was defined
@@ -1031,26 +1032,20 @@ def getargs(co):
10311032
'args' is the list of argument names. Keyword-only arguments are
10321033
appended. 'varargs' and 'varkw' are the names of the * and **
10331034
arguments or None."""
1034-
args, varargs, kwonlyargs, varkw = _getfullargs(co)
1035-
return Arguments(args + kwonlyargs, varargs, varkw)
1036-
1037-
def _getfullargs(co):
1038-
"""Get information about the arguments accepted by a code object.
1039-
1040-
Four things are returned: (args, varargs, kwonlyargs, varkw), where
1041-
'args' and 'kwonlyargs' are lists of argument names, and 'varargs'
1042-
and 'varkw' are the names of the * and ** arguments or None."""
1043-
10441035
if not iscode(co):
10451036
raise TypeError('{!r} is not a code object'.format(co))
10461037

1047-
nargs = co.co_argcount
10481038
names = co.co_varnames
1039+
nargs = co.co_argcount
1040+
nposonlyargs = co.co_posonlyargcount
10491041
nkwargs = co.co_kwonlyargcount
1050-
args = list(names[:nargs])
1051-
kwonlyargs = list(names[nargs:nargs+nkwargs])
1042+
nposargs = nargs + nposonlyargs
1043+
posonlyargs = list(names[:nposonlyargs])
1044+
args = list(names[nposonlyargs:nposonlyargs+nargs])
1045+
kwonlyargs = list(names[nposargs:nposargs+nkwargs])
10521046
step = 0
10531047

1048+
nargs += nposonlyargs
10541049
nargs += nkwargs
10551050
varargs = None
10561051
if co.co_flags & CO_VARARGS:
@@ -1059,8 +1054,7 @@ def _getfullargs(co):
10591054
varkw = None
10601055
if co.co_flags & CO_VARKEYWORDS:
10611056
varkw = co.co_varnames[nargs]
1062-
return args, varargs, kwonlyargs, varkw
1063-
1057+
return Arguments(posonlyargs + args + kwonlyargs, varargs, varkw)
10641058

10651059
ArgSpec = namedtuple('ArgSpec', 'args varargs keywords defaults')
10661060

@@ -1087,15 +1081,16 @@ def getargspec(func):
10871081
warnings.warn("inspect.getargspec() is deprecated since Python 3.0, "
10881082
"use inspect.signature() or inspect.getfullargspec()",
10891083
DeprecationWarning, stacklevel=2)
1090-
args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = \
1091-
getfullargspec(func)
1092-
if kwonlyargs or ann:
1093-
raise ValueError("Function has keyword-only parameters or annotations"
1094-
", use getfullargspec() API which can support them")
1084+
args, varargs, varkw, defaults, posonlyargs, kwonlyargs, \
1085+
kwonlydefaults, ann = getfullargspec(func)
1086+
if posonlyargs or kwonlyargs or ann:
1087+
raise ValueError("Function has positional-only, keyword-only parameters"
1088+
" or annotations, use getfullargspec() API which can"
1089+
" support them")
10951090
return ArgSpec(args, varargs, varkw, defaults)
10961091

10971092
FullArgSpec = namedtuple('FullArgSpec',
1098-
'args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations')
1093+
'args, varargs, varkw, defaults, posonlyargs, kwonlyargs, kwonlydefaults, annotations')
10991094

11001095
def getfullargspec(func):
11011096
"""Get the names and default values of a callable object's parameters.
@@ -1145,6 +1140,7 @@ def getfullargspec(func):
11451140
args = []
11461141
varargs = None
11471142
varkw = None
1143+
posonlyargs = []
11481144
kwonlyargs = []
11491145
defaults = ()
11501146
annotations = {}
@@ -1159,7 +1155,9 @@ def getfullargspec(func):
11591155
name = param.name
11601156

11611157
if kind is _POSITIONAL_ONLY:
1162-
args.append(name)
1158+
posonlyargs.append(name)
1159+
if param.default is not param.empty:
1160+
defaults += (param.default,)
11631161
elif kind is _POSITIONAL_OR_KEYWORD:
11641162
args.append(name)
11651163
if param.default is not param.empty:
@@ -1185,7 +1183,7 @@ def getfullargspec(func):
11851183
defaults = None
11861184

11871185
return FullArgSpec(args, varargs, varkw, defaults,
1188-
kwonlyargs, kwdefaults, annotations)
1186+
posonlyargs, kwonlyargs, kwdefaults, annotations)
11891187

11901188

11911189
ArgInfo = namedtuple('ArgInfo', 'args varargs keywords locals')
@@ -1216,7 +1214,8 @@ def _formatannotation(annotation):
12161214
return _formatannotation
12171215

12181216
def formatargspec(args, varargs=None, varkw=None, defaults=None,
1219-
kwonlyargs=(), kwonlydefaults={}, annotations={},
1217+
posonlyargs=(), kwonlyargs=(), kwonlydefaults={},
1218+
annotations={},
12201219
formatarg=str,
12211220
formatvarargs=lambda name: '*' + name,
12221221
formatvarkw=lambda name: '**' + name,
@@ -1249,12 +1248,17 @@ def formatargandannotation(arg):
12491248
return result
12501249
specs = []
12511250
if defaults:
1252-
firstdefault = len(args) - len(defaults)
1253-
for i, arg in enumerate(args):
1251+
firstdefault = len(posonlyargs) + len(args) - len(defaults)
1252+
posonly_left = len(posonlyargs)
1253+
for i, arg in enumerate([*posonlyargs, *args]):
12541254
spec = formatargandannotation(arg)
12551255
if defaults and i >= firstdefault:
12561256
spec = spec + formatvalue(defaults[i - firstdefault])
12571257
specs.append(spec)
1258+
posonly_left -= 1
1259+
if posonlyargs and posonly_left == 0:
1260+
specs.append('/')
1261+
12581262
if varargs is not None:
12591263
specs.append(formatvarargs(formatargandannotation(varargs)))
12601264
else:
@@ -1342,7 +1346,8 @@ def getcallargs(*func_and_positional, **named):
13421346
func = func_and_positional[0]
13431347
positional = func_and_positional[1:]
13441348
spec = getfullargspec(func)
1345-
args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = spec
1349+
(args, varargs, varkw, defaults, posonlyargs,
1350+
kwonlyargs, kwonlydefaults, ann) = spec
13461351
f_name = func.__name__
13471352
arg2value = {}
13481353

@@ -1351,12 +1356,16 @@ def getcallargs(*func_and_positional, **named):
13511356
# implicit 'self' (or 'cls' for classmethods) argument
13521357
positional = (func.__self__,) + positional
13531358
num_pos = len(positional)
1359+
num_posonlyargs = len(posonlyargs)
13541360
num_args = len(args)
13551361
num_defaults = len(defaults) if defaults else 0
13561362

1363+
n = min(num_pos, num_posonlyargs)
1364+
for i in range(num_posonlyargs):
1365+
arg2value[posonlyargs[i]] = positional[i]
13571366
n = min(num_pos, num_args)
13581367
for i in range(n):
1359-
arg2value[args[i]] = positional[i]
1368+
arg2value[args[i]] = positional[num_posonlyargs+i]
13601369
if varargs:
13611370
arg2value[varargs] = tuple(positional[n:])
13621371
possible_kwargs = set(args + kwonlyargs)
@@ -2137,9 +2146,12 @@ def _signature_from_function(cls, func):
21372146
func_code = func.__code__
21382147
pos_count = func_code.co_argcount
21392148
arg_names = func_code.co_varnames
2140-
positional = tuple(arg_names[:pos_count])
2149+
posonly_count = func_code.co_posonlyargcount
2150+
positional_count = posonly_count + pos_count
2151+
positional_only = tuple(arg_names[:posonly_count])
2152+
positional = tuple(arg_names[posonly_count:positional_count])
21412153
keyword_only_count = func_code.co_kwonlyargcount
2142-
keyword_only = arg_names[pos_count:(pos_count + keyword_only_count)]
2154+
keyword_only = arg_names[positional_count:(positional_count + keyword_only_count)]
21432155
annotations = func.__annotations__
21442156
defaults = func.__defaults__
21452157
kwdefaults = func.__kwdefaults__
@@ -2151,23 +2163,33 @@ def _signature_from_function(cls, func):
21512163

21522164
parameters = []
21532165

2166+
non_default_count = positional_count - pos_default_count
2167+
all_positional = positional_only + positional
2168+
2169+
posonly_left = posonly_count
2170+
21542171
# Non-keyword-only parameters w/o defaults.
2155-
non_default_count = pos_count - pos_default_count
2156-
for name in positional[:non_default_count]:
2172+
for name in all_positional[:non_default_count]:
2173+
kind = _POSITIONAL_ONLY if posonly_left else _POSITIONAL_OR_KEYWORD
21572174
annotation = annotations.get(name, _empty)
21582175
parameters.append(Parameter(name, annotation=annotation,
2159-
kind=_POSITIONAL_OR_KEYWORD))
2176+
kind=kind))
2177+
if posonly_left:
2178+
posonly_left -= 1
21602179

21612180
# ... w/ defaults.
2162-
for offset, name in enumerate(positional[non_default_count:]):
2181+
for offset, name in enumerate(all_positional[non_default_count:]):
2182+
kind = _POSITIONAL_ONLY if posonly_left else _POSITIONAL_OR_KEYWORD
21632183
annotation = annotations.get(name, _empty)
21642184
parameters.append(Parameter(name, annotation=annotation,
2165-
kind=_POSITIONAL_OR_KEYWORD,
2185+
kind=kind,
21662186
default=defaults[offset]))
2187+
if posonly_left:
2188+
posonly_left -= 1
21672189

21682190
# *args
21692191
if func_code.co_flags & CO_VARARGS:
2170-
name = arg_names[pos_count + keyword_only_count]
2192+
name = arg_names[positional_count + keyword_only_count]
21712193
annotation = annotations.get(name, _empty)
21722194
parameters.append(Parameter(name, annotation=annotation,
21732195
kind=_VAR_POSITIONAL))
@@ -2184,7 +2206,7 @@ def _signature_from_function(cls, func):
21842206
default=default))
21852207
# **kwargs
21862208
if func_code.co_flags & CO_VARKEYWORDS:
2187-
index = pos_count + keyword_only_count
2209+
index = positional_count + keyword_only_count
21882210
if func_code.co_flags & CO_VARARGS:
21892211
index += 1
21902212

Lib/modulefinder.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -619,8 +619,9 @@ def replace_paths_in_code(self, co):
619619
if isinstance(consts[i], type(co)):
620620
consts[i] = self.replace_paths_in_code(consts[i])
621621

622-
return types.CodeType(co.co_argcount, co.co_kwonlyargcount,
623-
co.co_nlocals, co.co_stacksize, co.co_flags,
622+
return types.CodeType(co.co_argcount, co.co_posonlyargcount,
623+
co.co_kwonlyargcount, co.co_nlocals,
624+
co.co_stacksize, co.co_flags,
624625
co.co_code, tuple(consts), co.co_names,
625626
co.co_varnames, new_filename, co.co_name,
626627
co.co_firstlineno, co.co_lnotab, co.co_freevars,

Lib/test/inspect_fodder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# line 5
66

77
# line 7
8-
def spam(a, b, c, d=3, e=4, f=5, *g, **h):
8+
def spam(a, /, b, c, d=3, e=4, f=5, *g, **h):
99
eggs(b + d, c + f)
1010

1111
# line 11

0 commit comments

Comments
 (0)