Commit ea569ef3 authored by Stefan Behnel's avatar Stefan Behnel

implement __kwdefaults__ for CyFunction

parent 774b88c6
...@@ -55,6 +55,13 @@ Features added ...@@ -55,6 +55,13 @@ Features added
Bugs fixed 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. * ``yield`` works inside of ``with gil`` sections. It previously lead to a crash.
This fixes trac ticket 803. This fixes trac ticket 803.
......
...@@ -6962,7 +6962,7 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin): ...@@ -6962,7 +6962,7 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
# module_name EncodedString Name of defining module # module_name EncodedString Name of defining module
# code_object CodeObjectNode the PyCodeObject creator node # code_object CodeObjectNode the PyCodeObject creator node
subexprs = ['code_object', 'defaults_tuple'] subexprs = ['code_object', 'defaults_tuple', 'defaults_kwdict']
self_object = None self_object = None
code_object = None code_object = None
...@@ -6972,6 +6972,7 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin): ...@@ -6972,6 +6972,7 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
defaults_struct = None defaults_struct = None
defaults_pyobjects = 0 defaults_pyobjects = 0
defaults_tuple = None defaults_tuple = None
defaults_kwdict = None
type = py_object_type type = py_object_type
is_temp = 1 is_temp = 1
...@@ -7002,6 +7003,7 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin): ...@@ -7002,6 +7003,7 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
nonliteral_objects = [] nonliteral_objects = []
nonliteral_other = [] nonliteral_other = []
default_args = [] default_args = []
default_kwargs = []
for arg in self.def_node.args: for arg in self.def_node.args:
if arg.default: if arg.default:
if not arg.default.is_literal: if not arg.default.is_literal:
...@@ -7012,6 +7014,9 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin): ...@@ -7012,6 +7014,9 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
nonliteral_other.append(arg) nonliteral_other.append(arg)
else: else:
arg.default = DefaultLiteralArgNode(arg.pos, arg.default) arg.default = DefaultLiteralArgNode(arg.pos, arg.default)
if arg.kw_only:
default_kwargs.append(arg)
else:
default_args.append(arg) default_args.append(arg)
if nonliteral_objects or nonliteral_other: if nonliteral_objects or nonliteral_other:
module_scope = env.global_scope() module_scope = env.global_scope()
...@@ -7037,20 +7042,40 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin): ...@@ -7037,20 +7042,40 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
Naming.dynamic_args_cname, entry.cname) Naming.dynamic_args_cname, entry.cname)
self.def_node.defaults_struct = self.defaults_struct.name self.def_node.defaults_struct = self.defaults_struct.name
if default_args: if default_args or default_kwargs:
if self.defaults_struct is None: if self.defaults_struct is None:
if default_args:
defaults_tuple = TupleNode(self.pos, args=[ defaults_tuple = TupleNode(self.pos, args=[
arg.default for arg in default_args]) arg.default for arg in default_args])
self.defaults_tuple = defaults_tuple.analyse_types(env) 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: 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( defaults_getter = Nodes.DefNode(
self.pos, args=[], star_arg=None, starstar_arg=None, self.pos, args=[], star_arg=None, starstar_arg=None,
body=Nodes.ReturnStatNode( body=Nodes.ReturnStatNode(
self.pos, return_type=py_object_type, self.pos, return_type=py_object_type,
value=DefaultsTupleNode( value=TupleNode(
self.pos, default_args, self.pos, args=[defaults_tuple, defaults_kwdict])),
self.defaults_struct)), decorators=None,
decorators=None, name=StringEncoding.EncodedString("__defaults__")) name=StringEncoding.EncodedString("__defaults__"))
defaults_getter.analyse_declarations(env) defaults_getter.analyse_declarations(env)
defaults_getter = defaults_getter.analyse_expressions(env) defaults_getter = defaults_getter.analyse_expressions(env)
defaults_getter.body = defaults_getter.body.analyse_expressions( defaults_getter.body = defaults_getter.body.analyse_expressions(
...@@ -7161,6 +7186,9 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin): ...@@ -7161,6 +7186,9 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
if self.defaults_tuple: if self.defaults_tuple:
code.putln('__Pyx_CyFunction_SetDefaultsTuple(%s, %s);' % ( code.putln('__Pyx_CyFunction_SetDefaultsTuple(%s, %s);' % (
self.result(), self.defaults_tuple.py_result())) 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: if def_node.defaults_getter:
code.putln('__Pyx_CyFunction_SetDefaultsGetter(%s, %s);' % ( code.putln('__Pyx_CyFunction_SetDefaultsGetter(%s, %s);' % (
self.result(), def_node.defaults_getter.entry.pyfunc_cname)) self.result(), def_node.defaults_getter.entry.pyfunc_cname))
...@@ -7306,6 +7334,21 @@ class DefaultsTupleNode(TupleNode): ...@@ -7306,6 +7334,21 @@ class DefaultsTupleNode(TupleNode):
super(DefaultsTupleNode, self).__init__(pos, args=args) 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): class LambdaNode(InnerFunctionNode):
# Lambda expression node (only used as a function reference) # Lambda expression node (only used as a function reference)
# #
......
...@@ -37,6 +37,7 @@ typedef struct { ...@@ -37,6 +37,7 @@ typedef struct {
/* Defaults info */ /* Defaults info */
PyObject *defaults_tuple; /* Const defaults tuple */ PyObject *defaults_tuple; /* Const defaults tuple */
PyObject *defaults_kwdict; /* Const kwonly defaults dict */
PyObject *(*defaults_getter)(PyObject *); PyObject *(*defaults_getter)(PyObject *);
} __pyx_CyFunctionObject; } __pyx_CyFunctionObject;
...@@ -55,6 +56,8 @@ static CYTHON_INLINE void *__Pyx_CyFunction_InitDefaults(PyObject *m, ...@@ -55,6 +56,8 @@ static CYTHON_INLINE void *__Pyx_CyFunction_InitDefaults(PyObject *m,
int pyobjects); int pyobjects);
static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsTuple(PyObject *m, static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsTuple(PyObject *m,
PyObject *tuple); PyObject *tuple);
static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsKwDict(PyObject *m,
PyObject *dict);
static int __Pyx_CyFunction_init(void); static int __Pyx_CyFunction_init(void);
...@@ -235,27 +238,85 @@ __Pyx_CyFunction_get_code(__pyx_CyFunctionObject *op) ...@@ -235,27 +238,85 @@ __Pyx_CyFunction_get_code(__pyx_CyFunctionObject *op)
return result; return result;
} }
static PyObject * static int
__Pyx_CyFunction_get_defaults(__pyx_CyFunctionObject *op) __Pyx_CyFunction_init_defaults(__pyx_CyFunctionObject *op) {
{ PyObject *res = op->defaults_getter((PyObject *) op);
if (op->defaults_tuple) { if (unlikely(!res))
return -1;
/* Cache result */
op->defaults_tuple = PyTuple_GET_ITEM(res, 0);
Py_INCREF(op->defaults_tuple); Py_INCREF(op->defaults_tuple);
return 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) {
PyObject* result = op->defaults_tuple;
if (unlikely(!result)) {
if (op->defaults_getter) { if (op->defaults_getter) {
PyObject *res = op->defaults_getter((PyObject *) op); if (__Pyx_CyFunction_init_defaults(op) < 0) return NULL;
result = op->defaults_tuple;
/* Cache result */ } else {
if (likely(res)) { result = Py_None;
Py_INCREF(res); }
op->defaults_tuple = res;
} }
return res; Py_INCREF(result);
return result;
}
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;
}
Py_INCREF(Py_None); static PyObject *
return Py_None; __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;
}
}
Py_INCREF(result);
return result;
} }
static PyGetSetDef __pyx_CyFunction_getsets[] = { static PyGetSetDef __pyx_CyFunction_getsets[] = {
...@@ -273,8 +334,9 @@ 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 *) "__closure__", (getter)__Pyx_CyFunction_get_closure, 0, 0, 0},
{(char *) "func_code", (getter)__Pyx_CyFunction_get_code, 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 *) "__code__", (getter)__Pyx_CyFunction_get_code, 0, 0, 0},
{(char *) "func_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, 0, 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} {0, 0, 0, 0, 0}
}; };
...@@ -328,6 +390,7 @@ static PyObject *__Pyx_CyFunction_New(PyTypeObject *type, PyMethodDef *ml, int f ...@@ -328,6 +390,7 @@ static PyObject *__Pyx_CyFunction_New(PyTypeObject *type, PyMethodDef *ml, int f
op->defaults_pyobjects = 0; op->defaults_pyobjects = 0;
op->defaults = NULL; op->defaults = NULL;
op->defaults_tuple = NULL; op->defaults_tuple = NULL;
op->defaults_kwdict = NULL;
op->defaults_getter = NULL; op->defaults_getter = NULL;
PyObject_GC_Track(op); PyObject_GC_Track(op);
return (PyObject *) op; return (PyObject *) op;
...@@ -345,6 +408,7 @@ __Pyx_CyFunction_clear(__pyx_CyFunctionObject *m) ...@@ -345,6 +408,7 @@ __Pyx_CyFunction_clear(__pyx_CyFunctionObject *m)
Py_CLEAR(m->func_code); Py_CLEAR(m->func_code);
Py_CLEAR(m->func_classobj); Py_CLEAR(m->func_classobj);
Py_CLEAR(m->defaults_tuple); Py_CLEAR(m->defaults_tuple);
Py_CLEAR(m->defaults_kwdict);
if (m->defaults) { if (m->defaults) {
PyObject **pydefaults = __Pyx_CyFunction_Defaults(PyObject *, m); PyObject **pydefaults = __Pyx_CyFunction_Defaults(PyObject *, m);
...@@ -380,6 +444,7 @@ static int __Pyx_CyFunction_traverse(__pyx_CyFunctionObject *m, visitproc visit, ...@@ -380,6 +444,7 @@ static int __Pyx_CyFunction_traverse(__pyx_CyFunctionObject *m, visitproc visit,
Py_VISIT(m->func_code); Py_VISIT(m->func_code);
Py_VISIT(m->func_classobj); Py_VISIT(m->func_classobj);
Py_VISIT(m->defaults_tuple); Py_VISIT(m->defaults_tuple);
Py_VISIT(m->defaults_kwdict);
if (m->defaults) { if (m->defaults) {
PyObject **pydefaults = __Pyx_CyFunction_Defaults(PyObject *, m); PyObject **pydefaults = __Pyx_CyFunction_Defaults(PyObject *, m);
...@@ -565,6 +630,12 @@ static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsTuple(PyObject *func, PyOb ...@@ -565,6 +630,12 @@ static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsTuple(PyObject *func, PyOb
Py_INCREF(tuple); 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 //////////////////// //////////////////// CyFunctionClassCell.proto ////////////////////
static CYTHON_INLINE void __Pyx_CyFunction_InitClassCell(PyObject *cyfunctions, static CYTHON_INLINE void __Pyx_CyFunction_InitClassCell(PyObject *cyfunctions,
PyObject *classobj); PyObject *classobj);
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
cimport cython cimport cython
import sys import sys
IS_PY3 = sys.version_info[0] >= 3
def get_defaults(func): def get_defaults(func):
if sys.version_info >= (2, 6, 0): if sys.version_info >= (2, 6, 0):
return func.__defaults__ return func.__defaults__
...@@ -86,6 +88,49 @@ def test_defaults_nonliteral_func_call(f): ...@@ -86,6 +88,49 @@ def test_defaults_nonliteral_func_call(f):
return a return a
return func 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 _counter2 = 1.0
def counter2(): def counter2():
global _counter2 global _counter2
......
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