implement __kwdefaults__ for CyFunction
authorStefan Behnel <stefan_ml@behnel.de>
Fri, 29 Mar 2013 16:09:58 +0000 (17:09 +0100)
committerStefan Behnel <stefan_ml@behnel.de>
Fri, 29 Mar 2013 16:09:58 +0000 (17:09 +0100)
CHANGES.rst
Cython/Compiler/ExprNodes.py
Cython/Utility/CythonFunction.c
tests/run/cyfunction_defaults.pyx

index c02bb12..a748037 100644 (file)
@@ -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.
 
index afffb1d..2be2f4e 100755 (executable)
@@ -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)
     #
index fd675ce..bd018d8 100644 (file)
@@ -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);
index 8c5cda3..f032e1e 100644 (file)
@@ -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