implement __qualname__ special attribute on Python functions/classes (PEP 3155)
authorStefan Behnel <stefan_ml@behnel.de>
Fri, 4 Jan 2013 15:39:01 +0000 (16:39 +0100)
committerStefan Behnel <stefan_ml@behnel.de>
Fri, 4 Jan 2013 15:39:01 +0000 (16:39 +0100)
CHANGES.rst
Cython/Compiler/ExprNodes.py
Cython/Utility/CythonFunction.c
Cython/Utility/ObjectHandling.c
tests/run/cyfunction.pyx
tests/run/metaclass.pyx

index 4277183..6aa349f 100644 (file)
@@ -8,6 +8,9 @@ Cython Changelog
 Features added
 --------------
 
+* Python functions/classes provide the special attribute "__qualname__"
+  as defined by PEP 3155.
+
 * Added a directive ``overflowcheck`` which raises an OverflowException when
   arithmetic with C ints overflow.  This has a modest performance penalty, but
   is much faster than using Python ints.
index 0990da8..3c4d865 100755 (executable)
@@ -6017,13 +6017,22 @@ class DictItemNode(ExprNode):
 
 
 class ModuleNameMixin(object):
-    def set_mod_name(self, env):
+    def set_qualified_name(self, env, self_name):
         self.module_name = env.global_scope().qualified_name
+        prefix = env.qualified_name[len(self.module_name)+1:]
+        if prefix:
+            self_name = prefix + '.' + self_name
+        self.qualname = StringEncoding.EncodedString(self_name)
 
     def get_py_mod_name(self, code):
         return code.get_py_string_const(
                  self.module_name, identifier=True)
 
+    def get_py_qualified_name(self, code):
+        return code.get_py_string_const(
+            self.qualname, identifier=True)
+
+
 class ClassNode(ExprNode, ModuleNameMixin):
     #  Helper class used in the implementation of Python
     #  class definitions. Constructs a class object given
@@ -6046,7 +6055,7 @@ class ClassNode(ExprNode, ModuleNameMixin):
         self.is_temp = 1
         env.use_utility_code(UtilityCode.load_cached("CreateClass", "ObjectHandling.c"))
         #TODO(craig,haoyu) This should be moved to a better place
-        self.set_mod_name(env)
+        self.set_qualified_name(env, self.name)
 
     def may_be_none(self):
         return True
@@ -6062,12 +6071,14 @@ class ClassNode(ExprNode, ModuleNameMixin):
                     self.dict.py_result(),
                     self.doc.py_result()))
         py_mod_name = self.get_py_mod_name(code)
+        qualname = self.get_py_qualified_name(code)
         code.putln(
-            '%s = __Pyx_CreateClass(%s, %s, %s, %s); %s' % (
+            '%s = __Pyx_CreateClass(%s, %s, %s, %s, %s); %s' % (
                 self.result(),
                 self.bases.py_result(),
                 self.dict.py_result(),
                 cname,
+                qualname,
                 py_mod_name,
                 code.error_goto_if_null(self.result(), self.pos)))
         code.put_gotref(self.py_result())
@@ -6264,7 +6275,7 @@ class PyClassNamespaceNode(ExprNode, ModuleNameMixin):
         self.type = py_object_type
         self.is_temp = 1
         #TODO(craig,haoyu) This should be moved to a better place
-        self.set_mod_name(env)
+        self.set_qualified_name(env, self.name)
 
     def may_be_none(self):
         return True
@@ -6272,16 +6283,18 @@ class PyClassNamespaceNode(ExprNode, ModuleNameMixin):
     def generate_result_code(self, code):
         cname = code.intern_identifier(self.name)
         py_mod_name = self.get_py_mod_name(code)
+        qualname = self.get_py_qualified_name(code)
         if self.doc:
             doc_code = self.doc.result()
         else:
             doc_code = '(PyObject *) NULL'
         code.putln(
-            "%s = __Pyx_Py3MetaclassPrepare(%s, %s, %s, %s, %s, %s); %s" % (
+            "%s = __Pyx_Py3MetaclassPrepare(%s, %s, %s, %s, %s, %s, %s); %s" % (
                 self.result(),
                 self.metaclass.result(),
                 self.bases.result(),
                 cname,
+                qualname,
                 self.mkw.result(),
                 py_mod_name,
                 doc_code,
@@ -6449,7 +6462,7 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
             self.analyse_default_args(env)
 
         #TODO(craig,haoyu) This should be moved to a better place
-        self.set_mod_name(env)
+        self.set_qualified_name(env, self.def_node.name)
 
     def analyse_default_args(self, env):
         """
@@ -6575,15 +6588,15 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
         else:
             flags = '0'
 
-        py_mod_name = self.get_py_mod_name(code)
         code.putln(
-            '%s = %s(&%s, %s, %s, %s, %s); %s' % (
+            '%s = %s(&%s, %s, %s, %s, %s, %s); %s' % (
                 self.result(),
                 constructor,
                 self.pymethdef_cname,
                 flags,
+                self.get_py_qualified_name(code),
                 self.self_result_code(),
-                py_mod_name,
+                self.get_py_mod_name(code),
                 code_object_result,
                 code.error_goto_if_null(self.result(), self.pos)))
 
index 909c512..27487b2 100644 (file)
@@ -25,6 +25,7 @@ typedef struct {
     PyObject *func_dict;
     PyObject *func_weakreflist;
     PyObject *func_name;
+    PyObject *func_qualname;
     PyObject *func_doc;
     PyObject *func_code;
     PyObject *func_closure;
@@ -41,11 +42,11 @@ typedef struct {
 
 static PyTypeObject *__pyx_CyFunctionType = 0;
 
-#define __Pyx_CyFunction_NewEx(ml, flags, self, module, code) \
-    __Pyx_CyFunction_New(__pyx_CyFunctionType, ml, flags, self, module, code)
+#define __Pyx_CyFunction_NewEx(ml, flags, qualname, self, module, code) \
+    __Pyx_CyFunction_New(__pyx_CyFunctionType, ml, flags, qualname, self, module, code)
 
-static PyObject *__Pyx_CyFunction_New(PyTypeObject *,
-                                      PyMethodDef *ml, int flags,
+static PyObject *__Pyx_CyFunction_New(PyTypeObject *, PyMethodDef *ml,
+                                      int flags, PyObject* qualname,
                                       PyObject *self, PyObject *module,
                                       PyObject* code);
 
@@ -128,6 +129,34 @@ __Pyx_CyFunction_set_name(__pyx_CyFunctionObject *op, PyObject *value)
 }
 
 static PyObject *
+__Pyx_CyFunction_get_qualname(__pyx_CyFunctionObject *op)
+{
+    Py_INCREF(op->func_qualname);
+    return op->func_qualname;
+}
+
+static int
+__Pyx_CyFunction_set_qualname(__pyx_CyFunctionObject *op, PyObject *value)
+{
+    PyObject *tmp;
+
+#if PY_MAJOR_VERSION >= 3
+    if (value == NULL || !PyUnicode_Check(value)) {
+#else
+    if (value == NULL || !PyString_Check(value)) {
+#endif
+        PyErr_SetString(PyExc_TypeError,
+                        "__qualname__ must be set to a string object");
+        return -1;
+    }
+    tmp = op->func_qualname;
+    Py_INCREF(value);
+    op->func_qualname = value;
+    Py_XDECREF(tmp);
+    return 0;
+}
+
+static PyObject *
 __Pyx_CyFunction_get_self(__pyx_CyFunctionObject *m, CYTHON_UNUSED void *closure)
 {
     PyObject *self;
@@ -230,6 +259,7 @@ static PyGetSetDef __pyx_CyFunction_getsets[] = {
     {(char *) "__doc__",  (getter)__Pyx_CyFunction_get_doc, (setter)__Pyx_CyFunction_set_doc, 0, 0},
     {(char *) "func_name", (getter)__Pyx_CyFunction_get_name, (setter)__Pyx_CyFunction_set_name, 0, 0},
     {(char *) "__name__", (getter)__Pyx_CyFunction_get_name, (setter)__Pyx_CyFunction_set_name, 0, 0},
+    {(char *) "__qualname__", (getter)__Pyx_CyFunction_get_qualname, (setter)__Pyx_CyFunction_set_qualname, 0, 0},
     {(char *) "__self__", (getter)__Pyx_CyFunction_get_self, 0, 0, 0},
     {(char *) "func_dict", (getter)__Pyx_CyFunction_get_dict, (setter)__Pyx_CyFunction_set_dict, 0, 0},
     {(char *) "__dict__", (getter)__Pyx_CyFunction_get_dict, (setter)__Pyx_CyFunction_set_dict, 0, 0},
@@ -269,7 +299,7 @@ static PyMethodDef __pyx_CyFunction_methods[] = {
 };
 
 
-static PyObject *__Pyx_CyFunction_New(PyTypeObject *type, PyMethodDef *ml, int flags,
+static PyObject *__Pyx_CyFunction_New(PyTypeObject *type, PyMethodDef *ml, int flags, PyObject* qualname,
                                       PyObject *closure, PyObject *module, PyObject* code) {
     __pyx_CyFunctionObject *op = PyObject_GC_New(__pyx_CyFunctionObject, type);
     if (op == NULL)
@@ -284,6 +314,8 @@ static PyObject *__Pyx_CyFunction_New(PyTypeObject *type, PyMethodDef *ml, int f
     op->func.m_module = module;
     op->func_dict = NULL;
     op->func_name = NULL;
+    Py_INCREF(qualname);
+    op->func_qualname = qualname;
     op->func_doc = NULL;
     op->func_classobj = NULL;
     Py_XINCREF(code);
@@ -304,6 +336,7 @@ __Pyx_CyFunction_clear(__pyx_CyFunctionObject *m)
     Py_CLEAR(m->func.m_module);
     Py_CLEAR(m->func_dict);
     Py_CLEAR(m->func_name);
+    Py_CLEAR(m->func_qualname);
     Py_CLEAR(m->func_doc);
     Py_CLEAR(m->func_code);
     Py_CLEAR(m->func_classobj);
@@ -338,6 +371,7 @@ static int __Pyx_CyFunction_traverse(__pyx_CyFunctionObject *m, visitproc visit,
     Py_VISIT(m->func.m_module);
     Py_VISIT(m->func_dict);
     Py_VISIT(m->func_name);
+    Py_VISIT(m->func_qualname);
     Py_VISIT(m->func_doc);
     Py_VISIT(m->func_code);
     Py_VISIT(m->func_classobj);
@@ -378,14 +412,12 @@ static PyObject *__Pyx_CyFunction_descr_get(PyObject *func, PyObject *obj, PyObj
 static PyObject*
 __Pyx_CyFunction_repr(__pyx_CyFunctionObject *op)
 {
-    PyObject *func_name = __Pyx_CyFunction_get_name(op);
-
 #if PY_MAJOR_VERSION >= 3
     return PyUnicode_FromFormat("<cyfunction %U at %p>",
-                                func_name, (void *)op);
+                                op->func_qualname, (void *)op);
 #else
     return PyString_FromFormat("<cyfunction %s at %p>",
-                               PyString_AsString(func_name), (void *)op);
+                               PyString_AsString(op->func_qualname), (void *)op);
 #endif
 }
 
@@ -555,11 +587,11 @@ typedef struct {
     PyObject *self;
 } __pyx_FusedFunctionObject;
 
-#define __pyx_FusedFunction_NewEx(ml, flags, self, module, code)         \
-        __pyx_FusedFunction_New(__pyx_FusedFunctionType, ml, flags, self, module, code)
+#define __pyx_FusedFunction_NewEx(ml, flags, qualname, self, module, code)         \
+        __pyx_FusedFunction_New(__pyx_FusedFunctionType, ml, flags, qualname, self, module, code)
 static PyObject *__pyx_FusedFunction_New(PyTypeObject *type,
                                          PyMethodDef *ml, int flags,
-                                         PyObject *self, PyObject *module,
+                                         PyObject *qualname, PyObject *self, PyObject *module,
                                          PyObject *code);
 
 static PyTypeObject *__pyx_FusedFunctionType = NULL;
@@ -571,11 +603,12 @@ static int __pyx_FusedFunction_init(void);
 //@requires: CythonFunction
 
 static PyObject *
-__pyx_FusedFunction_New(PyTypeObject *type, PyMethodDef *ml, int flags, PyObject *self,
+__pyx_FusedFunction_New(PyTypeObject *type, PyMethodDef *ml, int flags,
+                        PyObject *qualname, PyObject *self,
                         PyObject *module, PyObject *code)
 {
     __pyx_FusedFunctionObject *fusedfunc =
-        (__pyx_FusedFunctionObject *) __Pyx_CyFunction_New(type, ml, flags,
+        (__pyx_FusedFunctionObject *) __Pyx_CyFunction_New(type, ml, flags, qualname,
                                                            self, module, code);
     if (!fusedfunc)
         return NULL;
@@ -632,6 +665,7 @@ __pyx_FusedFunction_descr_get(PyObject *self, PyObject *obj, PyObject *type)
     meth = (__pyx_FusedFunctionObject *) __pyx_FusedFunction_NewEx(
                     ((PyCFunctionObject *) func)->m_ml,
                     ((__pyx_CyFunctionObject *) func)->flags,
+                    ((__pyx_CyFunctionObject *) func)->func_qualname,
                     ((__pyx_CyFunctionObject *) func)->func_closure,
                     ((PyCFunctionObject *) func)->m_module,
                     ((__pyx_CyFunctionObject *) func)->func_code);
index 5ffd5e3..2da2148 100644 (file)
@@ -443,18 +443,20 @@ static PyObject *__Pyx_Py3MetaclassGet(PyObject *bases, PyObject *mkw) {
 /////////////// CreateClass.proto ///////////////
 
 static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *name,
-                                   PyObject *modname); /*proto*/
+                                   PyObject *qualname, PyObject *modname); /*proto*/
 
 /////////////// CreateClass ///////////////
 //@requires: FindPy2Metaclass
 
 static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *name,
-                                   PyObject *modname) {
+                                   PyObject *qualname, PyObject *modname) {
     PyObject *result;
     PyObject *metaclass;
 
     if (PyDict_SetItemString(dict, "__module__", modname) < 0)
         return NULL;
+    if (PyDict_SetItemString(dict, "__qualname__", qualname) < 0)
+        return NULL;
 
     /* Python2 __metaclass__ */
     metaclass = PyDict_GetItemString(dict, "__metaclass__");
@@ -470,13 +472,13 @@ static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *na
 
 /////////////// Py3ClassCreate.proto ///////////////
 
-static PyObject *__Pyx_Py3MetaclassPrepare(PyObject *metaclass, PyObject *bases, PyObject *name, PyObject *mkw, PyObject *modname, PyObject *doc); /*proto*/
+static PyObject *__Pyx_Py3MetaclassPrepare(PyObject *metaclass, PyObject *bases, PyObject *name, PyObject *qualname, PyObject *mkw, PyObject *modname, PyObject *doc); /*proto*/
 static PyObject *__Pyx_Py3ClassCreate(PyObject *metaclass, PyObject *name, PyObject *bases, PyObject *dict, PyObject *mkw); /*proto*/
 
 /////////////// Py3ClassCreate ///////////////
 
 static PyObject *__Pyx_Py3MetaclassPrepare(PyObject *metaclass, PyObject *bases, PyObject *name,
-                                           PyObject *mkw, PyObject *modname, PyObject *doc) {
+                                           PyObject *qualname, PyObject *mkw, PyObject *modname, PyObject *doc) {
     PyObject *prep;
     PyObject *pargs;
     PyObject *ns;
@@ -519,6 +521,24 @@ static PyObject *__Pyx_Py3MetaclassPrepare(PyObject *metaclass, PyObject *bases,
         return NULL;
     }
     Py_DECREF(str);
+
+    #if PY_MAJOR_VERSION >= 3
+    str = PyUnicode_FromString("__qualname__");
+    #else
+    str = PyString_FromString("__qualname__");
+    #endif
+    if (!str) {
+        Py_DECREF(ns);
+        return NULL;
+    }
+
+    if (PyObject_SetItem(ns, str, qualname) < 0) {
+        Py_DECREF(ns);
+        Py_DECREF(str);
+        return NULL;
+    }
+    Py_DECREF(str);
+
     if (doc) {
         #if PY_MAJOR_VERSION >= 3
         str = PyUnicode_FromString("__doc__");
index 2185f09..2c3e024 100644 (file)
@@ -31,6 +31,38 @@ def test_name():
     'foo'
     """
 
+
+def test_qualname():
+    """
+    >>> test_qualname.__qualname__
+    'test_qualname'
+    >>> test_qualname.__qualname__ = 123 #doctest:+ELLIPSIS
+    Traceback (most recent call last):
+    TypeError: __qualname__ must be set to a ... object
+    >>> test_qualname.__qualname__ = 'foo'
+    >>> test_qualname.__qualname__
+    'foo'
+    """
+
+
+def test_nested_qualname():
+    """
+    >>> func = test_nested_qualname()
+    >>> func().__qualname__
+    'test_nested_qualname.outer.Test'
+    >>> func().test.__qualname__
+    'test_nested_qualname.outer.Test.test'
+    >>> func()().test.__qualname__
+    'test_nested_qualname.outer.Test.test'
+    """
+    def outer():
+        class Test(object):
+            def test(self):
+                return 123
+        return Test
+    return outer
+
+
 def test_doc():
     """
     >>> del test_doc.__doc__
index 3247e2f..81c7a7a 100644 (file)
@@ -49,7 +49,7 @@ class Py3ClassMCOnly(object, metaclass=Py3MetaclassPlusAttr):
     >>> obj.metaclass_was_here
     True
     >>> obj._order
-    ['__module__', '__doc__', 'bar', 'metaclass_was_here']
+    ['__module__', '__qualname__', '__doc__', 'bar', 'metaclass_was_here']
     """
     bar = 321
 
@@ -76,7 +76,7 @@ class Py3Foo(object, metaclass=Py3Base, foo=123):
     >>> obj.bar
     321
     >>> obj._order
-    ['__module__', '__doc__', 'bar', 'foo']
+    ['__module__', '__qualname__', '__doc__', 'bar', 'foo']
     """
     bar = 321