Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Lib/test/test_descr.py
Original file line number Diff line number Diff line change
Expand Up @@ -1630,8 +1630,8 @@ class SubSpam(spam.spamlist): pass
spam_cm()
self.assertEqual(
str(cm.exception),
"descriptor 'classmeth' of 'xxsubtype.spamlist' "
"object needs an argument")
"unbound method spamlist.classmeth() "
"needs an argument")

with self.assertRaises(TypeError) as cm:
spam_cm(spam.spamlist())
Expand Down
4 changes: 3 additions & 1 deletion Lib/test/test_gdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,9 @@ def test_pycfunction(self):
):
for obj in (
'_testcapi',
'_testcapi.MethClass',
# From bpo-45295, classmethods are unbound and don't give
# nice tracebacks.
# '_testcapi.MethClass',
'_testcapi.MethClass()',
'_testcapi.MethStatic()',

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Speed up C classmethod calls such as ``int.from_bytes``.
``_PyObject_GetMethod`` now supports unbound C classmethods.
``PyClassMethodDescr_Type`` now supports vectorcall. Patch by Ken Jin.
143 changes: 76 additions & 67 deletions Objects/descrobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,52 +71,34 @@ wrapperdescr_repr(PyWrapperDescrObject *descr)
"<slot wrapper '%V' of '%s' objects>");
}

static int
descr_check(PyDescrObject *descr, PyObject *obj, PyObject **pres)
{
if (obj == NULL) {
Py_INCREF(descr);
*pres = (PyObject *)descr;
return 1;
}
if (!PyObject_TypeCheck(obj, descr->d_type)) {
PyErr_Format(PyExc_TypeError,
"descriptor '%V' for '%.100s' objects "
"doesn't apply to a '%.100s' object",
descr_name((PyDescrObject *)descr), "?",
descr->d_type->tp_name,
Py_TYPE(obj)->tp_name);
*pres = NULL;
return 1;
}
return 0;
}

static PyObject *
classmethod_get(PyMethodDescrObject *descr, PyObject *obj, PyObject *type)
Py_LOCAL_INLINE(int)
classmethod_typecheck(PyMethodDescrObject *descr, PyObject *obj, PyObject **t)
{
PyObject *type = *t;
/* Ensure a valid type. Class methods ignore obj. */
if (type == NULL) {
if (obj != NULL)
type = (PyObject *)Py_TYPE(obj);
if (obj != NULL) {
type = *t = (PyObject *)Py_TYPE(obj);
}
else {
/* Wot - no type?! */
PyErr_Format(PyExc_TypeError,
"descriptor '%V' for type '%.100s' "
"needs either an object or a type",
descr_name((PyDescrObject *)descr), "?",
PyDescr_TYPE(descr)->tp_name);
return NULL;
return 1;
}
}
assert(type != NULL);
if (!PyType_Check(type)) {
PyErr_Format(PyExc_TypeError,
"descriptor '%V' for type '%.100s' "
"needs a type, not a '%.100s' as arg 2",
descr_name((PyDescrObject *)descr), "?",
PyDescr_TYPE(descr)->tp_name,
Py_TYPE(type)->tp_name);
return NULL;
return 1;
}
if (!PyType_IsSubtype((PyTypeObject *)type, PyDescr_TYPE(descr))) {
PyErr_Format(PyExc_TypeError,
Expand All @@ -125,6 +107,45 @@ classmethod_get(PyMethodDescrObject *descr, PyObject *obj, PyObject *type)
descr_name((PyDescrObject *)descr), "?",
PyDescr_TYPE(descr)->tp_name,
((PyTypeObject *)type)->tp_name);
return 1;
}
return 0;
}

static int
descr_check(PyDescrObject *descr, PyObject *o, PyObject **pres)
{
PyObject *obj = o;
if (obj == NULL) {
Py_INCREF(descr);
*pres = (PyObject *)descr;
return 1;
}
if (Py_IS_TYPE(descr, &PyClassMethodDescr_Type)) {
if (classmethod_typecheck((PyMethodDescrObject *)descr, NULL, &obj)) {
return 1;
}
return 0;
}
if (obj != (PyObject *)descr->d_type &&
!PyObject_TypeCheck(obj, descr->d_type)) {
PyErr_Format(PyExc_TypeError,
"descriptor '%V' for '%.100s' objects "
"doesn't apply to a '%.100s' object",
descr_name((PyDescrObject *)descr), "?",
descr->d_type->tp_name,
Py_TYPE(obj)->tp_name);
*pres = NULL;
return 1;
}
return 0;
}

static PyObject *
classmethod_get(PyMethodDescrObject *descr, PyObject *obj, PyObject *t)
{
PyObject *type = t;
if (classmethod_typecheck(descr, obj, &type)) {
return NULL;
}
PyTypeObject *cls = NULL;
Expand Down Expand Up @@ -464,39 +485,6 @@ method_vectorcall_O(
return result;
}


/* Instances of classmethod_descriptor are unlikely to be called directly.
For one, the analogous class "classmethod" (for Python classes) is not
callable. Second, users are not likely to access a classmethod_descriptor
directly, since it means pulling it from the class __dict__.

This is just an excuse to say that this doesn't need to be optimized:
we implement this simply by calling __get__ and then calling the result.
*/
static PyObject *
classmethoddescr_call(PyMethodDescrObject *descr, PyObject *args,
PyObject *kwds)
{
Py_ssize_t argc = PyTuple_GET_SIZE(args);
if (argc < 1) {
PyErr_Format(PyExc_TypeError,
"descriptor '%V' of '%.100s' "
"object needs an argument",
descr_name((PyDescrObject *)descr), "?",
PyDescr_TYPE(descr)->tp_name);
return NULL;
}
PyObject *self = PyTuple_GET_ITEM(args, 0);
PyObject *bound = classmethod_get(descr, NULL, self);
if (bound == NULL) {
return NULL;
}
PyObject *res = PyObject_VectorcallDict(bound, _PyTuple_ITEMS(args)+1,
argc-1, kwds);
Py_DECREF(bound);
return res;
}

Py_LOCAL_INLINE(PyObject *)
wrapperdescr_raw_call(PyWrapperDescrObject *descr, PyObject *self,
PyObject *args, PyObject *kwds)
Expand Down Expand Up @@ -735,7 +723,7 @@ PyTypeObject PyClassMethodDescr_Type = {
sizeof(PyMethodDescrObject),
0,
(destructor)descr_dealloc, /* tp_dealloc */
0, /* tp_vectorcall_offset */
offsetof(PyMethodDescrObject, vectorcall), /* tp_vectorcall_offset */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_as_async */
Expand All @@ -744,12 +732,14 @@ PyTypeObject PyClassMethodDescr_Type = {
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
(ternaryfunc)classmethoddescr_call, /* tp_call */
PyVectorcall_Call, /* tp_call */
0, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_HAVE_VECTORCALL |
Py_TPFLAGS_METHOD_DESCRIPTOR, /* tp_flags */
0, /* tp_doc */
descr_traverse, /* tp_traverse */
0, /* tp_clear */
Expand Down Expand Up @@ -899,8 +889,8 @@ descr_new(PyTypeObject *descrtype, PyTypeObject *type, const char *name)
return descr;
}

PyObject *
PyDescr_NewMethod(PyTypeObject *type, PyMethodDef *method)
Py_LOCAL_INLINE(void)
identify_vectorcallfunc(PyMethodDef *method, vectorcallfunc *vfunc)
{
/* Figure out correct vectorcall function to use */
vectorcallfunc vectorcall;
Expand Down Expand Up @@ -931,7 +921,18 @@ PyDescr_NewMethod(PyTypeObject *type, PyMethodDef *method)
default:
PyErr_Format(PyExc_SystemError,
"%s() method: bad call flags", method->ml_name);
return NULL;
vectorcall = NULL;
}
*vfunc = vectorcall;
}

PyObject *
PyDescr_NewMethod(PyTypeObject *type, PyMethodDef *method)
{
vectorcallfunc vectorcall;
identify_vectorcallfunc(method, &vectorcall);
if (vectorcall == NULL) {
return NULL;
}

PyMethodDescrObject *descr;
Expand All @@ -948,12 +949,20 @@ PyDescr_NewMethod(PyTypeObject *type, PyMethodDef *method)
PyObject *
PyDescr_NewClassMethod(PyTypeObject *type, PyMethodDef *method)
{
vectorcallfunc vectorcall;
identify_vectorcallfunc(method, &vectorcall);
if (vectorcall == NULL) {
return NULL;
}

PyMethodDescrObject *descr;

descr = (PyMethodDescrObject *)descr_new(&PyClassMethodDescr_Type,
type, method->ml_name);
if (descr != NULL)
if (descr != NULL) {
descr->d_method = method;
descr->vectorcall = vectorcall;
}
return (PyObject *)descr;
}

Expand Down
79 changes: 59 additions & 20 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -1120,11 +1120,18 @@ _PyObject_NextNotImplemented(PyObject *self)

`method` will point to the resolved attribute or NULL. In the
latter case, an error will be set.

Also works with C METH_CLASS methods, such as dict.fromkeys.

When a method is found, *method* is set to an unbound method, unbound
classmethod or wrapper descriptor.
*/
int
_PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method)
{
int meth_found = 0;
int is_classmethod = 0;
int is_valid_classmethod = 1;

assert(*method == NULL);

Expand All @@ -1136,17 +1143,46 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method)
}

if (tp->tp_getattro != PyObject_GenericGetAttr || !PyUnicode_Check(name)) {
*method = PyObject_GetAttr(obj, name);
return 0;
if (PyType_Check(obj) && PyType_CheckExact(tp)) {
is_classmethod = 1;
}
else {
*method = PyObject_GetAttr(obj, name);
return 0;
}
}

PyObject *descr = _PyType_Lookup(tp, name);
descrgetfunc f = NULL;
PyObject *descr = NULL;
if (is_classmethod) {
descr = _PyType_Lookup((PyTypeObject *)obj, name);
if (descr == NULL) {
*method = PyObject_GetAttr(obj, name);
return 0;
}
// Currently doesn't work for Python @classmethod(func) objects.
// For unbound classmethods, obj must be the type to work.
// E.g. {}.fromkeys() is rejected, while dict.fromkeys() works.
if (!Py_IS_TYPE(descr, &PyClassMethodDescr_Type) ||
(obj != (PyObject *)PyDescr_TYPE(descr))) {
*method = PyObject_GetAttr(obj, name);
return 0;
}
}
else {
descr = _PyType_Lookup(tp, name);
if (descr != NULL && Py_IS_TYPE(descr, &PyClassMethodDescr_Type)) {
is_valid_classmethod = (obj == (PyObject *)PyDescr_TYPE(descr));
}
}

if (descr != NULL) {
Py_INCREF(descr);
if (_PyType_HasFeature(Py_TYPE(descr), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
if (_PyType_HasFeature(Py_TYPE(descr), Py_TPFLAGS_METHOD_DESCRIPTOR) &&
is_valid_classmethod) {
meth_found = 1;
} else {
}
else {
f = Py_TYPE(descr)->tp_descr_get;
if (f != NULL && PyDescr_IsData(descr)) {
*method = f(descr, obj, (PyObject *)Py_TYPE(obj));
Expand All @@ -1156,26 +1192,29 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method)
}
}

PyObject **dictptr = _PyObject_GetDictPtr(obj);
PyObject *dict;
if (dictptr != NULL && (dict = *dictptr) != NULL) {
Py_INCREF(dict);
PyObject *attr = PyDict_GetItemWithError(dict, name);
if (attr != NULL) {
*method = Py_NewRef(attr);
// _PyType_Lookup did this for classmethods already.
if (!is_classmethod) {
PyObject **dictptr = _PyObject_GetDictPtr(obj);
PyObject *dict;
if (dictptr != NULL && (dict = *dictptr) != NULL) {
Py_INCREF(dict);
PyObject *attr = PyDict_GetItemWithError(dict, name);
if (attr != NULL) {
*method = Py_NewRef(attr);
Py_DECREF(dict);
Py_XDECREF(descr);
return 0;
}
Py_DECREF(dict);
Py_XDECREF(descr);
return 0;
}
Py_DECREF(dict);

if (PyErr_Occurred()) {
Py_XDECREF(descr);
return 0;
if (PyErr_Occurred()) {
Py_XDECREF(descr);
return 0;
}
}
}

if (meth_found) {
if (meth_found && is_valid_classmethod) {
*method = descr;
return 1;
}
Expand Down
6 changes: 3 additions & 3 deletions Python/specialize.c
Original file line number Diff line number Diff line change
Expand Up @@ -566,11 +566,11 @@ analyze_descriptor(PyTypeObject *type, PyObject *name, PyObject **descr, int sto
}
if (desc_cls->tp_descr_get) {
if (desc_cls->tp_flags & Py_TPFLAGS_METHOD_DESCRIPTOR) {
if (Py_IS_TYPE(descriptor, &PyClassMethodDescr_Type)) {
return BUILTIN_CLASSMETHOD;
}
return METHOD;
}
if (Py_IS_TYPE(descriptor, &PyClassMethodDescr_Type)) {
return BUILTIN_CLASSMETHOD;
}
if (Py_IS_TYPE(descriptor, &PyClassMethod_Type)) {
return PYTHON_CLASSMETHOD;
}
Expand Down