diff options
-rw-r--r-- | Include/object.h | 1 | ||||
-rw-r--r-- | Lib/test/test_descr.py | 52 | ||||
-rw-r--r-- | Objects/object.c | 15 | ||||
-rw-r--r-- | Objects/typeobject.c | 8 |
4 files changed, 64 insertions, 12 deletions
diff --git a/Include/object.h b/Include/object.h index 1bc13e7..12e0c46 100644 --- a/Include/object.h +++ b/Include/object.h @@ -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 *); diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index ae22af7..46fb581 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -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 diff --git a/Objects/object.c b/Objects/object.c index 6254dfa..3a76193 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -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); } } diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 304066f..eb3560b 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -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. */ |