Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

implement __annotations__ attribute on CyFunction (PEP 3107)

  • Loading branch information...
commit 717df7904650fe194bb0a0384f1b36a2d394382b 1 parent ea569ef
@scoder scoder authored
View
3  CHANGES.rst
@@ -8,6 +8,9 @@ Cython Changelog
Features added
--------------
+* Cython implemented functions make their argument and return type annotations
+ available through the ``__annotations__`` attribute (PEP 3107).
+
* Access to non-cdef module globals and Python object attributes is faster.
* ``Py_UNICODE*`` coerces from and to Python unicode strings. This is
View
27 Cython/Compiler/ExprNodes.py
@@ -6952,7 +6952,7 @@ def generate_result_code(self, code):
class PyCFunctionNode(ExprNode, ModuleNameMixin):
# Helper class used in the implementation of Python
- # class definitions. Constructs a PyCFunction object
+ # functions. Constructs a PyCFunction object
# from a PyMethodDef struct.
#
# pymethdef_cname string PyMethodDef structure
@@ -6962,7 +6962,8 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
# module_name EncodedString Name of defining module
# code_object CodeObjectNode the PyCodeObject creator node
- subexprs = ['code_object', 'defaults_tuple', 'defaults_kwdict']
+ subexprs = ['code_object', 'defaults_tuple', 'defaults_kwdict',
+ 'annotations_dict']
self_object = None
code_object = None
@@ -6973,6 +6974,7 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
defaults_pyobjects = 0
defaults_tuple = None
defaults_kwdict = None
+ annotations_dict = None
type = py_object_type
is_temp = 1
@@ -7004,6 +7006,7 @@ def analyse_default_args(self, env):
nonliteral_other = []
default_args = []
default_kwargs = []
+ annotations = []
for arg in self.def_node.args:
if arg.default:
if not arg.default.is_literal:
@@ -7018,6 +7021,16 @@ def analyse_default_args(self, env):
default_kwargs.append(arg)
else:
default_args.append(arg)
+ if arg.annotation:
+ arg.annotation = arg.annotation.analyse_types(env)
+ if not arg.annotation.type.is_pyobject:
+ arg.annotation = arg.annotation.coerce_to_pyobject(env)
+ annotations.append((arg.pos, arg.name, arg.annotation))
+ if self.def_node.return_type_annotation:
+ annotations.append((self.def_node.return_type_annotation.pos,
+ StringEncoding.EncodedString("return"),
+ self.def_node.return_type_annotation))
+
if nonliteral_objects or nonliteral_other:
module_scope = env.global_scope()
cname = module_scope.next_id(Naming.defaults_struct_prefix)
@@ -7083,6 +7096,13 @@ def analyse_default_args(self, env):
defaults_getter.py_wrapper_required = False
defaults_getter.pymethdef_required = False
self.def_node.defaults_getter = defaults_getter
+ if annotations:
+ annotations_dict = DictNode(self.pos, key_value_pairs=[
+ DictItemNode(
+ pos, key=IdentifierStringNode(pos, value=name),
+ value=value)
+ for pos, name, value in annotations])
+ self.annotations_dict = annotations_dict.analyse_types(env)
def may_be_none(self):
return False
@@ -7192,6 +7212,9 @@ def generate_cyfunction_code(self, code):
if def_node.defaults_getter:
code.putln('__Pyx_CyFunction_SetDefaultsGetter(%s, %s);' % (
self.result(), def_node.defaults_getter.entry.pyfunc_cname))
+ if self.annotations_dict:
+ code.putln('__Pyx_CyFunction_SetAnnotationsDict(%s, %s);' % (
+ self.result(), self.annotations_dict.py_result()))
class InnerFunctionNode(PyCFunctionNode):
View
2  Cython/Compiler/Nodes.py
@@ -729,7 +729,7 @@ class CArgDeclNode(Node):
# is_kw_only boolean Is a keyword-only argument
# is_dynamic boolean Non-literal arg stored inside CyFunction
- child_attrs = ["base_type", "declarator", "default"]
+ child_attrs = ["base_type", "declarator", "default", "annotation"]
is_self_arg = 0
is_type_arg = 0
View
47 Cython/Utility/CythonFunction.c
@@ -31,14 +31,15 @@ typedef struct {
PyObject *func_closure;
PyObject *func_classobj; /* No-args super() class cell */
- /* Dynamic default args*/
+ /* Dynamic default args and annotations */
void *defaults;
int defaults_pyobjects;
/* Defaults info */
- PyObject *defaults_tuple; /* Const defaults tuple */
- PyObject *defaults_kwdict; /* Const kwonly defaults dict */
+ PyObject *defaults_tuple; /* Const defaults tuple */
+ PyObject *defaults_kwdict; /* Const kwonly defaults dict */
PyObject *(*defaults_getter)(PyObject *);
+ PyObject *func_annotations; /* function annotations dict */
} __pyx_CyFunctionObject;
static PyTypeObject *__pyx_CyFunctionType = 0;
@@ -58,6 +59,8 @@ static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsTuple(PyObject *m,
PyObject *tuple);
static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsKwDict(PyObject *m,
PyObject *dict);
+static CYTHON_INLINE void __Pyx_CyFunction_SetAnnotationsDict(PyObject *m,
+ PyObject *dict);
static int __Pyx_CyFunction_init(void);
@@ -319,6 +322,35 @@ __Pyx_CyFunction_get_kwdefaults(__pyx_CyFunctionObject *op) {
return result;
}
+static int
+__Pyx_CyFunction_set_annotations(__pyx_CyFunctionObject *op, PyObject* value) {
+ PyObject* tmp;
+ if (!value || value == Py_None) {
+ value = NULL;
+ } else if (!PyDict_Check(value)) {
+ PyErr_SetString(PyExc_TypeError,
+ "__annotations__ must be set to a dict object");
+ return -1;
+ }
+ Py_XINCREF(value);
+ tmp = op->func_annotations;
+ op->func_annotations = value;
+ Py_XDECREF(tmp);
+ return 0;
+}
+
+static PyObject *
+__Pyx_CyFunction_get_annotations(__pyx_CyFunctionObject *op) {
+ PyObject* result = op->func_annotations;
+ if (unlikely(!result)) {
+ result = PyDict_New();
+ if (unlikely(!result)) return NULL;
+ op->func_annotations = result;
+ }
+ Py_INCREF(result);
+ return result;
+}
+
static PyGetSetDef __pyx_CyFunction_getsets[] = {
{(char *) "func_doc", (getter)__Pyx_CyFunction_get_doc, (setter)__Pyx_CyFunction_set_doc, 0, 0},
{(char *) "__doc__", (getter)__Pyx_CyFunction_get_doc, (setter)__Pyx_CyFunction_set_doc, 0, 0},
@@ -337,6 +369,7 @@ static PyGetSetDef __pyx_CyFunction_getsets[] = {
{(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},
+ {(char *) "__annotations__", (getter)__Pyx_CyFunction_get_annotations, (setter)__Pyx_CyFunction_set_annotations, 0, 0},
{0, 0, 0, 0, 0}
};
@@ -392,6 +425,7 @@ static PyObject *__Pyx_CyFunction_New(PyTypeObject *type, PyMethodDef *ml, int f
op->defaults_tuple = NULL;
op->defaults_kwdict = NULL;
op->defaults_getter = NULL;
+ op->func_annotations = NULL;
PyObject_GC_Track(op);
return (PyObject *) op;
}
@@ -409,6 +443,7 @@ __Pyx_CyFunction_clear(__pyx_CyFunctionObject *m)
Py_CLEAR(m->func_classobj);
Py_CLEAR(m->defaults_tuple);
Py_CLEAR(m->defaults_kwdict);
+ Py_CLEAR(m->func_annotations);
if (m->defaults) {
PyObject **pydefaults = __Pyx_CyFunction_Defaults(PyObject *, m);
@@ -636,6 +671,12 @@ static CYTHON_INLINE void __Pyx_CyFunction_SetDefaultsKwDict(PyObject *func, PyO
Py_INCREF(dict);
}
+static CYTHON_INLINE void __Pyx_CyFunction_SetAnnotationsDict(PyObject *func, PyObject *dict) {
+ __pyx_CyFunctionObject *m = (__pyx_CyFunctionObject *) func;
+ m->func_annotations = dict;
+ Py_INCREF(dict);
+}
+
//////////////////// CyFunctionClassCell.proto ////////////////////
static CYTHON_INLINE void __Pyx_CyFunction_InitClassCell(PyObject *cyfunctions,
PyObject *classobj);
View
41 tests/run/cyfunction.pyx
@@ -220,3 +220,44 @@ def test_code():
>>> codeof(cy_default_args).co_varnames
('x', 'b', 'l', 'm')
"""
+
+
+def test_annotations(a: "test", b: "other" = 2, c: 123 = 4) -> "ret":
+ """
+ >>> isinstance(test_annotations.__annotations__, dict)
+ True
+ >>> sorted(test_annotations.__annotations__.items())
+ [('a', 'test'), ('b', 'other'), ('c', 123), ('return', 'ret')]
+
+ >>> def func_b(): return 42
+ >>> def func_c(): return 99
+ >>> inner = test_annotations(1, func_b, func_c)
+ >>> sorted(inner.__annotations__.items())
+ [('return', 99), ('x', 'banana'), ('y', 42)]
+
+ >>> inner.__annotations__ = {234: 567}
+ >>> inner.__annotations__
+ {234: 567}
+ >>> inner.__annotations__ = None
+ >>> inner.__annotations__
+ {}
+ >>> inner.__annotations__ = 321
+ Traceback (most recent call last):
+ TypeError: __annotations__ must be set to a dict object
+ >>> inner.__annotations__
+ {}
+
+ >>> inner = test_annotations(1, func_b, func_c)
+ >>> sorted(inner.__annotations__.items())
+ [('return', 99), ('x', 'banana'), ('y', 42)]
+ >>> inner.__annotations__['abc'] = 66
+ >>> sorted(inner.__annotations__.items())
+ [('abc', 66), ('return', 99), ('x', 'banana'), ('y', 42)]
+
+ >>> inner = test_annotations(1, func_b, func_c)
+ >>> sorted(inner.__annotations__.items())
+ [('return', 99), ('x', 'banana'), ('y', 42)]
+ """
+ def inner(x: "banana", y: b()) -> c():
+ return x,y
+ return inner
Please sign in to comment.
Something went wrong with that request. Please try again.