Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cython
Commits
652f061d
Commit
652f061d
authored
May 14, 2019
by
Stefan Behnel
Committed by
GitHub
May 14, 2019
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #2949 from rjtobin/posonly_opt
Positional-only code generation optimization
parents
6e30c6ea
7b5ae8fc
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
140 additions
and
31 deletions
+140
-31
Cython/Compiler/Nodes.py
Cython/Compiler/Nodes.py
+73
-24
tests/run/posonly.pyx
tests/run/posonly.pyx
+67
-7
No files found.
Cython/Compiler/Nodes.py
View file @
652f061d
...
@@ -3661,6 +3661,7 @@ class DefNodeWrapper(FuncDefNode):
...
@@ -3661,6 +3661,7 @@ class DefNodeWrapper(FuncDefNode):
positional_args
=
[]
positional_args
=
[]
required_kw_only_args
=
[]
required_kw_only_args
=
[]
optional_kw_only_args
=
[]
optional_kw_only_args
=
[]
num_pos_only_args
=
0
for
arg
in
args
:
for
arg
in
args
:
if
arg
.
is_generic
:
if
arg
.
is_generic
:
if
arg
.
default
:
if
arg
.
default
:
...
@@ -3673,6 +3674,8 @@ class DefNodeWrapper(FuncDefNode):
...
@@ -3673,6 +3674,8 @@ class DefNodeWrapper(FuncDefNode):
required_kw_only_args
.
append
(
arg
)
required_kw_only_args
.
append
(
arg
)
elif
not
arg
.
is_self_arg
and
not
arg
.
is_type_arg
:
elif
not
arg
.
is_self_arg
and
not
arg
.
is_type_arg
:
positional_args
.
append
(
arg
)
positional_args
.
append
(
arg
)
if
arg
.
pos_only
:
num_pos_only_args
+=
1
# sort required kw-only args before optional ones to avoid special
# sort required kw-only args before optional ones to avoid special
# cases in the unpacking code
# cases in the unpacking code
...
@@ -3691,10 +3694,11 @@ class DefNodeWrapper(FuncDefNode):
...
@@ -3691,10 +3694,11 @@ class DefNodeWrapper(FuncDefNode):
code
.
putln
(
'{'
)
code
.
putln
(
'{'
)
all_args
=
tuple
(
positional_args
)
+
tuple
(
kw_only_args
)
all_args
=
tuple
(
positional_args
)
+
tuple
(
kw_only_args
)
non_posonly_args
=
[
arg
for
arg
in
all_args
if
not
arg
.
pos_only
]
code
.
putln
(
"static PyObject **%s[] = {%s};"
%
(
code
.
putln
(
"static PyObject **%s[] = {%s};"
%
(
Naming
.
pykwdlist_cname
,
Naming
.
pykwdlist_cname
,
','
.
join
([
'&%s'
%
code
.
intern_identifier
(
arg
.
name
)
','
.
join
([
'&%s'
%
code
.
intern_identifier
(
arg
.
name
)
for
arg
in
all_args
if
not
arg
.
pos_only
]
+
[
'0'
])))
for
arg
in
non_posonly_args
]
+
[
'0'
])))
# Before being converted and assigned to the target variables,
# Before being converted and assigned to the target variables,
# borrowed references to all unpacked argument values are
# borrowed references to all unpacked argument values are
...
@@ -3706,13 +3710,41 @@ class DefNodeWrapper(FuncDefNode):
...
@@ -3706,13 +3710,41 @@ class DefNodeWrapper(FuncDefNode):
# was passed for them.
# was passed for them.
self
.
generate_argument_values_setup_code
(
all_args
,
code
)
self
.
generate_argument_values_setup_code
(
all_args
,
code
)
# If all args are positional-only, we can raise an error
# straight away if we receive a non-empty kw-dict.
# This requires a PyDict_Size call. This call is wasteful
# for functions which do accept kw-args, so we do not generate
# the PyDict_Size call unless all args are positional-only.
accept_kwd_args
=
non_posonly_args
or
self
.
starstar_arg
if
accept_kwd_args
:
kw_unpacking_condition
=
Naming
.
kwds_cname
else
:
kw_unpacking_condition
=
"%s && PyDict_Size(%s) > 0"
%
(
Naming
.
kwds_cname
,
Naming
.
kwds_cname
)
# --- optimised code when we receive keyword arguments
# --- optimised code when we receive keyword arguments
code
.
putln
(
"if (%s(%s)) {"
%
(
code
.
putln
(
"if (%s(%s)) {"
%
(
(
self
.
num_required_kw_args
>
0
)
and
"likely"
or
"unlikely"
,
(
self
.
num_required_kw_args
>
0
)
and
"likely"
or
"unlikely"
,
Naming
.
kwds_cname
))
kw_unpacking_condition
))
if
accept_kwd_args
:
self
.
generate_keyword_unpacking_code
(
self
.
generate_keyword_unpacking_code
(
min_positional_args
,
max_positional_args
,
min_positional_args
,
max_positional_args
,
has_fixed_positional_count
,
has_kw_only_args
,
all_args
,
argtuple_error_label
,
code
)
has_fixed_positional_count
,
has_kw_only_args
,
all_args
,
argtuple_error_label
,
code
)
else
:
# Here we do not accept kw-args but we are passed a non-empty kw-dict.
# We call ParseOptionalKeywords which will raise an appropriate error if
# the kw-args dict passed is non-empty (which it will be, since kw_unpacking_condition is true)
code
.
globalstate
.
use_utility_code
(
UtilityCode
.
load_cached
(
"ParseKeywords"
,
"FunctionArguments.c"
))
code
.
putln
(
'if (likely(__Pyx_ParseOptionalKeywords(%s, %s, %s, %s, %s, "%s") < 0)) %s'
%
(
Naming
.
kwds_cname
,
Naming
.
pykwdlist_cname
,
self
.
starstar_arg
and
self
.
starstar_arg
.
entry
.
cname
or
'0'
,
'values'
,
0
,
self
.
name
,
code
.
error_goto
(
self
.
pos
)))
# --- optimised code when we do not receive any keyword arguments
# --- optimised code when we do not receive any keyword arguments
if
(
self
.
num_required_kw_args
and
min_positional_args
>
0
)
or
min_positional_args
==
max_positional_args
:
if
(
self
.
num_required_kw_args
and
min_positional_args
>
0
)
or
min_positional_args
==
max_positional_args
:
...
@@ -3877,18 +3909,42 @@ class DefNodeWrapper(FuncDefNode):
...
@@ -3877,18 +3909,42 @@ class DefNodeWrapper(FuncDefNode):
def
generate_keyword_unpacking_code
(
self
,
min_positional_args
,
max_positional_args
,
def
generate_keyword_unpacking_code
(
self
,
min_positional_args
,
max_positional_args
,
has_fixed_positional_count
,
has_fixed_positional_count
,
has_kw_only_args
,
all_args
,
argtuple_error_label
,
code
):
has_kw_only_args
,
all_args
,
argtuple_error_label
,
code
):
# First we count how many arguments must be passed as positional
num_required_posonly_args
=
num_pos_only_args
=
0
for
i
,
arg
in
enumerate
(
all_args
):
if
arg
.
pos_only
:
num_pos_only_args
+=
1
if
not
arg
.
default
:
num_required_posonly_args
+=
1
code
.
putln
(
'Py_ssize_t kw_args;'
)
code
.
putln
(
'Py_ssize_t kw_args;'
)
code
.
putln
(
'const Py_ssize_t pos_args = PyTuple_GET_SIZE(%s);'
%
Naming
.
args_cname
)
code
.
putln
(
'const Py_ssize_t pos_args = PyTuple_GET_SIZE(%s);'
%
Naming
.
args_cname
)
# copy the values from the args tuple and check that it's not too long
# copy the values from the args tuple and check that it's not too long
code
.
putln
(
'switch (pos_args) {'
)
code
.
putln
(
'switch (pos_args) {'
)
if
self
.
star_arg
:
if
self
.
star_arg
:
code
.
putln
(
'default:'
)
code
.
putln
(
'default:'
)
for
i
in
range
(
max_positional_args
-
1
,
-
1
,
-
1
):
for
i
in
range
(
max_positional_args
-
1
,
num_required_posonly_args
-
1
,
-
1
):
code
.
put
(
'case %2d: '
%
(
i
+
1
))
code
.
put
(
'case %2d: '
%
(
i
+
1
))
code
.
putln
(
"values[%d] = PyTuple_GET_ITEM(%s, %d);"
%
(
code
.
putln
(
"values[%d] = PyTuple_GET_ITEM(%s, %d);"
%
(
i
,
Naming
.
args_cname
,
i
))
i
,
Naming
.
args_cname
,
i
))
code
.
putln
(
'CYTHON_FALLTHROUGH;'
)
code
.
putln
(
'CYTHON_FALLTHROUGH;'
)
code
.
putln
(
'case 0: break;'
)
if
num_required_posonly_args
>
0
:
code
.
put
(
'case %2d: '
%
num_required_posonly_args
)
for
i
in
range
(
num_required_posonly_args
-
1
,
-
1
,
-
1
):
code
.
putln
(
"values[%d] = PyTuple_GET_ITEM(%s, %d);"
%
(
i
,
Naming
.
args_cname
,
i
))
code
.
putln
(
'break;'
)
for
i
in
range
(
num_required_posonly_args
-
2
,
-
1
,
-
1
):
code
.
put
(
'case %2d: '
%
(
i
+
1
))
code
.
putln
(
'CYTHON_FALLTHROUGH;'
)
code
.
put
(
'case 0: '
)
if
num_required_posonly_args
==
0
:
code
.
putln
(
'break;'
)
else
:
# catch-all for not enough pos-only args passed
code
.
put_goto
(
argtuple_error_label
)
if
not
self
.
star_arg
:
if
not
self
.
star_arg
:
code
.
put
(
'default: '
)
# more arguments than allowed
code
.
put
(
'default: '
)
# more arguments than allowed
code
.
put_goto
(
argtuple_error_label
)
code
.
put_goto
(
argtuple_error_label
)
...
@@ -3906,31 +3962,22 @@ class DefNodeWrapper(FuncDefNode):
...
@@ -3906,31 +3962,22 @@ class DefNodeWrapper(FuncDefNode):
code
.
putln
(
'kw_args = PyDict_Size(%s);'
%
Naming
.
kwds_cname
)
code
.
putln
(
'kw_args = PyDict_Size(%s);'
%
Naming
.
kwds_cname
)
if
self
.
num_required_args
or
max_positional_args
>
0
:
if
self
.
num_required_args
or
max_positional_args
>
0
:
last_required_arg
=
-
1
last_required_arg
=
-
1
last_required_posonly_arg
=
-
1
for
i
,
arg
in
enumerate
(
all_args
):
for
i
,
arg
in
enumerate
(
all_args
):
if
not
arg
.
default
:
if
not
arg
.
default
:
last_required_arg
=
i
last_required_arg
=
i
if
arg
.
pos_only
and
not
arg
.
default
:
last_required_posonly_arg
=
i
if
last_required_arg
<
max_positional_args
:
if
last_required_arg
<
max_positional_args
:
last_required_arg
=
max_positional_args
-
1
last_required_arg
=
max_positional_args
-
1
if
max_positional_args
>
0
:
if
max_positional_args
>
num_pos_only_args
:
code
.
putln
(
'switch (pos_args) {'
)
code
.
putln
(
'switch (pos_args) {'
)
for
i
,
arg
in
enumerate
(
all_args
[
:
last_required_arg
+
1
]
):
for
i
,
arg
in
enumerate
(
all_args
[
num_pos_only_args
:
last_required_arg
+
1
],
num_pos_only_args
):
if
max_positional_args
>
0
and
i
<=
max_positional_args
:
if
max_positional_args
>
num_pos_only_args
and
i
<=
max_positional_args
:
if
i
!=
0
:
if
i
!=
num_pos_only_args
:
code
.
putln
(
'CYTHON_FALLTHROUGH;'
)
code
.
putln
(
'CYTHON_FALLTHROUGH;'
)
if
self
.
star_arg
and
i
==
max_positional_args
:
if
self
.
star_arg
and
i
==
max_positional_args
:
code
.
putln
(
'default:'
)
code
.
putln
(
'default:'
)
else
:
else
:
code
.
putln
(
'case %2d:'
%
i
)
code
.
putln
(
'case %2d:'
%
i
)
pystring_cname
=
code
.
intern_identifier
(
arg
.
name
)
pystring_cname
=
code
.
intern_identifier
(
arg
.
name
)
if
arg
.
pos_only
:
if
i
==
last_required_posonly_arg
:
code
.
put_goto
(
argtuple_error_label
)
elif
i
==
last_required_arg
:
code
.
putln
(
'break;'
)
continue
if
arg
.
default
:
if
arg
.
default
:
if
arg
.
kw_only
:
if
arg
.
kw_only
:
# optional kw-only args are handled separately below
# optional kw-only args are handled separately below
...
@@ -3969,7 +4016,7 @@ class DefNodeWrapper(FuncDefNode):
...
@@ -3969,7 +4016,7 @@ class DefNodeWrapper(FuncDefNode):
self
.
name
,
pystring_cname
))
self
.
name
,
pystring_cname
))
code
.
putln
(
code
.
error_goto
(
self
.
pos
))
code
.
putln
(
code
.
error_goto
(
self
.
pos
))
code
.
putln
(
'}'
)
code
.
putln
(
'}'
)
if
max_positional_args
>
0
:
if
max_positional_args
>
num_pos_only_args
:
code
.
putln
(
'}'
)
code
.
putln
(
'}'
)
if
has_kw_only_args
:
if
has_kw_only_args
:
...
@@ -3989,7 +4036,6 @@ class DefNodeWrapper(FuncDefNode):
...
@@ -3989,7 +4036,6 @@ class DefNodeWrapper(FuncDefNode):
# ParseOptionalKeywords() needs to know how many of the arguments
# ParseOptionalKeywords() needs to know how many of the arguments
# that could be passed as keywords have in fact been passed as
# that could be passed as keywords have in fact been passed as
# positional args.
# positional args.
num_pos_only_args
=
self
.
num_posonly_args
if
num_pos_only_args
>
0
:
if
num_pos_only_args
>
0
:
# There are positional-only arguments which we don't want to count,
# There are positional-only arguments which we don't want to count,
# since they cannot be keyword arguments. Subtract the number of
# since they cannot be keyword arguments. Subtract the number of
...
@@ -4033,14 +4079,17 @@ class DefNodeWrapper(FuncDefNode):
...
@@ -4033,14 +4079,17 @@ class DefNodeWrapper(FuncDefNode):
def
generate_optional_kwonly_args_unpacking_code
(
self
,
all_args
,
code
):
def
generate_optional_kwonly_args_unpacking_code
(
self
,
all_args
,
code
):
optional_args
=
[]
optional_args
=
[]
first_optional_arg
=
-
1
first_optional_arg
=
-
1
num_posonly_args
=
0
for
i
,
arg
in
enumerate
(
all_args
):
for
i
,
arg
in
enumerate
(
all_args
):
if
arg
.
pos_only
:
num_posonly_args
+=
1
if
not
arg
.
kw_only
or
not
arg
.
default
:
if
not
arg
.
kw_only
or
not
arg
.
default
:
continue
continue
if
not
optional_args
:
if
not
optional_args
:
first_optional_arg
=
i
first_optional_arg
=
i
optional_args
.
append
(
arg
.
name
)
optional_args
.
append
(
arg
.
name
)
if
self
.
num_posonly_args
>
0
:
if
num_posonly_args
>
0
:
posonly_correction
=
'-%d'
%
self
.
num_posonly_args
posonly_correction
=
'-%d'
%
num_posonly_args
else
:
else
:
posonly_correction
=
''
posonly_correction
=
''
if
optional_args
:
if
optional_args
:
...
...
tests/run/posonly.pyx
View file @
652f061d
...
@@ -2,8 +2,6 @@
...
@@ -2,8 +2,6 @@
# mode: run
# mode: run
# tag: posonly
# tag: posonly
# TODO: remove posonly tag before merge
import
cython
import
cython
import
sys
import
sys
import
pickle
import
pickle
...
@@ -124,9 +122,9 @@ def test_use_positional_as_keyword2(a, /, b):
...
@@ -124,9 +122,9 @@ def test_use_positional_as_keyword2(a, /, b):
def
test_use_positional_as_keyword3
(
a
,
b
,
/
):
def
test_use_positional_as_keyword3
(
a
,
b
,
/
):
"""
"""
>>> test_use_positional_as_keyword3(1, 2)
>>> test_use_positional_as_keyword3(1, 2)
>>> test_use_positional_as_keyword3(a=1, b=2)
>>> test_use_positional_as_keyword3(a=1, b=2)
# doctest:+ELLIPSIS
Traceback (most recent call last):
Traceback (most recent call last):
TypeError: test_use_positional_as_keyword3()
takes exactly 2 positional arguments (0 given)
TypeError: test_use_positional_as_keyword3()
got an unexpected keyword argument '...'
"""
"""
def
test_positional_only_and_arg_invalid_calls
(
a
,
b
,
/
,
c
):
def
test_positional_only_and_arg_invalid_calls
(
a
,
b
,
/
,
c
):
...
@@ -266,7 +264,7 @@ class TestPosonlyMethods(object):
...
@@ -266,7 +264,7 @@ class TestPosonlyMethods(object):
Got type error
Got type error
>>> TestPosonlyMethods().f(1, b=2)
>>> TestPosonlyMethods().f(1, b=2)
Traceback (most recent call last):
Traceback (most recent call last):
TypeError: f()
takes exactly 3 positional arguments (2 given)
TypeError: f()
got an unexpected keyword argument 'b'
"""
"""
def
f
(
self
,
a
,
b
,
/
):
def
f
(
self
,
a
,
b
,
/
):
return
a
,
b
return
a
,
b
...
@@ -365,9 +363,9 @@ def test_serialization1(a, b, /):
...
@@ -365,9 +363,9 @@ def test_serialization1(a, b, /):
>>> unpickled_posonly = pickle.loads(pickled_posonly)
>>> unpickled_posonly = pickle.loads(pickled_posonly)
>>> unpickled_posonly(1, 2)
>>> unpickled_posonly(1, 2)
(1, 2)
(1, 2)
>>> unpickled_posonly(a=1, b=2)
>>> unpickled_posonly(a=1, b=2)
# doctest: +ELLIPSIS
Traceback (most recent call last):
Traceback (most recent call last):
TypeError: test_serialization1()
takes exactly 2 positional arguments (0 given)
TypeError: test_serialization1()
got an unexpected keyword argument '...'
"""
"""
return
(
a
,
b
)
return
(
a
,
b
)
...
@@ -496,3 +494,65 @@ def f_call_one_optional_kwd(a,/,*,b=2):
...
@@ -496,3 +494,65 @@ def f_call_one_optional_kwd(a,/,*,b=2):
(1, 3)
(1, 3)
"""
"""
return
(
a
,
b
)
return
(
a
,
b
)
def
f_call_posonly_stararg
(
a
,
/
,
*
args
):
"""
>>> f_call_posonly_stararg(1)
(1, ())
>>> f_call_posonly_stararg(1, 2, 3, 4)
(1, (2, 3, 4))
"""
return
(
a
,
args
)
def
f_call_posonly_kwarg
(
a
,
/
,
**
kw
):
"""
>>> f_call_posonly_kwarg(1)
(1, {})
>>> all_args = f_call_posonly_kwarg(1, b=2, c=3, d=4)
>>> all_args == (1, {'b': 2, 'c': 3, 'd': 4}) or all_args
True
"""
return
(
a
,
kw
)
def
f_call_posonly_stararg_kwarg
(
a
,
/
,
*
args
,
**
kw
):
"""
>>> f_call_posonly_stararg_kwarg(1)
(1, (), {})
>>> f_call_posonly_stararg_kwarg(1, 2)
(1, (2,), {})
>>> all_args = f_call_posonly_stararg_kwarg(1, b=3, c=4)
>>> all_args == (1, (), {'b': 3, 'c': 4}) or all_args
True
>>> all_args = f_call_posonly_stararg_kwarg(1, 2, b=3, c=4)
>>> all_args == (1, (2,), {'b': 3, 'c': 4}) or all_args
True
"""
return
(
a
,
args
,
kw
)
def
test_empty_kwargs
(
a
,
b
,
/
):
"""
>>> test_empty_kwargs(1, 2)
(1, 2)
>>> test_empty_kwargs(1, 2, **{})
(1, 2)
>>> test_empty_kwargs(1, 2, **{'c': 3})
Traceback (most recent call last):
TypeError: test_empty_kwargs() got an unexpected keyword argument 'c'
"""
return
(
a
,
b
)
cdef
class
TestExtensionClass
:
"""
>>> t = TestExtensionClass()
>>> t.f(1,2)
(1, 2, 3)
>>> t.f(1,2,4)
(1, 2, 4)
>>> t.f(1, 2, c=4)
(1, 2, 4)
>>> t.f(1, 2, 5, c=6)
Traceback (most recent call last):
TypeError: f() got multiple values for keyword argument 'c'
"""
def
f
(
self
,
a
,
b
,
/
,
c
=
3
):
return
(
a
,
b
,
c
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment