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
Xavier Thompson
cython
Commits
5cfa3bd4
Commit
5cfa3bd4
authored
Aug 27, 2019
by
Jeroen Demeyer
Committed by
Stefan Behnel
Aug 27, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Use METH_FASTCALL on CPython >= 3.7 (GH-3021)
parent
30628523
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
402 additions
and
84 deletions
+402
-84
Cython/Compiler/ExprNodes.py
Cython/Compiler/ExprNodes.py
+1
-1
Cython/Compiler/FusedNode.py
Cython/Compiler/FusedNode.py
+4
-0
Cython/Compiler/Naming.py
Cython/Compiler/Naming.py
+1
-0
Cython/Compiler/Nodes.py
Cython/Compiler/Nodes.py
+129
-61
Cython/Compiler/ParseTreeTransforms.py
Cython/Compiler/ParseTreeTransforms.py
+5
-0
Cython/Compiler/TypeSlots.py
Cython/Compiler/TypeSlots.py
+33
-4
Cython/Utility/FunctionArguments.c
Cython/Utility/FunctionArguments.c
+125
-14
Cython/Utility/ModuleSetupCode.c
Cython/Utility/ModuleSetupCode.c
+38
-3
Cython/Utility/ObjectHandling.c
Cython/Utility/ObjectHandling.c
+1
-1
tests/run/fastcall.pyx
tests/run/fastcall.pyx
+44
-0
tests/run/str_subclass_kwargs.pyx
tests/run/str_subclass_kwargs.pyx
+21
-0
No files found.
Cython/Compiler/ExprNodes.py
View file @
5cfa3bd4
...
...
@@ -9625,8 +9625,8 @@ class LambdaNode(InnerFunctionNode):
self
.
lambda_name
=
self
.
def_node
.
lambda_name
=
env
.
next_id
(
'lambda'
)
self
.
def_node
.
no_assignment_synthesis
=
True
self
.
def_node
.
pymethdef_required
=
True
self
.
def_node
.
analyse_declarations
(
env
)
self
.
def_node
.
is_cyfunction
=
True
self
.
def_node
.
analyse_declarations
(
env
)
self
.
pymethdef_cname
=
self
.
def_node
.
entry
.
pymethdef_cname
env
.
add_lambda_def
(
self
.
def_node
)
...
...
Cython/Compiler/FusedNode.py
View file @
5cfa3bd4
...
...
@@ -827,6 +827,10 @@ class FusedCFuncDefNode(StatListNode):
else
:
nodes
=
self
.
nodes
# For the moment, fused functions do not support METH_FASTCALL
for
node
in
nodes
:
node
.
entry
.
signature
.
use_fastcall
=
False
signatures
=
[
StringEncoding
.
EncodedString
(
node
.
specialized_signature_string
)
for
node
in
nodes
]
keys
=
[
ExprNodes
.
StringNode
(
node
.
pos
,
value
=
sig
)
...
...
Cython/Compiler/Naming.py
View file @
5cfa3bd4
...
...
@@ -77,6 +77,7 @@ interned_prefixes = {
ctuple_type_prefix
=
pyrex_prefix
+
"ctuple_"
args_cname
=
pyrex_prefix
+
"args"
nargs_cname
=
pyrex_prefix
+
"nargs"
kwvalues_cname
=
pyrex_prefix
+
"kwvalues"
generator_cname
=
pyrex_prefix
+
"generator"
sent_value_cname
=
pyrex_prefix
+
"sent_value"
pykwdlist_cname
=
pyrex_prefix
+
"pyargnames"
...
...
Cython/Compiler/Nodes.py
View file @
5cfa3bd4
...
...
@@ -3106,6 +3106,33 @@ class DefNode(FuncDefNode):
if
arg
.
is_generic
and
(
arg
.
type
.
is_extension_type
or
arg
.
type
.
is_builtin_type
):
arg
.
needs_type_test
=
1
# Decide whether to use METH_FASTCALL
# 1. If we use METH_NOARGS or METH_O, keep that. We can only change
# METH_VARARGS to METH_FASTCALL
# 2. Special methods like __call__ always use the METH_VARGARGS
# calling convention
# 3. For the moment, CyFunctions do not support METH_FASTCALL
mf
=
sig
.
method_flags
()
if
(
mf
and
TypeSlots
.
method_varargs
in
mf
and
not
self
.
entry
.
is_special
and
not
self
.
is_cyfunction
):
# 4. If the function uses the full args tuple, it's more
# efficient to use METH_VARARGS. This happens when the function
# takes *args but no other positional arguments (apart from
# possibly self). We don't do the analogous check for keyword
# arguments since the kwargs dict is copied anyway.
if
self
.
star_arg
:
uses_args_tuple
=
True
for
arg
in
self
.
args
:
if
(
arg
.
is_generic
and
not
arg
.
kw_only
and
not
arg
.
is_self_arg
and
not
arg
.
is_type_arg
):
# Other positional argument
uses_args_tuple
=
False
else
:
uses_args_tuple
=
False
if
not
uses_args_tuple
:
sig
=
self
.
entry
.
signature
=
sig
.
with_fastcall
()
def
bad_signature
(
self
):
sig
=
self
.
entry
.
signature
expected_str
=
"%d"
%
sig
.
num_fixed_args
()
...
...
@@ -3466,9 +3493,16 @@ class DefNodeWrapper(FuncDefNode):
if
entry
.
scope
.
is_c_class_scope
and
entry
.
name
==
"__ipow__"
:
arg_code_list
.
append
(
"CYTHON_UNUSED PyObject *unused"
)
if
sig
.
has_generic_args
:
arg_code_list
.
append
(
"PyObject *%s, PyObject *%s"
%
(
Naming
.
args_cname
,
Naming
.
kwds_cname
))
varargs_args
=
"PyObject *%s, PyObject *%s"
%
(
Naming
.
args_cname
,
Naming
.
kwds_cname
)
if
sig
.
use_fastcall
:
fastcall_args
=
"PyObject *const *%s, Py_ssize_t %s, PyObject *%s"
%
(
Naming
.
args_cname
,
Naming
.
nargs_cname
,
Naming
.
kwds_cname
)
arg_code_list
.
append
(
"
\
n
#if CYTHON_METH_FASTCALL
\
n
%s
\
n
#else
\
n
%s
\
n
#endif
\
n
"
%
(
fastcall_args
,
varargs_args
))
else
:
arg_code_list
.
append
(
varargs_args
)
arg_code
=
", "
.
join
(
arg_code_list
)
# Prevent warning: unused function '__pyx_pw_5numpy_7ndarray_1__getbuffer__'
...
...
@@ -3531,8 +3565,20 @@ class DefNodeWrapper(FuncDefNode):
# Assign nargs variable as len(args), but avoid an "unused" warning in the few cases where we don't need it.
if
self
.
signature_has_generic_args
():
code
.
putln
(
"CYTHON_UNUSED const Py_ssize_t %s = PyTuple_GET_SIZE(%s);"
%
(
Naming
.
nargs_cname
,
Naming
.
args_cname
))
nargs_code
=
"CYTHON_UNUSED const Py_ssize_t %s = PyTuple_GET_SIZE(%s);"
%
(
Naming
.
nargs_cname
,
Naming
.
args_cname
)
if
self
.
signature
.
use_fastcall
:
code
.
putln
(
"#if !CYTHON_METH_FASTCALL"
)
code
.
putln
(
nargs_code
)
code
.
putln
(
"#endif"
)
else
:
code
.
putln
(
nargs_code
)
# Array containing the values of keyword arguments when using METH_FASTCALL.
code
.
globalstate
.
use_utility_code
(
UtilityCode
.
load_cached
(
"fastcall"
,
"FunctionArguments.c"
))
code
.
putln
(
'CYTHON_UNUSED PyObject *const *%s = __Pyx_KwValues_%s(%s, %s);'
%
(
Naming
.
kwvalues_cname
,
self
.
signature
.
fastvar
,
Naming
.
args_cname
,
Naming
.
nargs_cname
))
def
generate_argument_parsing_code
(
self
,
env
,
code
):
# Generate fast equivalent of PyArg_ParseTuple call for
...
...
@@ -3557,6 +3603,8 @@ class DefNodeWrapper(FuncDefNode):
elif
not
self
.
signature_has_nongeneric_args
():
# func(*args) or func(**kw) or func(*args, **kw)
# possibly with a "self" argument but no other non-star
# arguments
self
.
generate_stararg_copy_code
(
code
)
else
:
...
...
@@ -3603,8 +3651,8 @@ class DefNodeWrapper(FuncDefNode):
else
:
kwarg_check
=
"%s"
%
Naming
.
kwds_cname
else
:
kwarg_check
=
"unlikely(%s) &&
unlikely(PyDict_Size(%s) > 0
)"
%
(
Naming
.
kwds_cname
,
Naming
.
kwds_cname
)
kwarg_check
=
"unlikely(%s) &&
__Pyx_NumKwargs_%s(%s
)"
%
(
Naming
.
kwds_cname
,
self
.
signature
.
fastvar
,
Naming
.
kwds_cname
)
code
.
globalstate
.
use_utility_code
(
UtilityCode
.
load_cached
(
"KeywordStringCheck"
,
"FunctionArguments.c"
))
code
.
putln
(
...
...
@@ -3613,29 +3661,29 @@ class DefNodeWrapper(FuncDefNode):
bool
(
self
.
starstar_arg
),
self
.
error_value
()))
if
self
.
starstar_arg
and
self
.
starstar_arg
.
entry
.
cf_used
:
if
all
(
ref
.
node
.
allow_null
for
ref
in
self
.
starstar_arg
.
entry
.
cf_references
):
code
.
putln
(
"if (%s) {"
%
kwarg_check
)
code
.
putln
(
"%s = PyDict_Copy(%s); if (unlikely(!%s)) return %s;"
%
(
self
.
starstar_arg
.
entry
.
cname
,
Naming
.
kwds_cname
,
self
.
starstar_arg
.
entry
.
cname
,
self
.
error_value
()))
code
.
put_gotref
(
self
.
starstar_arg
.
entry
.
cname
)
code
.
putln
(
"} else {"
)
code
.
putln
(
"if (%s) {"
%
kwarg_check
)
code
.
putln
(
"%s = __Pyx_KwargsAsDict_%s(%s, %s);"
%
(
self
.
starstar_arg
.
entry
.
cname
,
self
.
signature
.
fastvar
,
Naming
.
kwds_cname
,
Naming
.
kwvalues_cname
))
code
.
putln
(
"if (unlikely(!%s)) return %s;"
%
(
self
.
starstar_arg
.
entry
.
cname
,
self
.
error_value
()))
code
.
put_gotref
(
self
.
starstar_arg
.
entry
.
cname
)
code
.
putln
(
"} else {"
)
allow_null
=
all
(
ref
.
node
.
allow_null
for
ref
in
self
.
starstar_arg
.
entry
.
cf_references
)
if
allow_null
:
code
.
putln
(
"%s = NULL;"
%
(
self
.
starstar_arg
.
entry
.
cname
,))
code
.
putln
(
"}"
)
self
.
starstar_arg
.
entry
.
xdecref_cleanup
=
1
else
:
code
.
put
(
"%s = (%s) ? PyDict_Copy(%s) : PyDict_New(); "
%
(
self
.
starstar_arg
.
entry
.
cname
,
Naming
.
kwds_cname
,
Naming
.
kwds_cname
))
code
.
putln
(
"%s = PyDict_New();"
%
(
self
.
starstar_arg
.
entry
.
cname
,))
code
.
putln
(
"if (unlikely(!%s)) return %s;"
%
(
self
.
starstar_arg
.
entry
.
cname
,
self
.
error_value
()))
self
.
starstar_arg
.
entry
.
xdecref_cleanup
=
0
code
.
put_gotref
(
self
.
starstar_arg
.
entry
.
cname
)
self
.
starstar_arg
.
entry
.
xdecref_cleanup
=
allow_null
code
.
putln
(
"}"
)
if
self
.
self_in_stararg
and
not
self
.
target
.
is_staticmethod
:
assert
not
self
.
signature
.
use_fastcall
# need to create a new tuple with 'self' inserted as first item
code
.
put
(
"%s = PyTuple_New(%s + 1); if (unlikely(!%s)) "
%
(
self
.
star_arg
.
entry
.
cname
,
...
...
@@ -3666,6 +3714,7 @@ class DefNodeWrapper(FuncDefNode):
code
.
funcstate
.
release_temp
(
temp
)
self
.
star_arg
.
entry
.
xdecref_cleanup
=
0
elif
self
.
star_arg
:
assert
not
self
.
signature
.
use_fastcall
code
.
put_incref
(
Naming
.
args_cname
,
py_object_type
)
code
.
putln
(
"%s = %s;"
%
(
self
.
star_arg
.
entry
.
cname
,
...
...
@@ -3673,6 +3722,9 @@ class DefNodeWrapper(FuncDefNode):
self
.
star_arg
.
entry
.
xdecref_cleanup
=
0
def
generate_tuple_and_keyword_parsing_code
(
self
,
args
,
success_label
,
code
):
code
.
globalstate
.
use_utility_code
(
UtilityCode
.
load_cached
(
"fastcall"
,
"FunctionArguments.c"
))
self_name_csafe
=
self
.
name
.
as_c_string_literal
()
argtuple_error_label
=
code
.
new_label
(
"argtuple_error"
)
...
...
@@ -3738,13 +3790,14 @@ class DefNodeWrapper(FuncDefNode):
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
)
kw_unpacking_condition
=
"%s && __Pyx_NumKwargs_%s(%s) > 0"
%
(
Naming
.
kwds_cname
,
self
.
signature
.
fastvar
,
Naming
.
kwds_cname
)
if
self
.
num_required_kw_args
>
0
:
kw_unpacking_condition
=
"likely(%s)"
%
kw_unpacking_condition
# --- optimised code when we receive keyword arguments
code
.
putln
(
"if (%s(%s)) {"
%
(
(
self
.
num_required_kw_args
>
0
)
and
"likely"
or
"unlikely"
,
kw_unpacking_condition
))
code
.
putln
(
"if (%s) {"
%
kw_unpacking_condition
)
if
accept_kwd_args
:
self
.
generate_keyword_unpacking_code
(
...
...
@@ -3756,10 +3809,11 @@ class DefNodeWrapper(FuncDefNode):
# 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'
%
(
code
.
putln
(
'if (likely(__Pyx_ParseOptionalKeywords(%s, %s, %s, %s, %s, %s
, %s
) < 0)) %s'
%
(
Naming
.
kwds_cname
,
Naming
.
kwvalues_cname
,
Naming
.
pykwdlist_cname
,
self
.
starstar_arg
and
self
.
starstar_arg
.
entry
.
cname
or
'0'
,
self
.
starstar_arg
.
entry
.
cname
if
self
.
starstar_arg
else
0
,
'values'
,
0
,
self_name_csafe
,
...
...
@@ -3803,7 +3857,8 @@ class DefNodeWrapper(FuncDefNode):
# parse the exact number of positional arguments from
# the args tuple
for
i
,
arg
in
enumerate
(
positional_args
):
code
.
putln
(
"values[%d] = PyTuple_GET_ITEM(%s, %d);"
%
(
i
,
Naming
.
args_cname
,
i
))
code
.
putln
(
"values[%d] = __Pyx_Arg_%s(%s, %d);"
%
(
i
,
self
.
signature
.
fastvar
,
Naming
.
args_cname
,
i
))
else
:
# parse the positional arguments from the variable length
# args tuple and reject illegal argument tuple sizes
...
...
@@ -3816,7 +3871,8 @@ class DefNodeWrapper(FuncDefNode):
if
i
!=
reversed_args
[
0
][
0
]:
code
.
putln
(
'CYTHON_FALLTHROUGH;'
)
code
.
put
(
'case %2d: '
%
(
i
+
1
))
code
.
putln
(
"values[%d] = PyTuple_GET_ITEM(%s, %d);"
%
(
i
,
Naming
.
args_cname
,
i
))
code
.
putln
(
"values[%d] = __Pyx_Arg_%s(%s, %d);"
%
(
i
,
self
.
signature
.
fastvar
,
Naming
.
args_cname
,
i
))
if
min_positional_args
==
0
:
code
.
putln
(
'CYTHON_FALLTHROUGH;'
)
code
.
put
(
'case 0: '
)
...
...
@@ -3888,23 +3944,26 @@ class DefNodeWrapper(FuncDefNode):
code
.
put_gotref
(
self
.
starstar_arg
.
entry
.
cname
)
if
self
.
star_arg
:
self
.
star_arg
.
entry
.
xdecref_cleanup
=
0
code
.
putln
(
'if (%s > %d) {'
%
(
Naming
.
nargs_cname
,
max_positional_args
))
code
.
putln
(
'%s = PyTuple_GetSlice(%s, %d, %s);'
%
(
self
.
star_arg
.
entry
.
cname
,
Naming
.
args_cname
,
max_positional_args
,
Naming
.
nargs_cname
))
code
.
putln
(
"if (unlikely(!%s)) {"
%
self
.
star_arg
.
entry
.
cname
)
if
self
.
starstar_arg
:
code
.
put_decref_clear
(
self
.
starstar_arg
.
entry
.
cname
,
py_object_type
)
code
.
put_finish_refcount_context
()
code
.
putln
(
'return %s;'
%
self
.
error_value
())
code
.
putln
(
'}'
)
code
.
put_gotref
(
self
.
star_arg
.
entry
.
cname
)
code
.
putln
(
'} else {'
)
code
.
put
(
"%s = %s; "
%
(
self
.
star_arg
.
entry
.
cname
,
Naming
.
empty_tuple
))
code
.
put_incref
(
Naming
.
empty_tuple
,
py_object_type
)
code
.
putln
(
'}'
)
if
max_positional_args
==
0
:
# If there are no positional arguments, use the args tuple
# directly
assert
not
self
.
signature
.
use_fastcall
code
.
put_incref
(
Naming
.
args_cname
,
py_object_type
)
code
.
putln
(
"%s = %s;"
%
(
self
.
star_arg
.
entry
.
cname
,
Naming
.
args_cname
))
else
:
# It is possible that this is a slice of "negative" length,
# as in args[5:3]. That's not a problem, the function below
# handles that efficiently and returns the empty tuple.
code
.
putln
(
'%s = __Pyx_ArgsSlice_%s(%s, %d, %s);'
%
(
self
.
star_arg
.
entry
.
cname
,
self
.
signature
.
fastvar
,
Naming
.
args_cname
,
max_positional_args
,
Naming
.
nargs_cname
))
code
.
putln
(
"if (unlikely(!%s)) {"
%
self
.
star_arg
.
entry
.
cname
)
if
self
.
starstar_arg
:
code
.
put_decref_clear
(
self
.
starstar_arg
.
entry
.
cname
,
py_object_type
)
code
.
put_finish_refcount_context
()
code
.
putln
(
'return %s;'
%
self
.
error_value
())
code
.
putln
(
'}'
)
code
.
put_gotref
(
self
.
star_arg
.
entry
.
cname
)
def
generate_argument_values_setup_code
(
self
,
args
,
code
):
max_args
=
len
(
args
)
...
...
@@ -3944,14 +4003,14 @@ class DefNodeWrapper(FuncDefNode):
for
i
in
range
(
max_positional_args
-
1
,
num_required_posonly_args
-
1
,
-
1
):
code
.
put
(
'case %2d: '
%
(
i
+
1
))
code
.
putln
(
"values[%d] =
PyTuple_GET_ITEM
(%s, %d);"
%
(
i
,
Naming
.
args_cname
,
i
))
code
.
putln
(
"values[%d] =
__Pyx_Arg_%s
(%s, %d);"
%
(
i
,
self
.
signature
.
fastvar
,
Naming
.
args_cname
,
i
))
code
.
putln
(
'CYTHON_FALLTHROUGH;'
)
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
(
"values[%d] =
__Pyx_Arg_%s
(%s, %d);"
%
(
i
,
self
.
signature
.
fastvar
,
Naming
.
args_cname
,
i
))
code
.
putln
(
'break;'
)
for
i
in
range
(
num_required_posonly_args
-
2
,
-
1
,
-
1
):
code
.
put
(
'case %2d: '
%
(
i
+
1
))
...
...
@@ -3979,7 +4038,8 @@ class DefNodeWrapper(FuncDefNode):
# arguments with values from the kw dict
self_name_csafe
=
self
.
name
.
as_c_string_literal
()
code
.
putln
(
'kw_args = PyDict_Size(%s);'
%
Naming
.
kwds_cname
)
code
.
putln
(
'kw_args = __Pyx_NumKwargs_%s(%s);'
%
(
self
.
signature
.
fastvar
,
Naming
.
kwds_cname
))
if
self
.
num_required_args
or
max_positional_args
>
0
:
last_required_arg
=
-
1
for
i
,
arg
in
enumerate
(
all_args
):
...
...
@@ -4004,13 +4064,15 @@ class DefNodeWrapper(FuncDefNode):
continue
code
.
putln
(
'if (kw_args > 0) {'
)
# don't overwrite default argument
code
.
putln
(
'PyObject* value = __Pyx_
PyDict_GetItemStr(
%s, %s);'
%
(
Naming
.
kwd
s_cname
,
pystring_cname
))
code
.
putln
(
'PyObject* value = __Pyx_
GetKwValue_%s(%s,
%s, %s);'
%
(
self
.
signature
.
fastvar
,
Naming
.
kwds_cname
,
Naming
.
kwvalue
s_cname
,
pystring_cname
))
code
.
putln
(
'if (value) { values[%d] = value; kw_args--; }'
%
i
)
code
.
putln
(
'else if (unlikely(PyErr_Occurred())) %s'
%
code
.
error_goto
(
self
.
pos
))
code
.
putln
(
'}'
)
else
:
code
.
putln
(
'if (likely((values[%d] = __Pyx_PyDict_GetItemStr(%s, %s)) != 0)) kw_args--;'
%
(
i
,
Naming
.
kwds_cname
,
pystring_cname
))
code
.
putln
(
'if (likely((values[%d] = __Pyx_GetKwValue_%s(%s, %s, %s)) != 0)) kw_args--;'
%
(
i
,
self
.
signature
.
fastvar
,
Naming
.
kwds_cname
,
Naming
.
kwvalues_cname
,
pystring_cname
))
code
.
putln
(
'else if (unlikely(PyErr_Occurred())) %s'
%
code
.
error_goto
(
self
.
pos
))
if
i
<
min_positional_args
:
if
i
==
0
:
# special case: we know arg 0 is missing
...
...
@@ -4088,8 +4150,9 @@ class DefNodeWrapper(FuncDefNode):
values_array
=
'values'
code
.
globalstate
.
use_utility_code
(
UtilityCode
.
load_cached
(
"ParseKeywords"
,
"FunctionArguments.c"
))
code
.
putln
(
'if (unlikely(__Pyx_ParseOptionalKeywords(%s, %s, %s, %s, %s, %s) < 0)) %s'
%
(
code
.
putln
(
'if (unlikely(__Pyx_ParseOptionalKeywords(%s, %s, %s, %s, %s, %s
, %s
) < 0)) %s'
%
(
Naming
.
kwds_cname
,
Naming
.
kwvalues_cname
,
Naming
.
pykwdlist_cname
,
self
.
starstar_arg
and
self
.
starstar_arg
.
entry
.
cname
or
'0'
,
values_array
,
...
...
@@ -4129,9 +4192,14 @@ class DefNodeWrapper(FuncDefNode):
else
:
code
.
putln
(
'if (kw_args == 1) {'
)
code
.
putln
(
'const Py_ssize_t index = %d;'
%
first_optional_arg
)
code
.
putln
(
'PyObject* value = __Pyx_PyDict_GetItemStr(%s, *%s[index%s]);'
%
(
Naming
.
kwds_cname
,
Naming
.
pykwdlist_cname
,
posonly_correction
))
code
.
putln
(
'PyObject* value = __Pyx_GetKwValue_%s(%s, %s, *%s[index%s]);'
%
(
self
.
signature
.
fastvar
,
Naming
.
kwds_cname
,
Naming
.
kwvalues_cname
,
Naming
.
pykwdlist_cname
,
posonly_correction
))
code
.
putln
(
'if (value) { values[index] = value; kw_args--; }'
)
code
.
putln
(
'else if (unlikely(PyErr_Occurred())) %s'
%
code
.
error_goto
(
self
.
pos
))
if
len
(
optional_args
)
>
1
:
code
.
putln
(
'}'
)
code
.
putln
(
'}'
)
...
...
Cython/Compiler/ParseTreeTransforms.py
View file @
5cfa3bd4
...
...
@@ -1800,6 +1800,8 @@ if VALUE is not None:
node
.
stats
.
insert
(
0
,
node
.
py_func
)
node
.
py_func
=
self
.
visit
(
node
.
py_func
)
node
.
update_fused_defnode_entry
(
env
)
# For the moment, fused functions do not support METH_FASTCALL
node
.
py_func
.
entry
.
signature
.
use_fastcall
=
False
pycfunc
=
ExprNodes
.
PyCFunctionNode
.
from_defnode
(
node
.
py_func
,
binding
=
True
)
pycfunc
=
ExprNodes
.
ProxyNode
(
pycfunc
.
coerce_to_temp
(
env
))
node
.
resulting_fused_function
=
pycfunc
...
...
@@ -1937,6 +1939,9 @@ if VALUE is not None:
rhs
.
binding
=
True
node
.
is_cyfunction
=
rhs
.
binding
if
rhs
.
binding
:
# For the moment, CyFunctions do not support METH_FASTCALL
node
.
entry
.
signature
.
use_fastcall
=
False
return
self
.
_create_assignment
(
node
,
rhs
,
env
)
def
_create_assignment
(
self
,
def_node
,
rhs
,
env
):
...
...
Cython/Compiler/TypeSlots.py
View file @
5cfa3bd4
...
...
@@ -9,6 +9,8 @@ from . import Naming
from
.
import
PyrexTypes
from
.Errors
import
error
import
copy
invisible
=
[
'__cinit__'
,
'__dealloc__'
,
'__richcmp__'
,
'__nonzero__'
,
'__bool__'
]
...
...
@@ -23,6 +25,7 @@ class Signature(object):
# fixed_arg_format string
# ret_format string
# error_value string
# use_fastcall boolean
#
# The formats are strings made up of the following
# characters:
...
...
@@ -86,6 +89,9 @@ class Signature(object):
'z'
:
"-1"
,
}
# Use METH_FASTCALL instead of METH_VARARGS
use_fastcall
=
False
def
__init__
(
self
,
arg_format
,
ret_format
,
nogil
=
False
):
self
.
has_dummy_arg
=
False
self
.
has_generic_args
=
False
...
...
@@ -159,15 +165,20 @@ class Signature(object):
if
self
.
has_dummy_arg
:
full_args
=
"O"
+
full_args
if
full_args
in
[
"O"
,
"T"
]:
if
self
.
has_generic_args
:
return
[
method_varargs
,
method_keywords
]
else
:
if
not
self
.
has_generic_args
:
return
[
method_noargs
]
elif
self
.
use_fastcall
:
return
[
method_fastcall
,
method_keywords
]
else
:
return
[
method_varargs
,
method_keywords
]
elif
full_args
in
[
"OO"
,
"TO"
]
and
not
self
.
has_generic_args
:
return
[
method_onearg
]
if
self
.
is_staticmethod
:
return
[
method_varargs
,
method_keywords
]
if
self
.
use_fastcall
:
return
[
method_fastcall
,
method_keywords
]
else
:
return
[
method_varargs
,
method_keywords
]
return
None
def
method_function_type
(
self
):
...
...
@@ -179,8 +190,25 @@ class Signature(object):
return
"PyCFunction"
if
m
==
method_varargs
:
return
"PyCFunction"
+
kw
if
m
==
method_fastcall
:
return
"__Pyx_PyCFunction_FastCall"
+
kw
return
None
def
with_fastcall
(
self
):
# Return a copy of this Signature with use_fastcall=True
sig
=
copy
.
copy
(
self
)
sig
.
use_fastcall
=
True
return
sig
@
property
def
fastvar
(
self
):
# Used to select variants of functions, one dealing with METH_VARARGS
# and one dealing with __Pyx_METH_FASTCALL
if
self
.
use_fastcall
:
return
"FASTCALL"
else
:
return
"VARARGS"
class
SlotDescriptor
(
object
):
# Abstract base class for type slot descriptors.
...
...
@@ -958,5 +986,6 @@ MethodSlot(descrdelfunc, "", "__delete__")
method_noargs
=
"METH_NOARGS"
method_onearg
=
"METH_O"
method_varargs
=
"METH_VARARGS"
method_fastcall
=
"__Pyx_METH_FASTCALL"
# Actually VARARGS on versions < 3.7
method_keywords
=
"METH_KEYWORDS"
method_coexist
=
"METH_COEXIST"
Cython/Utility/FunctionArguments.c
View file @
5cfa3bd4
...
...
@@ -117,16 +117,19 @@ static void __Pyx_RaiseMappingExpectedError(PyObject* arg) {
//////////////////// KeywordStringCheck.proto ////////////////////
static
int
__Pyx_CheckKeywordStrings
(
PyObject
*
kw
dict
,
const
char
*
function_name
,
int
kw_allowed
);
/*proto*/
static
int
__Pyx_CheckKeywordStrings
(
PyObject
*
kw
,
const
char
*
function_name
,
int
kw_allowed
);
/*proto*/
//////////////////// KeywordStringCheck ////////////////////
// __Pyx_CheckKeywordStrings raises an error if non-string keywords
// were passed to a function, or if any keywords were passed to a
// function that does not accept them.
// __Pyx_CheckKeywordStrings raises an error if non-string keywords
// were passed to a function, or if any keywords were passed to a
// function that does not accept them.
//
// The "kw" argument is either a dict (for METH_VARARGS) or a tuple
// (for METH_FASTCALL).
static
int
__Pyx_CheckKeywordStrings
(
PyObject
*
kw
dict
,
PyObject
*
kw
,
const
char
*
function_name
,
int
kw_allowed
)
{
...
...
@@ -134,18 +137,35 @@ static int __Pyx_CheckKeywordStrings(
Py_ssize_t
pos
=
0
;
#if CYTHON_COMPILING_IN_PYPY
/* PyPy appears to check keywords at call time, not at unpacking time => not much to do here */
if
(
!
kw_allowed
&&
PyDict_Next
(
kw
dict
,
&
pos
,
&
key
,
0
))
if
(
!
kw_allowed
&&
PyDict_Next
(
kw
,
&
pos
,
&
key
,
0
))
goto
invalid_keyword
;
return
1
;
#else
while
(
PyDict_Next
(
kwdict
,
&
pos
,
&
key
,
0
))
{
if
(
CYTHON_METH_FASTCALL
&&
likely
(
PyTuple_Check
(
kw
)))
{
if
(
unlikely
(
PyTuple_GET_SIZE
(
kw
)
==
0
))
return
1
;
if
(
!
kw_allowed
)
goto
invalid_keyword
;
#if PY_VERSION_HEX < 0x03090000
// On CPython >= 3.9, the FASTCALL protocol guarantees that keyword
// names are strings (see https://bugs.python.org/issue37540)
for
(
pos
=
0
;
pos
<
PyTuple_GET_SIZE
(
kw
);
pos
++
)
{
key
=
PyTuple_GET_ITEM
(
kw
,
pos
);
if
(
unlikely
(
!
PyUnicode_Check
(
key
)))
goto
invalid_keyword_type
;
}
#endif
return
1
;
}
while
(
PyDict_Next
(
kw
,
&
pos
,
&
key
,
0
))
{
#if PY_MAJOR_VERSION < 3
if
(
unlikely
(
!
PyString_Check
(
key
)))
#endif
if
(
unlikely
(
!
PyUnicode_Check
(
key
)))
goto
invalid_keyword_type
;
}
if
(
(
!
kw_allowed
)
&&
unlikely
(
key
))
if
(
!
kw_allowed
&&
unlikely
(
key
))
goto
invalid_keyword
;
return
1
;
invalid_keyword_type:
...
...
@@ -154,11 +174,12 @@ invalid_keyword_type:
return
0
;
#endif
invalid_keyword:
PyErr_Format
(
PyExc_TypeError
,
#if PY_MAJOR_VERSION < 3
PyErr_Format
(
PyExc_TypeError
,
"%.200s() got an unexpected keyword argument '%.200s'"
,
function_name
,
PyString_AsString
(
key
));
#else
PyErr_Format
(
PyExc_TypeError
,
"%s() got an unexpected keyword argument '%U'"
,
function_name
,
key
);
#endif
...
...
@@ -168,17 +189,22 @@ invalid_keyword:
//////////////////// ParseKeywords.proto ////////////////////
static
int
__Pyx_ParseOptionalKeywords
(
PyObject
*
kwds
,
PyObject
**
argnames
[],
\
PyObject
*
kwds2
,
PyObject
*
values
[],
Py_ssize_t
num_pos_args
,
\
static
int
__Pyx_ParseOptionalKeywords
(
PyObject
*
kwds
,
PyObject
*
const
*
kwvalues
,
PyObject
**
argnames
[],
PyObject
*
kwds2
,
PyObject
*
values
[],
Py_ssize_t
num_pos_args
,
const
char
*
function_name
);
/*proto*/
//////////////////// ParseKeywords ////////////////////
//@requires: RaiseDoubleKeywords
// __Pyx_ParseOptionalKeywords copies the optional/unknown keyword
// arguments from
the kwds dict into
kwds2. If kwds2 is NULL, unknown
// arguments from
kwds into the dict
kwds2. If kwds2 is NULL, unknown
// keywords will raise an invalid keyword error.
//
// When not using METH_FASTCALL, kwds is a dict and kwvalues is NULL.
// Otherwise, kwds is a tuple with keyword names and kwvalues is a C
// array with the corresponding values.
//
// Three kinds of errors are checked: 1) non-string keywords, 2)
// unexpected keywords and 3) overlap with positional arguments.
//
...
...
@@ -190,6 +216,7 @@ static int __Pyx_ParseOptionalKeywords(PyObject *kwds, PyObject **argnames[], \
static
int
__Pyx_ParseOptionalKeywords
(
PyObject
*
kwds
,
PyObject
*
const
*
kwvalues
,
PyObject
**
argnames
[],
PyObject
*
kwds2
,
PyObject
*
values
[],
...
...
@@ -200,8 +227,20 @@ static int __Pyx_ParseOptionalKeywords(
Py_ssize_t
pos
=
0
;
PyObject
***
name
;
PyObject
***
first_kw_arg
=
argnames
+
num_pos_args
;
int
kwds_is_tuple
=
CYTHON_METH_FASTCALL
&&
likely
(
PyTuple_Check
(
kwds
));
while
(
1
)
{
if
(
kwds_is_tuple
)
{
if
(
pos
>=
PyTuple_GET_SIZE
(
kwds
))
break
;
key
=
PyTuple_GET_ITEM
(
kwds
,
pos
);
value
=
kwvalues
[
pos
];
pos
++
;
}
else
{
if
(
!
PyDict_Next
(
kwds
,
&
pos
,
&
key
,
&
value
))
break
;
}
while
(
PyDict_Next
(
kwds
,
&
pos
,
&
key
,
&
value
))
{
name
=
first_kw_arg
;
while
(
*
name
&&
(
**
name
!=
key
))
name
++
;
if
(
*
name
)
{
...
...
@@ -284,11 +323,12 @@ invalid_keyword_type:
"%.200s() keywords must be strings"
,
function_name
);
goto
bad
;
invalid_keyword:
PyErr_Format
(
PyExc_TypeError
,
#if PY_MAJOR_VERSION < 3
PyErr_Format
(
PyExc_TypeError
,
"%.200s() got an unexpected keyword argument '%.200s'"
,
function_name
,
PyString_AsString
(
key
));
#else
PyErr_Format
(
PyExc_TypeError
,
"%s() got an unexpected keyword argument '%U'"
,
function_name
,
key
);
#endif
...
...
@@ -350,3 +390,74 @@ bad:
Py_XDECREF
(
iter
);
return
-
1
;
}
/////////////// fastcall.proto ///////////////
// We define various functions and macros with two variants:
//..._FASTCALL and ..._VARARGS
// The first is used when METH_FASTCALL is enabled and the second is used
// otherwise. If the Python implementation does not support METH_FASTCALL
// (because it's an old version of CPython or it's not CPython at all),
// then the ..._FASTCALL macros simply alias ..._VARARGS
#define __Pyx_Arg_VARARGS(args, i) PyTuple_GET_ITEM(args, i)
#define __Pyx_NumKwargs_VARARGS(kwds) PyDict_Size(kwds)
#define __Pyx_KwValues_VARARGS(args, nargs) NULL
#define __Pyx_GetKwValue_VARARGS(kw, kwvalues, s) __Pyx_PyDict_GetItemStrWithError(kw, s)
#define __Pyx_KwargsAsDict_VARARGS(kw, kwvalues) PyDict_Copy(kw)
#if CYTHON_METH_FASTCALL
#define __Pyx_Arg_FASTCALL(args, i) args[i]
#define __Pyx_NumKwargs_FASTCALL(kwds) PyTuple_GET_SIZE(kwds)
#define __Pyx_KwValues_FASTCALL(args, nargs) (&args[nargs])
static
CYTHON_INLINE
PyObject
*
__Pyx_GetKwValue_FASTCALL
(
PyObject
*
kwnames
,
PyObject
*
const
*
kwvalues
,
PyObject
*
s
);
#define __Pyx_KwargsAsDict_FASTCALL(kw, kwvalues) _PyStack_AsDict(kwvalues, kw)
#else
#define __Pyx_Arg_FASTCALL __Pyx_Arg_VARARGS
#define __Pyx_NumKwargs_FASTCALL __Pyx_NumKwargs_VARARGS
#define __Pyx_KwValues_FASTCALL __Pyx_KwValues_VARARGS
#define __Pyx_GetKwValue_FASTCALL __Pyx_GetKwValue_VARARGS
#define __Pyx_KwargsAsDict_FASTCALL __Pyx_KwargsAsDict_VARARGS
#endif
#if CYTHON_COMPILING_IN_CPYTHON
#define __Pyx_ArgsSlice_VARARGS(args, start, stop) __Pyx_PyTuple_FromArray(&__Pyx_Arg_VARARGS(args, start), stop - start)
#define __Pyx_ArgsSlice_FASTCALL(args, start, stop) __Pyx_PyTuple_FromArray(&__Pyx_Arg_FASTCALL(args, start), stop - start)
#else
/* Not CPython, so certainly no METH_FASTCALL support */
#define __Pyx_ArgsSlice_VARARGS(args, start, stop) PyTuple_GetSlice(args, start, stop)
#define __Pyx_ArgsSlice_FASTCALL(args, start, stop) PyTuple_GetSlice(args, start, stop)
#endif
/////////////// fastcall ///////////////
//@requires: ObjectHandling.c::TupleAndListFromArray
//@requires: StringTools.c::UnicodeEquals
#if CYTHON_METH_FASTCALL
// kwnames: tuple with names of keyword arguments
// kwvalues: C array with values of keyword arguments
// s: str with the keyword name to look for
static
CYTHON_INLINE
PyObject
*
__Pyx_GetKwValue_FASTCALL
(
PyObject
*
kwnames
,
PyObject
*
const
*
kwvalues
,
PyObject
*
s
)
{
// Search the kwnames array for s and return the corresponding value.
// We do two loops: a first one to compare pointers (which will find a
// match if the name in kwnames is interned, given that s is interned
// by Cython). A second loop compares the actual strings.
Py_ssize_t
i
,
n
=
PyTuple_GET_SIZE
(
kwnames
);
for
(
i
=
0
;
i
<
n
;
i
++
)
{
if
(
s
==
PyTuple_GET_ITEM
(
kwnames
,
i
))
return
kwvalues
[
i
];
}
for
(
i
=
0
;
i
<
n
;
i
++
)
{
int
eq
=
__Pyx_PyUnicode_Equals
(
s
,
PyTuple_GET_ITEM
(
kwnames
,
i
),
Py_EQ
);
if
(
unlikely
(
eq
!=
0
))
{
if
(
unlikely
(
eq
<
0
))
return
NULL
;
// error
return
kwvalues
[
i
];
}
}
return
NULL
;
// not found (no exception set)
}
#endif
Cython/Utility/ModuleSetupCode.c
View file @
5cfa3bd4
...
...
@@ -73,6 +73,8 @@
#define CYTHON_FAST_THREAD_STATE 0
#undef CYTHON_FAST_GIL
#define CYTHON_FAST_GIL 0
#undef CYTHON_METH_FASTCALL
#define CYTHON_METH_FASTCALL 0
#undef CYTHON_FAST_PYCALL
#define CYTHON_FAST_PYCALL 0
#undef CYTHON_PEP489_MULTI_PHASE_INIT
...
...
@@ -118,6 +120,8 @@
#define CYTHON_FAST_THREAD_STATE 0
#undef CYTHON_FAST_GIL
#define CYTHON_FAST_GIL 0
#undef CYTHON_METH_FASTCALL
#define CYTHON_METH_FASTCALL 0
#undef CYTHON_FAST_PYCALL
#define CYTHON_FAST_PYCALL 0
#undef CYTHON_PEP489_MULTI_PHASE_INIT
...
...
@@ -177,6 +181,11 @@
// Py3<3.5.2 does not support _PyThreadState_UncheckedGet().
#define CYTHON_FAST_GIL (PY_MAJOR_VERSION < 3 || PY_VERSION_HEX >= 0x03060000)
#endif
#ifndef CYTHON_METH_FASTCALL
/* CPython 3.6 introduced METH_FASTCALL but with slightly different
* semantics. It became stable starting from CPython 3.7 */
#define CYTHON_METH_FASTCALL (PY_VERSION_HEX >= 0x030700A1)
#endif
#ifndef CYTHON_FAST_PYCALL
#define CYTHON_FAST_PYCALL 1
#endif
...
...
@@ -450,6 +459,16 @@ class __Pyx_FakeReference {
#define __Pyx_PyCFunctionFastWithKeywords _PyCFunctionFastWithKeywords
#endif
#if CYTHON_METH_FASTCALL
#define __Pyx_METH_FASTCALL METH_FASTCALL
#define __Pyx_PyCFunction_FastCall __Pyx_PyCFunctionFast
#define __Pyx_PyCFunction_FastCallWithKeywords __Pyx_PyCFunctionFastWithKeywords
#else
#define __Pyx_METH_FASTCALL METH_VARARGS
#define __Pyx_PyCFunction_FastCall PyCFunction
#define __Pyx_PyCFunction_FastCallWithKeywords PyCFunctionWithKeywords
#endif
#if CYTHON_COMPILING_IN_PYPY && !defined(PyObject_Malloc)
#define PyObject_Malloc(s) PyMem_Malloc(s)
#define PyObject_Free(p) PyMem_Free(p)
...
...
@@ -531,10 +550,26 @@ static CYTHON_INLINE void * PyThread_tss_get(Py_tss_t *key) {
#define __Pyx_PyNumber_InPlaceDivide(x,y) PyNumber_InPlaceDivide(x,y)
#endif
#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x030500A1 && CYTHON_USE_UNICODE_INTERNALS
#define __Pyx_PyDict_GetItemStr(dict, name) _PyDict_GetItem_KnownHash(dict, name, ((PyASCIIObject *) name)->hash)
#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX > 0x030600B4 && CYTHON_USE_UNICODE_INTERNALS
// _PyDict_GetItem_KnownHash() exists since CPython 3.5, but it was
// dropping exceptions. Since 3.6, exceptions are kept.
#define __Pyx_PyDict_GetItemStrWithError(dict, name) _PyDict_GetItem_KnownHash(dict, name, ((PyASCIIObject *) name)->hash)
static
CYTHON_INLINE
PyObject
*
__Pyx_PyDict_GetItemStr
(
PyObject
*
dict
,
PyObject
*
name
)
{
PyObject
*
res
=
__Pyx_PyDict_GetItemStrWithError
(
dict
,
name
);
if
(
res
==
NULL
)
PyErr_Clear
();
return
res
;
}
#elif PY_MAJOR_VERSION >= 3
#define __Pyx_PyDict_GetItemStrWithError PyDict_GetItemWithError
#define __Pyx_PyDict_GetItemStr PyDict_GetItem
#else
#define __Pyx_PyDict_GetItemStr(dict, name) PyDict_GetItem(dict, name)
static
CYTHON_INLINE
PyObject
*
__Pyx_PyDict_GetItemStrWithError
(
PyObject
*
dict
,
PyObject
*
name
)
{
PyObject
*
res
=
PyObject_GetItem
(
dict
,
name
);
if
(
res
==
NULL
&&
PyErr_ExceptionMatches
(
PyExc_KeyError
))
PyErr_Clear
();
return
res
;
}
#define __Pyx_PyDict_GetItemStr PyDict_GetItem
#endif
/* new Py3.3 unicode type (PEP 393) */
...
...
Cython/Utility/ObjectHandling.c
View file @
5cfa3bd4
...
...
@@ -738,8 +738,8 @@ bad:
/////////////// TupleAndListFromArray.proto ///////////////
#if CYTHON_COMPILING_IN_CPYTHON
static
CYTHON_INLINE
PyObject
*
__Pyx_PyTuple_FromArray
(
PyObject
*
const
*
src
,
Py_ssize_t
n
);
static
CYTHON_INLINE
PyObject
*
__Pyx_PyList_FromArray
(
PyObject
*
const
*
src
,
Py_ssize_t
n
);
static
CYTHON_INLINE
PyObject
*
__Pyx_PyTuple_FromArray
(
PyObject
*
const
*
src
,
Py_ssize_t
n
);
#endif
/////////////// TupleAndListFromArray ///////////////
...
...
tests/run/fastcall.pyx
View file @
5cfa3bd4
# mode: run
# tag: METH_FASTCALL
cimport
cython
import
sys
import
struct
from
collections
import
deque
...
...
@@ -63,3 +65,45 @@ cdef class SelfCast:
"""
def
index_of_self
(
self
,
list
orbit
not
None
):
return
orbit
.
index
(
self
)
cdef
extern
from
*
:
int
PyCFunction_Check
(
op
)
int
PyCFunction_GET_FLAGS
(
op
)
def
has_fastcall
(
meth
):
"""
Given a builtin_function_or_method ``meth``, return whether it uses
``METH_FASTCALL``.
"""
if
not
PyCFunction_Check
(
meth
):
raise
TypeError
(
"not a builtin_function_or_method"
)
# Hardcode METH_FASTCALL constant equal to 0x80 for simplicity
return
bool
(
PyCFunction_GET_FLAGS
(
meth
)
&
0x80
)
def
assert_fastcall
(
meth
):
"""
Assert that ``meth`` uses ``METH_FASTCALL`` if the Python
implementation supports it.
"""
# getattr uses METH_FASTCALL on CPython >= 3.7
if
has_fastcall
(
getattr
)
and
not
has_fastcall
(
meth
):
raise
AssertionError
(
f"
{
meth
}
does not use METH_FASTCALL"
)
@
cython
.
binding
(
False
)
def
fastcall_function
(
**
kw
):
"""
>>> assert_fastcall(fastcall_function)
"""
return
kw
cdef
class
Dummy
:
@
cython
.
binding
(
False
)
def
fastcall_method
(
self
,
x
,
*
args
,
**
kw
):
"""
>>> assert_fastcall(Dummy().fastcall_method)
"""
return
tuple
(
args
)
+
tuple
(
kw
)
tests/run/str_subclass_kwargs.pyx
0 → 100644
View file @
5cfa3bd4
def
test_str_subclass_kwargs
(
k
=
None
):
"""
Test passing keywords with names that are not of type ``str``
but a subclass:
>>> class StrSubclass(str):
... pass
>>> class StrNoCompare(str):
... def __eq__(self, other):
... raise RuntimeError("do not compare me")
... def __hash__(self):
... return hash(str(self))
>>> kwargs = {StrSubclass('k'): 'value'}
>>> test_str_subclass_kwargs(**kwargs)
'value'
>>> kwargs = {StrNoCompare('k'): 'value'}
>>> test_str_subclass_kwargs(**kwargs)
Traceback (most recent call last):
RuntimeError: do not compare me
"""
return
k
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