From ea569ef3312746901333efa612f5bc7d2d951042 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Fri, 29 Mar 2013 17:09:58 +0100 Subject: [PATCH] implement __kwdefaults__ for CyFunction --- CHANGES.rst | 7 +++ Cython/Compiler/ExprNodes.py | 63 +++++++++++++++++++---- Cython/Utility/CythonFunction.c | 105 ++++++++++++++++++++++++++++++++------ tests/run/cyfunction_defaults.pyx | 45 ++++++++++++++++ 4 files changed, 193 insertions(+), 27 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index c02bb12..a748037 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -55,6 +55,13 @@ Features added Bugs fixed ---------- +* The ``__defaults__`` attribute was not writable for Cython implemented + functions. + +* Default values of keyword-only arguments showed up in ``__defaults__`` instead + of ``__kwdefaults__`` (which was not implemented). Both are available for + Cython implemented functions now, as specified in Python 3.x. + * ``yield`` works inside of ``with gil`` sections. It previously lead to a crash. This fixes trac ticket 803. diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index afffb1d..2be2f4e 100755 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -6962,7 +6962,7 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin): # module_name EncodedString Name of defining module # code_object CodeObjectNode the PyCodeObject creator node - subexprs = ['code_object', 'defaults_tuple'] + subexprs = ['code_object', 'defaults_tuple', 'defaults_kwdict'] self_object = None code_object = None @@ -6972,6 +6972,7 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin): defaults_struct = None defaults_pyobjects = 0 defaults_tuple = None + defaults_kwdict = None type = py_object_type is_temp = 1 @@ -7002,6 +7003,7 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin): nonliteral_objects = [] nonliteral_other = [] default_args = [] + default_kwargs = [] for arg in self.def_node.args: if arg.default: if not arg.default.is_literal: @@ -7012,7 +7014,10 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin): nonliteral_other.append(arg) else: arg.default = DefaultLiteralArgNode(arg.pos, arg.default) - default_args.append(arg) + if arg.kw_only: + default_kwargs.append(arg) + else: + default_args.append(arg) if nonliteral_objects or nonliteral_other: module_scope = env.global_scope() cname = module_scope.next_id(Naming.defaults_struct_prefix) @@ -7037,20 +7042,40 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin): Naming.dynamic_args_cname, entry.cname) self.def_node.defaults_struct = self.defaults_struct.name - if default_args: + if default_args or default_kwargs: if self.defaults_struct is None: - defaults_tuple = TupleNode(self.pos, args=[ - arg.default for arg in default_args]) - self.defaults_tuple = defaults_tuple.analyse_types(env) + if default_args: + defaults_tuple = TupleNode(self.pos, args=[ + arg.default for arg in default_args]) + self.defaults_tuple = defaults_tuple.analyse_types(env) + if default_kwargs: + defaults_kwdict = DictNode(self.pos, key_value_pairs=[ + DictItemNode( + arg.pos, + key=IdentifierStringNode(arg.pos, value=arg.name), + value=arg.default) + for arg in default_kwargs]) + self.defaults_kwdict = defaults_kwdict.analyse_types(env) else: + if default_args: + defaults_tuple = DefaultsTupleNode( + self.pos, default_args, self.defaults_struct) + else: + defaults_tuple = NoneNode(self.pos) + if default_kwargs: + defaults_kwdict = DefaultsKwDictNode( + self.pos, default_kwargs, self.defaults_struct) + else: + defaults_kwdict = NoneNode(self.pos) + defaults_getter = Nodes.DefNode( self.pos, args=[], star_arg=None, starstar_arg=None, body=Nodes.ReturnStatNode( self.pos, return_type=py_object_type, - value=DefaultsTupleNode( - self.pos, default_args, - self.defaults_struct)), - decorators=None, name=StringEncoding.EncodedString("__defaults__")) + value=TupleNode( + self.pos, args=[defaults_tuple, defaults_kwdict])), + decorators=None, + name=StringEncoding.EncodedString("__defaults__")) defaults_getter.analyse_declarations(env) defaults_getter = defaults_getter.analyse_expressions(env) defaults_getter.body = defaults_getter.body.analyse_expressions( @@ -7161,6 +7186,9 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin): if self.defaults_tuple: code.putln('__Pyx_CyFunction_SetDefaultsTuple(%s, %s);' % ( self.result(), self.defaults_tuple.py_result())) + if self.defaults_kwdict: + code.putln('__Pyx_CyFunction_SetDefaultsKwDict(%s, %s);' % ( + self.result(), self.defaults_kwdict.py_result())) if def_node.defaults_getter: code.putln('__Pyx_CyFunction_SetDefaultsGetter(%s, %s);' % ( self.result(), def_node.defaults_getter.entry.pyfunc_cname)) @@ -7306,6 +7334,21 @@ class DefaultsTupleNode(TupleNode): super(DefaultsTupleNode, self).__init__(pos, args=args) +class DefaultsKwDictNode(DictNode): + # CyFunction's __kwdefaults__ dict + + def __init__(self, pos, defaults, defaults_struct): + items = [] + for arg in defaults: + name = IdentifierStringNode(arg.pos, value=arg.name) + if not arg.default.is_literal: + arg = DefaultNonLiteralArgNode(pos, arg, defaults_struct) + else: + arg = arg.default + items.append(DictItemNode(arg.pos, key=name, value=arg)) + super(DefaultsKwDictNode, self).__init__(pos, key_value_pairs=items) + + class LambdaNode(InnerFunctionNode): # Lambda expression node (only used as a function reference) # diff --git a/Cython/Utility/CythonFunction.c b/Cython/Utility/CythonFunction.c index fd675ce..bd018d8 100644 --- a/Cython/Utility/CythonFunction.c +++ b/Cython/Utility/CythonFunction.c @@ -37,6 +37,7 @@ typedef struct { /* Defaults info */ PyObject *defaults_tuple; /* Const defaults tuple */ + PyObject *defaults_kwdict; /* Const kwonly defaults dict */ PyObject *(*defaults_getter)(PyObject *); } __pyx_CyFunctionObject; @@ -55,6 +56,8 @@ static CYTHON_INLINE void *__Pyx_CyFunction_InitDefaults(PyObject *m, int pyobjects); static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsTuple(PyObject *m, PyObject *tuple); +static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsKwDict(PyObject *m, + PyObject *dict); static int __Pyx_CyFunction_init(void); @@ -235,27 +238,85 @@ __Pyx_CyFunction_get_code(__pyx_CyFunctionObject *op) return result; } +static int +__Pyx_CyFunction_init_defaults(__pyx_CyFunctionObject *op) { + PyObject *res = op->defaults_getter((PyObject *) op); + if (unlikely(!res)) + return -1; + + /* Cache result */ + op->defaults_tuple = PyTuple_GET_ITEM(res, 0); + Py_INCREF(op->defaults_tuple); + op->defaults_kwdict = PyTuple_GET_ITEM(res, 1); + Py_INCREF(op->defaults_kwdict); + Py_DECREF(res); + return 0; +} + +static int +__Pyx_CyFunction_set_defaults(__pyx_CyFunctionObject *op, PyObject* value) { + PyObject* tmp; + if (!value) { + // del => explicit None to prevent rebuilding + value = Py_None; + } else if (value != Py_None && !PyTuple_Check(value)) { + PyErr_SetString(PyExc_TypeError, + "__defaults__ must be set to a tuple object"); + return -1; + } + Py_INCREF(value); + tmp = op->defaults_tuple; + op->defaults_tuple = value; + Py_XDECREF(tmp); + return 0; +} + static PyObject * -__Pyx_CyFunction_get_defaults(__pyx_CyFunctionObject *op) -{ - if (op->defaults_tuple) { - Py_INCREF(op->defaults_tuple); - return op->defaults_tuple; +__Pyx_CyFunction_get_defaults(__pyx_CyFunctionObject *op) { + PyObject* result = op->defaults_tuple; + if (unlikely(!result)) { + if (op->defaults_getter) { + if (__Pyx_CyFunction_init_defaults(op) < 0) return NULL; + result = op->defaults_tuple; + } else { + result = Py_None; + } } + Py_INCREF(result); + return result; +} - if (op->defaults_getter) { - PyObject *res = op->defaults_getter((PyObject *) op); +static int +__Pyx_CyFunction_set_kwdefaults(__pyx_CyFunctionObject *op, PyObject* value) { + PyObject* tmp; + if (!value) { + // del => explicit None to prevent rebuilding + value = Py_None; + } else if (value != Py_None && !PyDict_Check(value)) { + PyErr_SetString(PyExc_TypeError, + "__kwdefaults__ must be set to a dict object"); + return -1; + } + Py_INCREF(value); + tmp = op->defaults_kwdict; + op->defaults_kwdict = value; + Py_XDECREF(tmp); + return 0; +} - /* Cache result */ - if (likely(res)) { - Py_INCREF(res); - op->defaults_tuple = res; +static PyObject * +__Pyx_CyFunction_get_kwdefaults(__pyx_CyFunctionObject *op) { + PyObject* result = op->defaults_kwdict; + if (unlikely(!result)) { + if (op->defaults_getter) { + if (__Pyx_CyFunction_init_defaults(op) < 0) return NULL; + result = op->defaults_kwdict; + } else { + result = Py_None; } - return res; } - - Py_INCREF(Py_None); - return Py_None; + Py_INCREF(result); + return result; } static PyGetSetDef __pyx_CyFunction_getsets[] = { @@ -273,8 +334,9 @@ static PyGetSetDef __pyx_CyFunction_getsets[] = { {(char *) "__closure__", (getter)__Pyx_CyFunction_get_closure, 0, 0, 0}, {(char *) "func_code", (getter)__Pyx_CyFunction_get_code, 0, 0, 0}, {(char *) "__code__", (getter)__Pyx_CyFunction_get_code, 0, 0, 0}, - {(char *) "func_defaults", (getter)__Pyx_CyFunction_get_defaults, 0, 0, 0}, - {(char *) "__defaults__", (getter)__Pyx_CyFunction_get_defaults, 0, 0, 0}, + {(char *) "func_defaults", (getter)__Pyx_CyFunction_get_defaults, (setter)__Pyx_CyFunction_set_defaults, 0, 0}, + {(char *) "__defaults__", (getter)__Pyx_CyFunction_get_defaults, (setter)__Pyx_CyFunction_set_defaults, 0, 0}, + {(char *) "__kwdefaults__", (getter)__Pyx_CyFunction_get_kwdefaults, (setter)__Pyx_CyFunction_set_kwdefaults, 0, 0}, {0, 0, 0, 0, 0} }; @@ -328,6 +390,7 @@ static PyObject *__Pyx_CyFunction_New(PyTypeObject *type, PyMethodDef *ml, int f op->defaults_pyobjects = 0; op->defaults = NULL; op->defaults_tuple = NULL; + op->defaults_kwdict = NULL; op->defaults_getter = NULL; PyObject_GC_Track(op); return (PyObject *) op; @@ -345,6 +408,7 @@ __Pyx_CyFunction_clear(__pyx_CyFunctionObject *m) Py_CLEAR(m->func_code); Py_CLEAR(m->func_classobj); Py_CLEAR(m->defaults_tuple); + Py_CLEAR(m->defaults_kwdict); if (m->defaults) { PyObject **pydefaults = __Pyx_CyFunction_Defaults(PyObject *, m); @@ -380,6 +444,7 @@ static int __Pyx_CyFunction_traverse(__pyx_CyFunctionObject *m, visitproc visit, Py_VISIT(m->func_code); Py_VISIT(m->func_classobj); Py_VISIT(m->defaults_tuple); + Py_VISIT(m->defaults_kwdict); if (m->defaults) { PyObject **pydefaults = __Pyx_CyFunction_Defaults(PyObject *, m); @@ -565,6 +630,12 @@ static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsTuple(PyObject *func, PyOb Py_INCREF(tuple); } +static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsKwDict(PyObject *func, PyObject *dict) { + __pyx_CyFunctionObject *m = (__pyx_CyFunctionObject *) func; + m->defaults_kwdict = dict; + Py_INCREF(dict); +} + //////////////////// CyFunctionClassCell.proto //////////////////// static CYTHON_INLINE void __Pyx_CyFunction_InitClassCell(PyObject *cyfunctions, PyObject *classobj); diff --git a/tests/run/cyfunction_defaults.pyx b/tests/run/cyfunction_defaults.pyx index 8c5cda3..f032e1e 100644 --- a/tests/run/cyfunction_defaults.pyx +++ b/tests/run/cyfunction_defaults.pyx @@ -5,6 +5,8 @@ cimport cython import sys +IS_PY3 = sys.version_info[0] >= 3 + def get_defaults(func): if sys.version_info >= (2, 6, 0): return func.__defaults__ @@ -86,6 +88,49 @@ def test_defaults_nonliteral_func_call(f): return a return func + +def cy_kwonly_default_args(a, x=1, *, b=2): + l = m = 1 + +def test_kwdefaults(value): + """ + >>> cy_kwonly_default_args.__defaults__ + (1,) + >>> cy_kwonly_default_args.func_defaults + (1,) + + >>> cy_kwonly_default_args.__kwdefaults__ + {'b': 2} + + >>> if IS_PY3: test_kwdefaults.__defaults__ is None + ... else: print(True) + True + >>> test_kwdefaults.__kwdefaults__ is None + ... else: print(True) + True + + >>> f = test_kwdefaults(5) + >>> f.__defaults__ + (1,) + >>> f.__kwdefaults__ + {'b': 5} + >>> f.__kwdefaults__ = () + Traceback (most recent call last): + TypeError: __kwdefaults__ must be set to a dict object + >>> f.__kwdefaults__ = None + >>> f.__kwdefaults__ + >>> f.__kwdefaults__ = {} + >>> f.__kwdefaults__ + {} + >>> f.__kwdefaults__ = {'a': 2} + >>> f.__kwdefaults__ + {'a': 2} + """ + def kwonly_default_args(a, x=1, *, b=value): + return a, x, b + return kwonly_default_args + + _counter2 = 1.0 def counter2(): global _counter2 -- 2.7.4