]> granicus.if.org Git - python/commitdiff
add _PyObject_LookupSpecial to handle fetching special method lookup
authorBenjamin Peterson <benjamin@python.org>
Fri, 8 May 2009 03:06:00 +0000 (03:06 +0000)
committerBenjamin Peterson <benjamin@python.org>
Fri, 8 May 2009 03:06:00 +0000 (03:06 +0000)
Include/object.h
Lib/test/test_descr.py
Objects/object.c
Objects/typeobject.c

index 1bc13e7c2af4e97f566d86e2718aa44a136e66a1..12e0c46ed6030d394194a4b887db3f3ff6314e25 100644 (file)
@@ -451,6 +451,7 @@ PyAPI_FUNC(PyObject *) PyType_GenericAlloc(PyTypeObject *, Py_ssize_t);
 PyAPI_FUNC(PyObject *) PyType_GenericNew(PyTypeObject *,
                                               PyObject *, PyObject *);
 PyAPI_FUNC(PyObject *) _PyType_Lookup(PyTypeObject *, PyObject *);
+PyAPI_FUNC(PyObject *) _PyObject_LookupSpecial(PyObject *, char *, PyObject **);
 PyAPI_FUNC(unsigned int) PyType_ClearCache(void);
 PyAPI_FUNC(void) PyType_Modified(PyTypeObject *);
 
index ae22af7ffc38a03b5f3761c608c50499e357c17c..46fb581e4ab0e8e666eb6e372b41c38c8f931723 100644 (file)
@@ -1665,6 +1665,58 @@ order (MRO) for bases """
         self.assertEqual(E().foo, C.foo) # i.e., unbound
         self.assert_(repr(C.foo.__get__(C(1))).startswith("<bound method "))
 
+    def test_special_method_lookup(self):
+        # The lookup of special methods bypasses __getattr__ and
+        # __getattribute__, but they still can be descriptors.
+
+        def run_context(manager):
+            with manager:
+                pass
+        def iden(self):
+            return self
+        def hello(self):
+            return "hello"
+
+        # It would be nice to have every special method tested here, but I'm
+        # only listing the ones I can remember outside of typeobject.c, since it
+        # does it right.
+        specials = [
+            ("__unicode__", unicode, hello),
+            # These two fail because the compiler generates LOAD_ATTR to look
+            # them up.  We'd have to add a new opcode to fix this, and it's
+            # probably not worth it.
+            # ("__enter__", run_context, iden),
+            # ("__exit__", run_context, iden),
+            ]
+
+        class Checker(object):
+            def __getattr__(self, attr, test=self):
+                test.fail("__getattr__ called with {0}".format(attr))
+            def __getattribute__(self, attr, test=self):
+                test.fail("__getattribute__ called with {0}".format(attr))
+        class SpecialDescr(object):
+            def __init__(self, impl):
+                self.impl = impl
+            def __get__(self, obj, owner):
+                record.append(1)
+                return self
+            def __call__(self, *args):
+                return self.impl(*args)
+
+
+        for name, runner, meth_impl in specials:
+            class X(Checker):
+                pass
+            setattr(X, name, staticmethod(meth_impl))
+            runner(X())
+
+            record = []
+            class X(Checker):
+                pass
+            setattr(X, name, SpecialDescr(meth_impl))
+            runner(X())
+            self.assertEqual(record, [1], name)
+
     def test_specials(self):
         # Testing special operators...
         # Test operators like __hash__ for which a built-in default exists
index 6254dfa0e51158f3e158d9126779f56fec4d171e..3a7619324d279fa5376a3bfadf898bda2077f411 100644 (file)
@@ -488,12 +488,6 @@ PyObject_Unicode(PyObject *v)
                return v;
        }
 
-       /* Try the __unicode__ method */
-       if (unicodestr == NULL) {
-               unicodestr= PyString_InternFromString("__unicode__");
-               if (unicodestr == NULL)
-                       return NULL;
-       }
        if (PyInstance_Check(v)) {
                /* We're an instance of a classic class */
                /* Try __unicode__ from the instance -- alas we have no type */
@@ -508,15 +502,12 @@ PyObject_Unicode(PyObject *v)
                }
        }
        else {
-               /* Not a classic class instance, try __unicode__ from type */
-               /* _PyType_Lookup doesn't create a reference */
-               func = _PyType_Lookup(Py_TYPE(v), unicodestr);
+               /* Not a classic class instance, try __unicode__. */
+               func = _PyObject_LookupSpecial(v, "__unicode__", &unicodestr);
                if (func != NULL) {
                        unicode_method_found = 1;
                        res = PyObject_CallFunctionObjArgs(func, v, NULL);
-               }
-               else {
-                       PyErr_Clear();
+                       Py_DECREF(func);
                }
        }
 
index 304066f80d3bfed5937dc2d62582d13da83c702b..eb3560b4c41a8df9183fc84123bb5a5b1052f63f 100644 (file)
@@ -1179,6 +1179,8 @@ PyType_IsSubtype(PyTypeObject *a, PyTypeObject *b)
      when the _PyType_Lookup() call fails;
 
    - lookup_method() always raises an exception upon errors.
+
+   - _PyObject_LookupSpecial() exported for the benefit of other places.
 */
 
 static PyObject *
@@ -1211,6 +1213,12 @@ lookup_method(PyObject *self, char *attrstr, PyObject **attrobj)
        return res;
 }
 
+PyObject *
+_PyObject_LookupSpecial(PyObject *self, char *attrstr, PyObject **attrobj)
+{
+       return lookup_maybe(self, attrstr, attrobj);
+}
+
 /* A variation of PyObject_CallMethod that uses lookup_method()
    instead of PyObject_GetAttrString().         This uses the same convention
    as lookup_method to cache the interned name string object. */