use more CPython-like (and potentially faster) _PyType_Lookup() for internal special...
authorStefan Behnel <stefan_ml@behnel.de>
Fri, 22 Nov 2013 18:48:18 +0000 (19:48 +0100)
committerStefan Behnel <stefan_ml@behnel.de>
Fri, 22 Nov 2013 18:48:18 +0000 (19:48 +0100)
Cython/Compiler/ExprNodes.py
Cython/Compiler/Nodes.py
Cython/Compiler/ParseTreeTransforms.py
Cython/Utility/ObjectHandling.c
tests/run/pyclass_special_methods.py [new file with mode: 0644]

index d3e8098..8766ab4 100644 (file)
@@ -4991,6 +4991,7 @@ class AttributeNode(ExprNode):
     is_called = 0
     needs_none_check = True
     is_memslice_transpose = False
+    is_special_lookup = False
 
     def as_cython_attribute(self):
         if (isinstance(self.obj, NameNode) and
@@ -5359,11 +5360,18 @@ class AttributeNode(ExprNode):
 
     def generate_result_code(self, code):
         if self.is_py_attr:
-            code.globalstate.use_utility_code(
-                UtilityCode.load_cached("PyObjectGetAttrStr", "ObjectHandling.c"))
+            if self.is_special_lookup:
+                code.globalstate.use_utility_code(
+                    UtilityCode.load_cached("PyObjectLookupSpecial", "ObjectHandling.c"))
+                lookup_func_name = '__Pyx_PyObject_LookupSpecial'
+            else:
+                code.globalstate.use_utility_code(
+                    UtilityCode.load_cached("PyObjectGetAttrStr", "ObjectHandling.c"))
+                lookup_func_name = '__Pyx_PyObject_GetAttrStr'
             code.putln(
-                '%s = __Pyx_PyObject_GetAttrStr(%s, %s); %s' % (
+                '%s = %s(%s, %s); %s' % (
                     self.result(),
+                    lookup_func_name,
                     self.obj.py_result(),
                     code.intern_identifier(self.attribute),
                     code.error_goto_if_null(self.result(), self.pos)))
index c1986c0..4ac0b77 100644 (file)
@@ -5919,8 +5919,8 @@ class WithStatNode(StatNode):
         self.manager.generate_evaluation_code(code)
         self.exit_var = code.funcstate.allocate_temp(py_object_type, manage_ref=False)
         code.globalstate.use_utility_code(
-            UtilityCode.load_cached("PyObjectGetAttrStr", "ObjectHandling.c"))
-        code.putln("%s = __Pyx_PyObject_GetAttrStr(%s, %s); %s" % (
+            UtilityCode.load_cached("PyObjectLookupSpecial", "ObjectHandling.c"))
+        code.putln("%s = __Pyx_PyObject_LookupSpecial(%s, %s); %s" % (
             self.exit_var,
             self.manager.py_result(),
             code.intern_identifier(EncodedString('__exit__')),
index 9861022..3cc9dd7 100644 (file)
@@ -1212,11 +1212,12 @@ class WithTransform(CythonTransform, SkipDeclarations):
         pos = node.pos
         body, target, manager = node.body, node.target, node.manager
         node.enter_call = ExprNodes.SimpleCallNode(
-            pos, function = ExprNodes.AttributeNode(
-                pos, obj = ExprNodes.CloneNode(manager),
-                attribute = EncodedString('__enter__')),
-            args = [],
-            is_temp = True)
+            pos, function=ExprNodes.AttributeNode(
+                pos, obj=ExprNodes.CloneNode(manager),
+                attribute=EncodedString('__enter__'),
+                is_special_lookup=True),
+            args=[],
+            is_temp=True)
         if target is not None:
             body = Nodes.StatListNode(
                 pos, stats = [
index 733d75f..be07171 100644 (file)
@@ -1038,6 +1038,35 @@ static CYTHON_INLINE PyObject *__Pyx_GetAttr(PyObject *o, PyObject *n) {
     return PyObject_GetAttr(o, n);
 }
 
+/////////////// PyObjectLookupSpecial.proto ///////////////
+//@requires: PyObjectGetAttrStr
+
+#if CYTHON_COMPILING_IN_CPYTHON
+static CYTHON_INLINE PyObject* __Pyx_PyObject_LookupSpecial(PyObject* obj, PyObject* attr_name) {
+    PyObject *res;
+    PyTypeObject *tp = Py_TYPE(obj);
+#if PY_MAJOR_VERSION < 3
+    if (unlikely(PyInstance_Check(obj)))
+        return __Pyx_PyObject_GetAttrStr(obj, attr_name);
+#endif
+    // adapted from CPython's special_lookup() in ceval.c
+    res = _PyType_Lookup(tp, attr_name);
+    if (likely(res)) {
+        descrgetfunc f = Py_TYPE(res)->tp_descr_get;
+        if (!f) {
+            Py_INCREF(res);
+        } else {
+            res = f(res, obj, (PyObject *)tp);
+        }
+    } else {
+        PyErr_SetObject(PyExc_AttributeError, attr_name);
+    }
+    return res;
+}
+#else
+#define __Pyx_PyObject_LookupSpecial(o,n) PyObject_GetAttr(o,n)
+#endif
+
 /////////////// PyObjectGetAttrStr.proto ///////////////
 
 #if CYTHON_COMPILING_IN_CPYTHON
diff --git a/tests/run/pyclass_special_methods.py b/tests/run/pyclass_special_methods.py
new file mode 100644 (file)
index 0000000..c3a9c88
--- /dev/null
@@ -0,0 +1,45 @@
+# mode: run
+# tag: pyclass, getattr
+
+"""
+Python bypasses __getattribute__ overrides for some special method lookups.
+"""
+
+lookups = []
+
+
+class PyClass(object):
+    """
+    >>> del lookups[:]
+    >>> obj = PyClass()
+    >>> obj.test
+    'getattribute(test)'
+    >>> lookups
+    ['getattribute(test)']
+    """
+    def __getattribute__(self, name):
+        lookup = 'getattribute(%s)' % name
+        lookups.append(lookup)
+        return lookup
+
+    def __getattr__(self, name):
+        lookup = 'getattr(%s)' % name
+        lookups.append(lookup)
+        return lookup
+
+
+def use_as_context_manager(obj):
+    """
+    >>> del lookups[:]
+    >>> class PyCM(PyClass):
+    ...     def __enter__(self): return '__enter__(%s)' % (self is obj or self)
+    ...     def __exit__(self, *args): pass
+    >>> obj = PyCM()
+    >>> use_as_context_manager(obj)
+    '__enter__(True)'
+    >>> lookups
+    []
+    """
+    with obj as x:
+        pass
+    return x