Repository: cython/cython

Issue:
Python 2: __qualname__ is wrong (when subclassed or on metaclass)
When defining a Python `class` using Cython, the `__qualname__` attribute is always set, even on Python 2. When this Cython-defined class is either subclassed or used as metaclass in pure Python code, that new class gets the wrong `__qualname__` from Cython.

Examples:
```
# In Cython
class X(type): pass
```
```
# In Python
>>> class Y(X): pass
>>> Y.__qualname__
X
```
```
# In Python
>>> class Y(object): __metaclass__ = X
>>> Y.__qualname__
X
```
As you can see, we get a wrong `__qualname__` in both cases. This confuses for example tools like Sphinx, where it produces incorrect output.

Since `__qualname__` is useless on Python 2, I suggest to simply not set `__qualname__` on Python 2. For Sphinx, not having a `__qualname__` is fine, since it will simply use `__name__` which is correct.


Discussion:
Personally, I consider `__qualname__` a useful debugging feature all by itself, so I'd prefer keeping it if/where possible. But the problem here is that Python doesn't override it for its own subtypes, so I understand that it gets in the way for classes. Functions should be ok (although, see #2562).

Should we remove it only from classes in Py2?

Resolving patch:
diff --git a/runtests.py b/runtests.py
--- a/runtests.py
+++ b/runtests.py
@@ -428,6 +428,7 @@ def get_openmp_compiler_flags(language):
     (3,3) : (operator.lt, lambda x: x in ['build.package_compilation',
                                           'run.yield_from_py33',
                                           'pyximport.pyximport_namespace',
+                                          'run.qualname',
                                           ]),
     (3,4): (operator.lt, lambda x: x in ['run.py34_signature',
                                          'run.test_unicode',  # taken from Py3.7, difficult to backport


Test patch:
diff --git a/tests/run/decorators_T593.pyx b/tests/run/decorators_T593.pyx
--- a/tests/run/decorators_T593.pyx
+++ b/tests/run/decorators_T593.pyx
@@ -110,8 +110,8 @@ class Base(type):
 
 class Bar(metaclass=Base):
    """
-   >>> Bar._order
-   ['__module__', '__qualname__', '__doc__', 'bar']
+   >>> [n for n in Bar._order if n != "__qualname__"]
+   ['__module__', '__doc__', 'bar']
    """
    @property
    def bar(self):
diff --git a/tests/run/locals_T732.pyx b/tests/run/locals_T732.pyx
--- a/tests/run/locals_T732.pyx
+++ b/tests/run/locals_T732.pyx
@@ -23,8 +23,8 @@ def test_class_locals_and_dir():
     >>> klass = test_class_locals_and_dir()
     >>> 'visible' in klass.locs and 'not_visible' not in klass.locs
     True
-    >>> klass.names
-    ['__module__', '__qualname__', 'visible']
+    >>> [n for n in klass.names if n != "__qualname__"]
+    ['__module__', 'visible']
     """
     not_visible = 1234
     class Foo:
diff --git a/tests/run/metaclass.pyx b/tests/run/metaclass.pyx
--- a/tests/run/metaclass.pyx
+++ b/tests/run/metaclass.pyx
@@ -69,8 +69,8 @@ class Py3ClassMCOnly(object, metaclass=Py3MetaclassPlusAttr):
     321
     >>> obj.metaclass_was_here
     True
-    >>> obj._order
-    ['__module__', '__qualname__', '__doc__', 'bar', 'metaclass_was_here']
+    >>> [n for n in obj._order if n != "__qualname__"]
+    ['__module__', '__doc__', 'bar', 'metaclass_was_here']
     """
     bar = 321
 
@@ -81,8 +81,8 @@ class Py3InheritedMetaclass(Py3ClassMCOnly):
     345
     >>> obj.metaclass_was_here
     True
-    >>> obj._order
-    ['__module__', '__qualname__', '__doc__', 'bar', 'metaclass_was_here']
+    >>> [n for n in obj._order if n != "__qualname__"]
+    ['__module__', '__doc__', 'bar', 'metaclass_was_here']
     """
     bar = 345
 
@@ -109,8 +109,8 @@ class Py3Foo(object, metaclass=Py3Base, foo=123):
     123
     >>> obj.bar
     321
-    >>> obj._order
-    ['__module__', '__qualname__', '__doc__', 'bar', 'foo']
+    >>> [n for n in obj._order if n != "__qualname__"]
+    ['__module__', '__doc__', 'bar', 'foo']
     """
     bar = 321
 
@@ -122,8 +122,8 @@ class Py3FooInherited(Py3Foo, foo=567):
     567
     >>> obj.bar
     321
-    >>> obj._order
-    ['__module__', '__qualname__', '__doc__', 'bar', 'foo']
+    >>> [n for n in obj._order if n != "__qualname__"]
+    ['__module__', '__doc__', 'bar', 'foo']
     """
     bar = 321