From 8f6412275c4c2d1dcf43ad40306f858b114104ed Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Fri, 22 Nov 2013 19:48:18 +0100 Subject: [PATCH] use more CPython-like (and potentially faster) _PyType_Lookup() for internal special method lookups instead of generic attribute lookup --- Cython/Compiler/ExprNodes.py | 14 ++++++++--- Cython/Compiler/Nodes.py | 4 +-- Cython/Compiler/ParseTreeTransforms.py | 11 +++++---- Cython/Utility/ObjectHandling.c | 29 ++++++++++++++++++++++ tests/run/pyclass_special_methods.py | 45 ++++++++++++++++++++++++++++++++++ 5 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 tests/run/pyclass_special_methods.py diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index d3e8098..8766ab4 100644 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -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))) diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index c1986c0..4ac0b77 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -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__')), diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py index 9861022..3cc9dd7 100644 --- a/Cython/Compiler/ParseTreeTransforms.py +++ b/Cython/Compiler/ParseTreeTransforms.py @@ -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 = [ diff --git a/Cython/Utility/ObjectHandling.c b/Cython/Utility/ObjectHandling.c index 733d75f..be07171 100644 --- a/Cython/Utility/ObjectHandling.c +++ b/Cython/Utility/ObjectHandling.c @@ -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 index 0000000..c3a9c88 --- /dev/null +++ b/tests/run/pyclass_special_methods.py @@ -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 -- 2.7.4