diff -r 27856e7dbcd1 Lib/test/test_decorators.py --- a/Lib/test/test_decorators.py Mon Apr 13 11:08:55 2015 -0500 +++ b/Lib/test/test_decorators.py Mon Apr 13 13:45:19 2015 -0400 @@ -77,12 +77,6 @@ self.assertEqual(C.foo(), 42) self.assertEqual(C().foo(), 42) - def test_staticmethod_function(self): - @staticmethod - def notamethod(x): - return x - self.assertRaises(TypeError, notamethod, 1) - def test_dotted(self): decorators = MiscDecorators() @decorators.author('Cleese') diff -r 27856e7dbcd1 Lib/test/test_descr.py --- a/Lib/test/test_descr.py Mon Apr 13 11:08:55 2015 -0500 +++ b/Lib/test/test_descr.py Mon Apr 13 13:45:19 2015 -0400 @@ -1473,6 +1473,17 @@ del cm.x self.assertNotHasAttr(cm, "x") + # Verify that we can also call classmethod functions that are not + # plain Python function objects (i.e. because they are implemented + # via a __call__ method, or in C, etc.). Important, as the previous + # tests in the method only test a plain Python function. + self.assertTrue(classmethod(callable)(callable)) + + class ClassWithCallable: + def __call__(self): + return 42 + self.assertEqual(classmethod(ClassWithCallable())(), 42) + @support.impl_detail("the module 'xxsubtype' is internal") def test_classmethods_in_c(self): # Testing C-based class methods... @@ -1528,6 +1539,17 @@ del sm.x self.assertNotHasAttr(sm, "x") + # Verify that we can also call staticmethod functions that are not + # plain Python function objects (i.e. because they are implemented + # via a __call__ method, or in C, etc.). Important, as the previous + # tests in the method only test a plain Python function. + self.assertTrue(staticmethod(callable)(callable)) + + class ClassWithCallable: + def __call__(self): + return 42 + self.assertEqual(staticmethod(ClassWithCallable())(), 42) + @support.impl_detail("the module 'xxsubtype' is internal") def test_staticmethods_in_c(self): # Testing C-based static methods... @@ -5274,12 +5296,99 @@ pass +class TestDescriptorsCallable(unittest.TestCase): + """Test that every possible descriptor is callable. + + We check all combinations of: + * an instance method, + * a class method, and + * a static method. + + Accessing them as: + * as an instance attribute, + * as a class attribute, and + * through the class __dict__. + + And we call: + * one user method and + * one built-in method. + + See http://bugs.python.org/issue20309 for the history behind these tests. + """ + + class C: + def normal_method(self, a, b): + print(self, a, b) + + @classmethod + def class_method(cls, a, b): + print(cls, a, b) + + @staticmethod + def static_method(a, b): + print(a, b) + + def test_userclass_instance_instancemethod(self): + self.assertTrue(callable(self.C().normal_method)) + + def test_builtinclass_instance_instancemethod(self): + self.assertTrue(callable((123).__add__)) + + def test_userclass_instance_classmethod(self): + self.assertTrue(callable(self.C().class_method)) + + def test_builtinclass__instance_classmethod(self): + self.assertTrue(callable({}.fromkeys)) + + def test_userclass_instance_staticmethod(self): + self.assertTrue(callable(self.C().static_method)) + + def test_builtinclass_instance_staticmethod(self): + self.assertTrue(callable('abc'.maketrans)) + + def test_userclass_class_instancemethod(self): + self.assertTrue(callable(self.C.normal_method)) + + def test_builtinclass_class_instancemethod(self): + self.assertTrue(callable(int.__add__)) + + def test_userclass_class_classmethod(self): + self.assertTrue(callable(self.C.class_method)) + + def test_builtinclass_class_classmethod(self): + self.assertTrue(callable(dict.fromkeys)) + + def test_userclass_class_staticmethod(self): + self.assertTrue(callable(self.C.static_method)) + + def test_builtinclass_class_staticmethod(self): + self.assertTrue(callable(str.maketrans)) + + def test_userclass_classdict_instancemethod(self): + self.assertTrue(callable(self.C.__dict__['normal_method'])) + + def test_builtinclass_classdict_instancemethod(self): + self.assertTrue(callable(int.__dict__['__add__'])) + + def test_userclass_classdict_classmethod(self): + self.assertTrue(callable(self.C.__dict__['class_method'])) + + def test_builtinclass_classdict_instancemethod(self): + self.assertTrue(callable(dict.__dict__['fromkeys'])) + + def test_userclass_classdict_staticmethod(self): + self.assertTrue(callable(self.C.__dict__['static_method'])) + + def test_builtinclass__classdict_staticmethod(self): + self.assertTrue(callable(str.__dict__['maketrans'])) + + def test_main(): # Run all local test cases, with PTypesLongInitTest first. support.run_unittest(PTypesLongInitTest, OperatorsTest, ClassPropertiesAndMethods, DictProxyTests, MiscTests, PicklingTests, SharedKeyTests, - MroTest) + MroTest, TestDescriptorsCallable) if __name__ == "__main__": test_main() diff -r 27856e7dbcd1 Objects/funcobject.c --- a/Objects/funcobject.c Mon Apr 13 11:08:55 2015 -0500 +++ b/Objects/funcobject.c Mon Apr 13 13:45:19 2015 -0400 @@ -776,6 +776,20 @@ return 0; } +static PyObject * +cm_call(PyObject *func, PyObject *arg, PyObject *kw) +{ + classmethod *cm = (classmethod *)func; + + if (cm->cm_callable == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "uninitialized classmethod object"); + return NULL; + } + + return PyObject_Call(cm->cm_callable, arg, kw); +} + static PyMemberDef cm_memberlist[] = { {"__func__", T_OBJECT, offsetof(classmethod, cm_callable), READONLY}, {NULL} /* Sentinel */ @@ -839,7 +853,7 @@ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ - 0, /* tp_call */ + cm_call, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ @@ -940,6 +954,20 @@ return sm->sm_callable; } +static PyObject * +sm_call(PyObject *func, PyObject *arg, PyObject *kw) +{ + staticmethod *sm = (staticmethod *)func; + + if (sm->sm_callable == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "uninitialized staticmethod object"); + return NULL; + } + + return PyObject_Call(sm->sm_callable, arg, kw); +} + static int sm_init(PyObject *self, PyObject *args, PyObject *kwds) { @@ -1015,7 +1043,7 @@ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ - 0, /* tp_call */ + sm_call, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */