Commit 3ee57173 authored by Stefan Behnel's avatar Stefan Behnel

partially implement PEP 448 for function calls only

parent 1ba47438
......@@ -15,6 +15,9 @@ Features added
* Tracing is supported in ``nogil`` functions/sections and module init code.
* PEP 448 (Additional Unpacking Generalizations) was implemented for function
calls.
* When generators are used in a Cython module and the module imports the
modules "inspect" and/or "asyncio", Cython enables interoperability by
patching these modules to recognise Cython's internal generator type.
......
......@@ -5450,8 +5450,9 @@ class AsTupleNode(ExprNode):
self.compile_time_value_error(e)
def analyse_types(self, env):
self.arg = self.arg.analyse_types(env)
self.arg = self.arg.coerce_to_pyobject(env)
self.arg = self.arg.analyse_types(env).coerce_to_pyobject(env)
if self.arg.type is tuple_type:
return self.arg.as_none_safe_node("'NoneType' object is not iterable")
self.type = tuple_type
self.is_temp = 1
return self
......@@ -5471,6 +5472,159 @@ class AsTupleNode(ExprNode):
code.put_gotref(self.py_result())
class KeywordArgsNode(ExprNode):
# Helper class for keyword arguments.
#
# keyword_args [DictNode or other ExprNode]
subexprs = ['keyword_args']
is_temp = 1
type = dict_type
reject_duplicates = True
def calculate_constant_result(self):
result = {}
reject_duplicates = self.reject_duplicates
for item in self.keyword_args:
if item.is_dict_literal:
# process items in order
items = ((key.constant_result, value.constant_result)
for key, value in item.key_value_pairs)
else:
items = item.constant_result.iteritems()
for key, value in items:
if reject_duplicates and key in result:
raise ValueError("duplicate keyword argument found: %s" % key)
result[key] = value
self.constant_result = result
def compile_time_value(self, denv):
result = {}
reject_duplicates = self.reject_duplicates
for item in self.keyword_args:
if item.is_dict_literal:
# process items in order
items = [(key.compile_time_value(denv), value.compile_time_value(denv))
for key, value in item.key_value_pairs]
else:
items = item.compile_time_value(denv).iteritems()
try:
for key, value in items:
if reject_duplicates and key in result:
raise ValueError("duplicate keyword argument found: %s" % key)
result[key] = value
except Exception, e:
self.compile_time_value_error(e)
return result
def type_dependencies(self, env):
return ()
def infer_type(self, env):
return dict_type
def analyse_types(self, env):
args = [
arg.analyse_types(env).coerce_to_pyobject(env).as_none_safe_node(
# FIXME: CPython's error message starts with the runtime function name
'argument after ** must be a mapping, not NoneType')
for arg in self.keyword_args
]
if len(args) == 1 and args[0].type is dict_type:
# strip this intermediate node and use the bare dict
arg = args[0]
if arg.is_name and arg.entry.is_arg and len(arg.entry.cf_assignments) == 1:
# passing **kwargs through to function call => allow NULL
arg.allow_null = True
return arg
self.keyword_args = args
return self
def may_be_none(self):
return False
gil_message = "Constructing Python dict"
def generate_evaluation_code(self, code):
code.mark_pos(self.pos)
self.allocate_temp_result(code)
if self.reject_duplicates and len(self.keyword_args) > 1:
code.globalstate.use_utility_code(
UtilityCode.load_cached("RaiseDoubleKeywords", "FunctionArguments.c"))
args = iter(self.keyword_args)
item = next(args)
item.generate_evaluation_code(code)
if item.type is not dict_type:
# CPython supports calling functions with non-dicts, so do we
code.putln('if (likely(PyDict_Check(%s))) {' %
item.py_result())
code.putln("%s = %s;" % (self.result(), item.py_result()))
item.generate_post_assignment_code(code)
if item.type is not dict_type:
code.putln('} else {')
code.putln("%s = PyObject_CallFunctionObjArgs((PyObject*)&PyDict_Type, %s, NULL); %s" % (
self.result(),
item.py_result(),
code.error_goto_if_null(self.result(), self.pos)))
code.put_gotref(self.py_result())
item.generate_disposal_code(code)
code.putln('}')
item.free_temps(code)
for item in args:
if item.is_dict_literal:
for arg in item.keyword_args:
arg.generate_evaluation_code(code)
if self.reject_duplicates:
code.putln("if (unlikely(PyDict_Contains(%s, %s))) {" % (
self.result(),
arg.key.py_result()))
# FIXME: find out function name at runtime!
code.putln('__Pyx_RaiseDoubleKeywordsError("function", %s); %s' % (
arg.key.py_result(),
code.error_goto(self.pos)))
code.putln("}")
code.put_error_if_neg(arg.key.pos, "PyDict_SetItem(%s, %s, %s)" % (
self.result(),
arg.key.py_result(),
arg.value.py_result()))
arg.generate_disposal_code(code)
arg.free_temps(code)
continue
item.generate_evaluation_code(code)
if self.reject_duplicates:
# merge mapping into kwdict one by one as we need to check for duplicates
code.globalstate.use_utility_code(
UtilityCode.load_cached("MergeKeywords", "FunctionArguments.c"))
code.put_error_if_neg(item.pos, "__Pyx_MergeKeywords(%s, %s)" % (self.result(), item.py_result()))
else:
# simple case, just add all entries
code.globalstate.use_utility_code(
UtilityCode.load_cached("RaiseMappingExpected", "FunctionArguments.c"))
code.putln("if (unlikely(PyDict_Update(%s, %s) < 0)) {" % (self.result(), item.py_result()))
code.putln("if (PyErr_ExceptionMatches(PyExc_AttributeError)) __Pyx_RaiseMappingExpected(%s);" % (
item.py_result()))
code.putln(code.error_goto(item.pos))
code.putln("}")
code.putln("}")
item.generate_disposal_code(code)
item.free_temps(code)
def annotate(self, code):
for item in self.keyword_args:
item.annotate(code)
class AttributeNode(ExprNode):
# obj.attribute
#
......@@ -7134,6 +7288,7 @@ class DictNode(ExprNode):
exclude_null_values = False
type = dict_type
is_dict_literal = True
reject_duplicates = False
obj_conversion_errors = []
......@@ -7223,23 +7378,38 @@ class DictNode(ExprNode):
# pairs are evaluated and used one at a time.
code.mark_pos(self.pos)
self.allocate_temp_result(code)
if self.type.is_pyobject:
is_dict = self.type.is_pyobject
if is_dict:
self.release_errors()
code.putln(
"%s = PyDict_New(); %s" % (
self.result(),
code.error_goto_if_null(self.result(), self.pos)))
code.put_gotref(self.py_result())
for item in self.key_value_pairs:
if self.reject_duplicates and len(self.key_value_pairs) > 1:
code.globalstate.use_utility_code(
UtilityCode.load_cached("RaiseDoubleKeywords", "FunctionArguments.c"))
for i, item in enumerate(self.key_value_pairs):
item.generate_evaluation_code(code)
if self.type.is_pyobject:
if is_dict:
if self.exclude_null_values:
code.putln('if (%s) {' % item.value.py_result())
code.put_error_if_neg(self.pos,
"PyDict_SetItem(%s, %s, %s)" % (
if self.reject_duplicates and i >= 1:
code.putln('if (unlikely(PyDict_Contains(%s, %s))) {' % (
self.result(), item.key.py_result()))
# currently only used in function calls
code.putln('__Pyx_RaiseDoubleKeywordsError("function", %s); %s' % (
item.key.py_result(),
code.error_goto(item.pos)))
code.putln("} else {")
code.put_error_if_neg(self.pos, "PyDict_SetItem(%s, %s, %s)" % (
self.result(),
item.key.py_result(),
item.value.py_result()))
if self.reject_duplicates and i >= 1:
code.putln('}')
if self.exclude_null_values:
code.putln('}')
else:
......@@ -7254,6 +7424,7 @@ class DictNode(ExprNode):
for item in self.key_value_pairs:
item.annotate(code)
class DictItemNode(ExprNode):
# Represents a single item in a DictNode
#
......@@ -7454,128 +7625,6 @@ class Py3ClassNode(ExprNode):
code.put_gotref(self.py_result())
class KeywordArgsNode(ExprNode):
# Helper class for keyword arguments.
#
# starstar_arg DictNode
# keyword_args [DictItemNode]
subexprs = ['starstar_arg', 'keyword_args']
is_temp = 1
type = dict_type
def calculate_constant_result(self):
result = dict(self.starstar_arg.constant_result)
for item in self.keyword_args:
key, value = item.constant_result
if key in result:
raise ValueError("duplicate keyword argument found: %s" % key)
result[key] = value
self.constant_result = result
def compile_time_value(self, denv):
result = self.starstar_arg.compile_time_value(denv)
pairs = [ (item.key.compile_time_value(denv), item.value.compile_time_value(denv))
for item in self.keyword_args ]
try:
result = dict(result)
for key, value in pairs:
if key in result:
raise ValueError("duplicate keyword argument found: %s" % key)
result[key] = value
except Exception, e:
self.compile_time_value_error(e)
return result
def type_dependencies(self, env):
return ()
def infer_type(self, env):
return dict_type
def analyse_types(self, env):
arg = self.starstar_arg.analyse_types(env)
arg = arg.coerce_to_pyobject(env)
arg = arg.as_none_safe_node(
# FIXME: CPython's error message starts with the runtime function name
'argument after ** must be a mapping, not NoneType')
self.starstar_arg = arg
if not self.keyword_args and arg.type is dict_type:
# strip this intermediate node and use the bare dict
if arg.is_name and arg.entry.is_arg and len(arg.entry.cf_assignments) == 1:
# passing **kwargs through to function call => allow NULL
arg.allow_null = True
return arg
self.keyword_args = [item.analyse_types(env) for item in self.keyword_args]
return self
def may_be_none(self):
return False
gil_message = "Constructing Python dict"
def generate_evaluation_code(self, code):
code.mark_pos(self.pos)
self.allocate_temp_result(code)
self.starstar_arg.generate_evaluation_code(code)
if self.starstar_arg.type is not dict_type:
# CPython supports calling functions with non-dicts, so do we
code.putln('if (likely(PyDict_Check(%s))) {' %
self.starstar_arg.py_result())
if self.keyword_args:
code.putln(
"%s = PyDict_Copy(%s); %s" % (
self.result(),
self.starstar_arg.py_result(),
code.error_goto_if_null(self.result(), self.pos)))
code.put_gotref(self.py_result())
else:
code.putln("%s = %s;" % (
self.result(),
self.starstar_arg.py_result()))
code.put_incref(self.result(), py_object_type)
if self.starstar_arg.type is not dict_type:
code.putln('} else {')
code.putln(
"%s = PyObject_CallFunctionObjArgs("
"(PyObject*)&PyDict_Type, %s, NULL); %s" % (
self.result(),
self.starstar_arg.py_result(),
code.error_goto_if_null(self.result(), self.pos)))
code.put_gotref(self.py_result())
code.putln('}')
self.starstar_arg.generate_disposal_code(code)
self.starstar_arg.free_temps(code)
if not self.keyword_args:
return
code.globalstate.use_utility_code(
UtilityCode.load_cached("RaiseDoubleKeywords", "FunctionArguments.c"))
for item in self.keyword_args:
item.generate_evaluation_code(code)
code.putln("if (unlikely(PyDict_GetItem(%s, %s))) {" % (
self.result(),
item.key.py_result()))
# FIXME: find out function name at runtime!
code.putln('__Pyx_RaiseDoubleKeywordsError("function", %s); %s' % (
item.key.py_result(),
code.error_goto(self.pos)))
code.putln("}")
code.put_error_if_neg(self.pos,
"PyDict_SetItem(%s, %s, %s)" % (
self.result(),
item.key.py_result(),
item.value.py_result()))
item.generate_disposal_code(code)
item.free_temps(code)
def annotate(self, code):
self.starstar_arg.annotate(code)
for item in self.keyword_args:
item.annotate(code)
class PyClassMetaclassNode(ExprNode):
# Helper class holds Python3 metaclass object
#
......
......@@ -48,7 +48,7 @@ cdef p_power(PyrexScanner s)
cdef p_new_expr(PyrexScanner s)
cdef p_trailer(PyrexScanner s, node1)
cdef p_call_parse_args(PyrexScanner s, bint allow_genexp = *)
cdef p_call_build_packed_args(pos, positional_args, keyword_args, star_arg, starstar_arg)
cdef p_call_build_packed_args(pos, positional_args, keyword_args)
cdef p_call(PyrexScanner s, function)
cdef p_index(PyrexScanner s, base)
cdef tuple p_subscript_list(PyrexScanner s)
......
......@@ -12,10 +12,11 @@ cython.declare(Nodes=object, ExprNodes=object, EncodedString=object,
FileSourceDescriptor=object, lookup_unicodechar=object,
Future=object, Options=object, error=object, warning=object,
Builtin=object, ModuleNode=object, Utils=object,
re=object, _unicode=object, _bytes=object)
re=object, _unicode=object, _bytes=object, partial=object)
import re
from unicodedata import lookup as lookup_unicodechar
from functools import partial
from .Scanning import PyrexScanner, FileSourceDescriptor
from . import Nodes
......@@ -409,24 +410,35 @@ def p_trailer(s, node1):
return ExprNodes.AttributeNode(pos,
obj=node1, attribute=name)
# arglist: argument (',' argument)* [',']
# argument: [test '='] test # Really [keyword '='] test
def p_call_parse_args(s, allow_genexp = True):
# since PEP 448:
# argument: ( test [comp_for] |
# test '=' test |
# '**' expr |
# star_expr )
def p_call_parse_args(s, allow_genexp=True):
# s.sy == '('
pos = s.position()
s.next()
positional_args = []
keyword_args = []
star_arg = None
starstar_arg = None
while s.sy not in ('**', ')'):
starstar_seen = False
last_was_tuple_unpack = False
while s.sy != ')':
if s.sy == '*':
if star_arg:
s.error("only one star-arg parameter allowed",
pos=s.position())
if starstar_seen:
s.error("Non-keyword arg following keyword arg", pos=s.position())
s.next()
star_arg = p_test(s)
positional_args.append(p_test(s))
last_was_tuple_unpack = True
elif s.sy == '**':
s.next()
keyword_args.append(p_test(s))
starstar_seen = True
else:
arg = p_test(s)
if s.sy == '=':
......@@ -441,73 +453,78 @@ def p_call_parse_args(s, allow_genexp = True):
keyword_args.append((keyword, arg))
else:
if keyword_args:
s.error("Non-keyword arg following keyword arg",
pos=arg.pos)
if star_arg:
s.error("Non-keyword arg following star-arg",
pos=arg.pos)
positional_args.append(arg)
s.error("Non-keyword arg following keyword arg", pos=arg.pos)
if positional_args and not last_was_tuple_unpack:
positional_args[-1].append(arg)
else:
positional_args.append([arg])
last_was_tuple_unpack = False
if s.sy != ',':
break
s.next()
if s.sy == 'for':
if len(positional_args) == 1 and not star_arg:
positional_args = [ p_genexp(s, positional_args[0]) ]
elif s.sy == '**':
s.next()
starstar_arg = p_test(s)
if s.sy == ',':
s.next() # FIXME: this is actually not valid Python syntax
if not keyword_args and not last_was_tuple_unpack:
if len(positional_args) == 1 and len(positional_args[0]) == 1:
positional_args = [p_genexp(s, positional_args[0][0])]
s.expect(')')
return positional_args, keyword_args, star_arg, starstar_arg
return positional_args or [[]], keyword_args
def p_call_build_packed_args(pos, positional_args, keyword_args,
star_arg, starstar_arg):
arg_tuple = None
def p_call_build_packed_args(pos, positional_args, keyword_args):
keyword_dict = None
if positional_args or not star_arg:
arg_tuple = ExprNodes.TupleNode(pos,
args = positional_args)
if star_arg:
star_arg_tuple = ExprNodes.AsTupleNode(pos, arg = star_arg)
if arg_tuple:
arg_tuple = ExprNodes.binop_node(pos,
operator = '+', operand1 = arg_tuple,
operand2 = star_arg_tuple)
else:
arg_tuple = star_arg_tuple
if keyword_args or starstar_arg:
keyword_args = [ExprNodes.DictItemNode(pos=key.pos, key=key, value=value)
for key, value in keyword_args]
if starstar_arg:
keyword_dict = ExprNodes.KeywordArgsNode(
pos,
starstar_arg = starstar_arg,
keyword_args = keyword_args)
else:
keyword_dict = ExprNodes.DictNode(
pos, key_value_pairs = keyword_args)
subtuples = [
ExprNodes.TupleNode(pos, args=arg) if isinstance(arg, list) else ExprNodes.AsTupleNode(pos, arg=arg)
for arg in positional_args
]
# TODO: implement a faster way to join tuples than creating each one and adding them
arg_tuple = reduce(partial(ExprNodes.binop_node, pos, '+'), subtuples)
if keyword_args:
kwargs = []
dict_items = []
for item in keyword_args:
if isinstance(item, tuple):
key, value = item
dict_items.append(ExprNodes.DictItemNode(pos=key.pos, key=key, value=value))
elif item.is_dict_literal:
# unpack "**{a:b}" directly
dict_items.extend(item.key_value_pairs)
else:
if dict_items:
kwargs.append(ExprNodes.DictNode(
dict_items[0].pos, key_value_pairs=dict_items, reject_duplicates=True))
dict_items = []
kwargs.append(item)
if dict_items:
kwargs.append(ExprNodes.DictNode(
dict_items[0].pos, key_value_pairs=dict_items, reject_duplicates=True))
if kwargs:
if len(kwargs) == 1 and kwargs[0].is_dict_literal:
# only simple keyword arguments found -> one dict
keyword_dict = kwargs[0]
else:
# at least one **kwargs
keyword_dict = ExprNodes.KeywordArgsNode(pos, keyword_args=kwargs)
return arg_tuple, keyword_dict
def p_call(s, function):
# s.sy == '('
pos = s.position()
positional_args, keyword_args = p_call_parse_args(s)
positional_args, keyword_args, star_arg, starstar_arg = \
p_call_parse_args(s)
if not (keyword_args or star_arg or starstar_arg):
return ExprNodes.SimpleCallNode(pos,
function = function,
args = positional_args)
if not keyword_args and len(positional_args) == 1 and isinstance(positional_args[0], list):
return ExprNodes.SimpleCallNode(pos, function=function, args=positional_args[0])
else:
arg_tuple, keyword_dict = p_call_build_packed_args(
pos, positional_args, keyword_args, star_arg, starstar_arg)
return ExprNodes.GeneralCallNode(pos,
function = function,
positional_args = arg_tuple,
keyword_args = keyword_dict)
arg_tuple, keyword_dict = p_call_build_packed_args(pos, positional_args, keyword_args)
return ExprNodes.GeneralCallNode(
pos, function=function, positional_args=arg_tuple, keyword_args=keyword_dict)
#lambdef: 'lambda' [varargslist] ':' test
......@@ -2969,6 +2986,7 @@ def p_py_arg_decl(s, annotated = 1):
annotation = p_test(s)
return Nodes.PyArgDeclNode(pos, name = name, annotation = annotation)
def p_class_statement(s, decorators):
# s.sy == 'class'
pos = s.position()
......@@ -2979,10 +2997,8 @@ def p_class_statement(s, decorators):
keyword_dict = None
starstar_arg = None
if s.sy == '(':
positional_args, keyword_args, star_arg, starstar_arg = \
p_call_parse_args(s, allow_genexp = False)
arg_tuple, keyword_dict = p_call_build_packed_args(
pos, positional_args, keyword_args, star_arg, None)
positional_args, keyword_args = p_call_parse_args(s, allow_genexp=False)
arg_tuple, keyword_dict = p_call_build_packed_args(pos, positional_args, keyword_args)
if arg_tuple is None:
# XXX: empty arg_tuple
arg_tuple = ExprNodes.TupleNode(pos, args=[])
......@@ -2995,6 +3011,7 @@ def p_class_statement(s, decorators):
doc=doc, body=body, decorators=decorators,
force_py3_semantics=s.context.language_level >= 3)
def p_c_class_definition(s, pos, ctx):
# s.sy == 'class'
s.next()
......
......@@ -109,17 +109,29 @@ subscript: test | [test] ':' [test] [sliceop]
sliceop: ':' [test]
exprlist: (expr|star_expr) (',' (expr|star_expr))* [',']
testlist: test (',' test)* [',']
dictorsetmaker: ( (test ':' test (comp_for | (',' test ':' test)* [','])) |
(test (comp_for | (',' test)* [','])) )
dictorsetmaker: ( ((test ':' test | '**' expr)
(comp_for | (',' (test ':' test | '**' expr))* [','])) |
((test | star_expr)
(comp_for | (',' (test | star_expr))* [','])) )
classdef: 'class' PY_NAME ['(' [arglist] ')'] ':' suite
arglist: (argument ',')* (argument [',']
|'*' test (',' argument)* [',' '**' test]
|'**' test)
arglist: argument (',' argument)* [',']
# The reason that keywords are test nodes instead of NAME is that using NAME
# results in an ambiguity. ast.c makes sure it's a NAME.
argument: test [comp_for] | test '=' test # Really [keyword '='] test
# "test '=' test" is really "keyword '=' test", but we have no such token.
# These need to be in a single rule to avoid grammar that is ambiguous
# to our LL(1) parser. Even though 'test' includes '*expr' in star_expr,
# we explicitly match '*' here, too, to give it proper precedence.
# Illegal combinations and orderings are blocked in ast.c:
# multiple (test comp_for) arguements are blocked; keyword unpackings
# that precede iterable unpackings are blocked; etc.
argument: ( test [comp_for] |
test '=' test |
'**' expr |
star_expr )
comp_iter: comp_for | comp_if
comp_for: 'for' exprlist ('in' or_test | for_from_clause) [comp_iter]
comp_if: 'if' test_nocond [comp_iter]
......
......@@ -110,6 +110,17 @@ static void __Pyx_RaiseDoubleKeywordsError(
}
//////////////////// RaiseMappingExpected.proto ////////////////////
static void __Pyx_RaiseMappingExpectedError(PyObject* arg); /*proto*/
//////////////////// RaiseMappingExpected ////////////////////
static void __Pyx_RaiseMappingExpectedError(PyObject* arg) {
PyErr_Format(PyExc_TypeError, "'%.200s' object is not a mapping", Py_TYPE(arg)->tp_name);
}
//////////////////// KeywordStringCheck.proto ////////////////////
static CYTHON_INLINE int __Pyx_CheckKeywordStrings(PyObject *kwdict, const char* function_name, int kw_allowed); /*proto*/
......@@ -290,3 +301,43 @@ invalid_keyword:
bad:
return -1;
}
//////////////////// MergeKeywords.proto ////////////////////
static int __Pyx_MergeKeywords(PyObject *kwdict, PyObject *source_mapping); /*proto*/
//////////////////// MergeKeywords ////////////////////
//@requires: RaiseDoubleKeywords
//@requires: Optimize.c::dict_iter
static int __Pyx_MergeKeywords(PyObject *kwdict, PyObject *source_mapping) {
PyObject *iter, *key, *value;
int source_is_dict, result;
Py_ssize_t orig_length, ppos = 0;
iter = __Pyx_dict_iterator(source_mapping, 0, PYIDENT("items"), &orig_length, &source_is_dict);
if (unlikely(!iter)) goto bad;
while (1) {
result = __Pyx_dict_iter_next(iter, orig_length, &ppos, &key, &value, NULL, source_is_dict);
if (unlikely(result < 0)) goto bad;
if (!result) break;
if (unlikely(PyDict_Contains(kwdict, key))) {
__Pyx_RaiseDoubleKeywordsError("function", key);
result = -1;
} else {
result = PyDict_SetItem(kwdict, key, value);
}
Py_DECREF(key);
Py_DECREF(value);
if (unlikely(result < 0)) goto bad;
}
Py_XDECREF(iter);
return 0;
bad:
Py_XDECREF(iter);
return -1;
}
# mode: run
# tag: pep448
from __future__ import print_function
import sys
IS_PY3 = sys.version_info[0] >= 3
if IS_PY3:
__doc__ = """
>>> def f(*, w): pass
>>> try: errors_call_no_args(f)
... except TypeError: pass
... else: print("FAILED!")
>>> def f(*, a, b, c, d, e): pass
>>> try: errors_call_no_args(f)
... except TypeError: pass
... else: print("FAILED!")
>>> def f(*, kw, b): pass
>>> errors_call_3args_2kwargs(f)
... except TypeError: pass
... else: print("FAILED!")
>>> def f(a, b=2, *, kw): pass
>>> try: errors_call_3args_1kwarg(f)
... except TypeError: pass
... else: print("FAILED!")
>>> def f(*, kw): pass
>>> try: errors_call_1arg_1kwarg(f)
... except TypeError: pass
... else: print("FAILED!")
"""
# test for method/function calls. adapted from CPython's "test_extcall.py".
def sortdict(d):
return '{%s}' % ', '.join(['%r: %r' % item for item in sorted(d.items())])
# We're going the use these types for extra testing
try:
from collections import UserList, UserDict
except ImportError:
from UserList import UserList
from UserDict import UserDict
# We're defining four helper functions
def e(a,b):
print(a, b)
def f(*a, **k):
print(a, sortdict(k))
def g(x, *y, **z):
print(x, y, sortdict(z))
def h(j=1, a=2, h=3):
print(j, a, h)
# Argument list examples
def call_f_positional():
"""
>>> call_f_positional()
() {}
(1,) {}
(1, 2) {}
(1, 2, 3) {}
(1, 2, 3, 4, 5) {}
(1, 2, 3, 4, 5) {}
(1, 2, 3, 4, 5) {}
(1, 2, 3, 4, 5) {}
(1, 2, 3, 4, 5, 6, 7) {}
(1, 2, 3, 4, 5, 6, 7) {}
(1, 2, 3, 4, 5, 6, 7) {}
"""
f()
f(1)
f(1, 2)
f(1, 2, 3)
f(1, 2, 3, *(4, 5))
f(1, 2, 3, *[4, 5])
f(*[1, 2, 3], 4, 5)
f(1, 2, 3, *UserList([4, 5]))
f(1, 2, 3, *[4, 5], *[6, 7])
f(1, *[2, 3], 4, *[5, 6], 7)
f(*UserList([1, 2]), *UserList([3, 4]), 5, *UserList([6, 7]))
# Here we add keyword arguments
def call_f_kwargs():
"""
>>> call_f_kwargs()
(1, 2, 3) {'a': 4, 'b': 5}
(1, 2, 3, 4, 5) {'a': 6, 'b': 7}
(1, 2, 3, 6, 7) {'a': 8, 'b': 9, 'x': 4, 'y': 5}
(1, 2, 3, 4, 5) {'a': 6, 'b': 7, 'c': 8}
(1, 2, 3, 4, 5) {'a': 8, 'b': 9, 'x': 6, 'y': 7}
(1, 2, 3) {'a': 4, 'b': 5}
(1, 2, 3, 4, 5) {'a': 6, 'b': 7}
(1, 2, 3, 6, 7) {'a': 8, 'b': 9, 'x': 4, 'y': 5}
(1, 2, 3, 4, 5) {'a': 8, 'b': 9, 'x': 6, 'y': 7}
"""
f(1, 2, 3, **{'a':4, 'b':5})
f(1, 2, 3, *[4, 5], **{'a':6, 'b':7})
f(1, 2, 3, x=4, y=5, *(6, 7), **{'a':8, 'b': 9})
f(1, 2, 3, *[4, 5], **{'c': 8}, **{'a':6, 'b':7})
f(1, 2, 3, *(4, 5), x=6, y=7, **{'a':8, 'b': 9})
f(1, 2, 3, **UserDict(a=4, b=5))
f(1, 2, 3, *(4, 5), **UserDict(a=6, b=7))
f(1, 2, 3, x=4, y=5, *(6, 7), **UserDict(a=8, b=9))
f(1, 2, 3, *(4, 5), x=6, y=7, **UserDict(a=8, b=9))
# Examples with invalid arguments (TypeErrors). We're also testing the function
# names in the exception messages.
#
# Verify clearing of SF bug #733667
def errors_f1():
"""
>>> errors_f1() # doctest: +ELLIPSIS
Traceback (most recent call last):
...
TypeError: ...got multiple values for keyword argument 'a'
"""
f(1, 2, **{'a': -1, 'b': 5}, **{'a': 4, 'c': 6})
def errors_f2():
"""
>>> errors_f2() # doctest: +ELLIPSIS
Traceback (most recent call last):
...
TypeError: ...multiple values for keyword argument 'a'
"""
f(1, 2, **{'a': -1, 'b': 5}, a=4, c=6)
def errors_e1():
"""
>>> try: errors_e1()
... except TypeError: pass
... else: print("FAILED!")
"""
e(c=4)
def errors_e2():
"""
>>> try: errors_e2()
... except TypeError: pass
... else: print("FAILED!")
"""
e(a=1, b=2, c=4)
def errors_g1():
"""
>>> errors_g1()
Traceback (most recent call last):
...
TypeError: g() takes at least 1 positional argument (0 given)
# TypeError: g() missing 1 required positional argument: 'x'
"""
g()
def errors_g2():
"""
>>> errors_g2()
Traceback (most recent call last):
...
TypeError: g() takes at least 1 positional argument (0 given)
# TypeError: g() missing 1 required positional argument: 'x'
"""
g(*())
def errors_g3():
"""
>>> errors_g3()
Traceback (most recent call last):
...
TypeError: g() takes at least 1 positional argument (0 given)
# TypeError: g() missing 1 required positional argument: 'x'
"""
g(*(), **{})
def call_g_positional():
"""
>>> call_g_positional()
1 () {}
1 (2,) {}
1 (2, 3) {}
1 (2, 3, 4, 5) {}
"""
g(1)
g(1, 2)
g(1, 2, 3)
g(1, 2, 3, *(4, 5))
def call_nonseq_positional1():
"""
>>> call_nonseq_positional1()
Traceback (most recent call last):
...
TypeError: 'Nothing' object is not iterable
# TypeError: g() argument after * must be a sequence, not Nothing
"""
class Nothing(object): pass
g(*Nothing())
def call_nonseq_positional2():
"""
>>> call_nonseq_positional2()
Traceback (most recent call last):
...
TypeError: 'Nothing' object is not iterable
# TypeError: g() argument after * must be a sequence, not Nothing
"""
class Nothing(object):
def __len__(self): return 5
g(*Nothing())
def call_seqlike_positional1():
"""
>>> call_seqlike_positional1()
0 (1, 2) {}
"""
class Nothing(object):
def __len__(self): return 5
def __getitem__(self, i):
if i<3: return i
else: raise IndexError(i)
g(*Nothing())
def call_seqlike_positional2():
"""
>>> call_seqlike_positional2()
0 (1, 2, 3) {}
"""
class Nothing:
def __init__(self): self.c = 0
def __iter__(self): return self
def __next__(self):
if self.c == 4:
raise StopIteration
c = self.c
self.c += 1
return c
next = __next__
g(*Nothing())
# Make sure that the function doesn't stomp the dictionary
def call_kwargs_unmodified1():
"""
>>> call_kwargs_unmodified1()
1 () {'a': 1, 'b': 2, 'c': 3, 'd': 4}
True
"""
d = {'a': 1, 'b': 2, 'c': 3}
d2 = d.copy()
g(1, d=4, **d)
return d == d2
# What about willful misconduct?
def call_kwargs_unmodified2():
"""
>>> call_kwargs_unmodified2()
{}
"""
def saboteur(**kw):
kw['x'] = 'm'
return kw
d = {}
kw = saboteur(a=1, **d)
return d
def errors_args_kwargs_overlap():
"""
>>> errors_args_kwargs_overlap() # doctest: +ELLIPSIS
Traceback (most recent call last):
...
TypeError: ...got multiple values for... argument 'x'
"""
g(1, 2, 3, **{'x': 4, 'y': 5})
def errors_non_string_kwarg():
"""
>>> errors_non_string_kwarg()
Traceback (most recent call last):
...
TypeError: f() keywords must be strings
"""
f(**{1:2})
def errors_unexpected_kwarg():
"""
>>> errors_unexpected_kwarg()
Traceback (most recent call last):
...
TypeError: h() got an unexpected keyword argument 'e'
"""
h(**{'e': 2})
def errors_call_nonseq():
"""
>>> try: errors_call_nonseq()
... except TypeError: pass
... else: print("FAILED!")
"""
h(*h)
def errors_call_builtin_nonseq():
"""
>>> try: errors_call_builtin_nonseq()
... except TypeError: pass
... else: print("FAILED!")
"""
dir(*h)
def errors_call_none_nonseq():
"""
>>> try: errors_call_none_nonseq()
... except TypeError: pass
... else: print("FAILED!")
"""
None(*h)
def errors_call_nonmapping_kwargs():
"""
>>> try: errors_call_nonmapping_kwargs()
... except TypeError: pass
... else: print("FAILED!")
"""
h(**h)
def errors_call_builtin_nonmapping_kwargs():
"""
>>> try: errors_call_builtin_nonmapping_kwargs()
... except TypeError: pass
... else: print("FAILED!")
"""
dir(**h)
def errors_call_none_nonmapping_kwargs():
"""
>>> try: errors_call_none_nonmapping_kwargs()
... except TypeError: pass
... else: print("FAILED!")
"""
None(**h)
''' # compile time error in Cython
def errors_call_builtin_duplicate_kwarg():
"""
>>> errors_call_builtin_duplicate_kwarg() # doctest: +ELLIPSIS
Traceback (most recent call last):
...
TypeError: ...got multiple values for keyword argument 'b'
"""
dir(b=1, **{'b': 1})
'''
# Another helper function
def f2(*a, **b):
return a, b
def call_many_kwargs():
"""
call_many_kwargs()
(3, 512, True)
"""
d = {}
for i in range(512):
key = 'k%d' % i
d[key] = i
a, b = f2(1, *(2,3), **d)
return len(a), len(b), b == d
def call_method(Foo):
"""
>>> class Foo(object):
... def method(self, arg1, arg2):
... print(arg1+arg2)
>>> call_method(Foo)
3
3
5
5
"""
x = Foo()
Foo.method(*(x, 1, 2))
Foo.method(x, *(1, 2))
if sys.version_info[0] >= 3:
Foo.method(*(1, 2, 3))
Foo.method(1, *[2, 3])
else:
print(5)
print(5)
# A PyCFunction that takes only positional parameters should allow an
# empty keyword dictionary to pass without a complaint, but raise a
# TypeError if te dictionary is not empty
def call_builtin_empty_dict():
"""
>>> call_builtin_empty_dict()
"""
silence = id(1, *{})
silence = id(1, **{})
def call_builtin_nonempty_dict():
"""
>>> call_builtin_nonempty_dict()
Traceback (most recent call last):
...
TypeError: id() takes no keyword arguments
"""
return id(1, **{'foo': 1})
''' Cython: currently just passes empty kwargs into f() while CPython keeps the content
# A corner case of keyword dictionary items being deleted during
# the function call setup. See <http://bugs.python.org/issue2016>.
def call_kwargs_modified_while_building():
"""
>>> call_kwargs_modified_while_building()
1 2
"""
class Name(str):
def __eq__(self, other):
try:
del x[self]
except KeyError:
pass
return str.__eq__(self, other)
def __hash__(self):
return str.__hash__(self)
x = {Name("a"):1, Name("b"):2}
def f(a, b):
print(a,b)
f(**x)
'''
# Too many arguments:
def errors_call_one_arg(f):
"""
>>> def f(): pass
>>> try: errors_call_one_arg(f)
... except TypeError: pass
... else: print("FAILED!")
"""
f(1)
def errors_call_2args(f):
"""
>>> def f(a): pass
>>> try: errors_call_2args(f)
... except TypeError: pass
... else: print("FAILED!")
"""
f(1, 2)
def errors_call_3args(f):
"""
>>> def f(a, b=1): pass
>>> try: errors_call_3args(f)
... except TypeError: pass
... else: print("FAILED!")
"""
f(1, 2, 3)
def errors_call_1arg_1kwarg(f):
# Py3 only
f(1, kw=3)
def errors_call_3args_2kwargs(f):
# Py3 only
f(1, 2, 3, b=3, kw=3)
def errors_call_3args_1kwarg(f):
# Py3 only
f(2, 3, 4, kw=4)
# Too few and missing arguments:
def errors_call_no_args(f):
"""
>>> def f(a): pass
>>> try: errors_call_no_args(f)
... except TypeError: pass
... else: print("FAILED!")
>>> def f(a, b): pass
>>> try: errors_call_no_args(f)
... except TypeError: pass
... else: print("FAILED!")
>>> def f(a, b, c): pass
>>> try: errors_call_no_args(f)
... except TypeError: pass
... else: print("FAILED!")
>>> def f(a, b, c, d, e): pass
>>> try: errors_call_no_args(f)
... except TypeError: pass
... else: print("FAILED!")
"""
f()
def errors_call_one_missing_kwarg(f):
"""
>>> def f(a, b=4, c=5, d=5): pass
>>> try: errors_call_one_missing_kwarg(f)
... except TypeError: pass
... else: print("FAILED!")
"""
f(c=12, b=9)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment