__getattr(ibute)__ special methods now work with subclasses.
authorPeter Todd <pete@petertodd.org>
Mon, 5 May 2008 03:28:40 +0000 (23:28 -0400)
committerPeter Todd <pete@petertodd.org>
Mon, 5 May 2008 03:28:40 +0000 (23:28 -0400)
Cython/Compiler/ModuleNode.py
tests/run/__getattribute_subclasses__.pyx [new file with mode: 0644]

index 2b63c919ff1e7f63e392114840591bfbf9e1356e..eead6a44537311abc7b682cddf1dc4d9b885ec16 100644 (file)
@@ -1033,10 +1033,21 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
         # First try to get the attribute using __getattribute__, if defined, or
         # PyObject_GenericGetAttr.
         #
-        # If that raises an AttributeError, call the user's __getattr__
-        # method, if defined.
-        getattr_entry = scope.lookup_here("__getattr__")
-        getattribute_entry = scope.lookup_here("__getattribute__")
+        # If that raises an AttributeError, call the __getattr__ if defined.
+        #
+        # In both cases, defined can be in this class, or any base class.
+        def lookup_here_or_base(n,type=None):
+            # Recursive lookup
+            if type is None:
+                type = scope.parent_type
+            r = type.scope.lookup_here(n)
+            if r is None and \
+               type.base_type is not None:
+                return lookup_here_or_base(n,type.base_type)
+            else:
+                return r
+        getattr_entry = lookup_here_or_base("__getattr__")
+        getattribute_entry = lookup_here_or_base("__getattribute__")
         code.putln("")
         code.putln(
             "static PyObject *%s(PyObject *o, PyObject *n) {"
diff --git a/tests/run/__getattribute_subclasses__.pyx b/tests/run/__getattribute_subclasses__.pyx
new file mode 100644 (file)
index 0000000..413e5e9
--- /dev/null
@@ -0,0 +1,126 @@
+__doc__ = """
+__getattribute__ and __getattr__ special methods and subclasses. 
+
+getattr does not override members. 
+    >>> a = getattr_boring()
+    >>> a.boring_member
+    10
+    >>> a.resolved_by
+    'getattr_boring'
+
+getattribute does.
+    >>> a = getattribute_boring()
+    >>> a.boring_member
+    Traceback (most recent call last):
+    AttributeError
+    >>> a.resolved_by
+    'getattribute_boring'
+
+Is inherited.
+    >>> a = boring_boring_getattribute()
+    >>> a.boring_getattribute_member
+    Traceback (most recent call last):
+    AttributeError
+    >>> a.boring_boring_getattribute_member
+    Traceback (most recent call last):
+    AttributeError
+    >>> a.resolved_by
+    '_getattribute'
+
+__getattribute__ is always tried first, then __getattr__, regardless of where
+in the inheritance hiarchy they came from.
+    >>> a = getattribute_boring_boring_getattr()
+    >>> a.foo
+    Traceback (most recent call last):
+    AttributeError
+    >>> a.resolved_by
+    'getattribute_boring_boring_getattr'
+    >>> a.getattribute_boring_boring_getattr
+    True
+    >>> a._getattr
+    True
+
+    >>> a = getattr_boring_boring_getattribute()
+    >>> a.foo
+    Traceback (most recent call last):
+    AttributeError
+    >>> a.resolved_by
+    '_getattribute'
+    >>> a.getattr_boring_boring_getattribute
+    True
+    >>> a._getattribute
+    True
+
+"""
+
+cdef class boring:
+    cdef readonly int boring_member 
+    def __init__(self):
+        self.boring_member = 10
+
+cdef class getattr_boring(boring):
+    def __getattr__(self,n):
+        if n == 'resolved_by':
+            return 'getattr_boring'
+        elif n == 'getattr_boring':
+            return True
+        else:
+            raise AttributeError
+
+cdef class getattribute_boring(boring):
+    def __getattribute__(self,n):
+        if n == 'resolved_by':
+            return 'getattribute_boring'
+        elif n == 'getattribute_boring':
+            return True
+        else:
+            raise AttributeError
+
+cdef class _getattr:
+    def __getattr__(self,n):
+        if n == 'resolved_by':
+            return '_getattr'
+        elif n == '_getattr':
+            return True
+        else:
+            raise AttributeError
+
+cdef class _getattribute(boring):
+    def __getattribute__(self,n):
+        if n == 'resolved_by':
+            return '_getattribute'
+        elif n == '_getattribute':
+            return True
+        else:
+            raise AttributeError
+
+cdef class boring_getattribute(_getattribute):
+    cdef readonly int boring_getattribute_member
+
+cdef class boring_boring_getattribute(boring_getattribute):
+    cdef readonly int boring_boring_getattribute_member 
+
+cdef class boring_getattr(_getattr):
+    cdef readonly int boring_getattr_member
+
+cdef class boring_boring_getattr(boring_getattr):
+    cdef readonly int boring_boring_getattr_member 
+
+cdef class getattribute_boring_boring_getattr(boring_boring_getattr):
+    def __getattribute__(self,n):
+        if n == 'resolved_by':
+            return 'getattribute_boring_boring_getattr'
+        elif n == 'getattribute_boring_boring_getattr':
+            return True
+        else:
+            raise AttributeError
+
+cdef class getattr_boring_boring_getattribute(boring_boring_getattribute):
+    def __getattr__(self,n):
+        if n == 'resolved_by':
+            return 'getattr_boring_boring_getattribute'
+        elif n == 'getattr_boring_boring_getattribute':
+            return True
+        else:
+            raise AttributeError
+