Skip to content

bpo-32226: Implementation of PEP 560 (core components) #4732

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 40 commits into from
Dec 14, 2017

Conversation

ilevkivskyi
Copy link
Member

@ilevkivskyi ilevkivskyi commented Dec 5, 2017

This is a basic implementation of the PEP. Suggestions are welcome.

@gvanrossum @serhiy-storchaka Can one of you (or both?) please review?

cc: @markshannon IIRC you are interested in implementation details of this PEP.

https://bugs.python.org/issue32226

@ilevkivskyi
Copy link
Member Author

@serhiy-storchaka Thank you very much for review! I will make the changes tomorrow.

@ilevkivskyi
Copy link
Member Author

I have made the requested changes; please review again

@bedevere-bot
Copy link

Thanks for making the requested changes!

@serhiy-storchaka: please review the changes made to this pull request.

@ilevkivskyi
Copy link
Member Author

@serhiy-storchaka Following your comment #4731 (comment) I also removed callability checks for special methods here.

If you think it makes sense to customize error messages in future, then I can open a b.p.o. issue for this.

@ilevkivskyi
Copy link
Member Author

(I will add documentation for this in a separate PR after the second part of changes -- changes to typing -- will be in.)

goto error;
}
if (!replacements) {
replacements = PyTuple_New(nargs - 2);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handle an error.

I suggest to use a list instead of a tuple. You can fill it at the single pass and convert to a tuple at end.

if (!new_bases) {
    new_bases = PyList_New(i);
    if (!new_bases) {
        goto error;
    }
    // copy i items to new_bases
}
if (_PyList_Extend(new_bases, new_base) < 0) {
    goto error;
}

replacements = NULL;
/* We have a separate cycle to calculate replacements with the idea that in
most cases we just scroll quickly though it and return original bases */
for (i = 2; i < nargs; i++){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the code would look simpler if pass nargs-2 and args+2. Or passing just bases may be enough.

Needed a space before {.

@@ -206,6 +298,9 @@ builtin___build_class__(PyObject *self, PyObject **args, Py_ssize_t nargs,
Py_DECREF(meta);
Py_XDECREF(mkw);
Py_DECREF(bases);
if (modified_bases) {
Py_DECREF(old_bases);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just

Py_XDECREF(old_bases);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not really possible, since this code is also executed on success, and in this case if there were no conversion, then bases should be DECREFed exactly once.

return NULL;
}
else {
old_bases = bases;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code would look simpler if set the original bases to orig_bases.

orig_bases = _PyStack_AsTupleSlice(...);
if (orig_bases == NULL)
    return NULL;
bases = update_bases(...);
if (bases == NULL) {
    Py_DECREF(orig_bases);
    return NULL;
}

@@ -168,6 +255,11 @@ builtin___build_class__(PyObject *self, PyObject **args, Py_ssize_t nargs,
NULL, 0, NULL, 0, NULL, 0, NULL,
PyFunction_GET_CLOSURE(func));
if (cell != NULL) {
if (modified_bases) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (bases != orig_bases) {

modified_bases is not needed.

@ilevkivskyi
Copy link
Member Author

@serhiy-storchaka Thank you for another review! I converted the code to the single pass as you suggested. The code indeed looks cleaner, however performance is worse now. For normal (non-generic) classes there is slow-down of up to 5%. Do you have any ideas about how to boost performance here?

@ilevkivskyi
Copy link
Member Author

@serhiy-storchaka Ah, sorry, I forgot to merge master before bechmarking. In fact, there are no visible slow-downs. (Which makes sense, since I just added few pointer comparisons.)

PyErr_Clear();
if (new_bases) {
if (PyList_Append(new_bases, base) < 0) {
Py_DECREF(meth);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

meth is NULL here.

if (!new_base) {
goto error;
}
if (PyTuple_Check(new_base)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to swap branches and move the success branch to upper level. I think this will make the code clearer.

if (!PyTuple_Check(new_base)) {
    ...
    goto error;
}
if (!new_bases) {
    ...

}
continue;
}
if (!(meth = _PyObject_GetAttrId(base, &PyId___mro_entries__))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would look clearer if make an assignment on a separate line.

PyObject *stack[1] = {bases};
assert(PyTuple_Check(bases));

/* We have a separate cycle to calculate replacements with the idea that in
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment outdated?


/* We have a separate cycle to calculate replacements with the idea that in
most cases we just scroll quickly though it and return original bases */
for (i = 0; i < nargs; i++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For performance try to add a separate fast loop for non-generic types.

for (i = 0; i < nargs && PyType_Check(args[i]); i++) {
}
if (i == nargs) {
    return bases;
}
new_bases = PyList_New(i);
...
for (; i < nargs; i++) {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't give any visible difference, so I will just keep the single loop (As I mentioned before, a more careful benchmark showed that it is already good).

if (!new_bases) {
/* If this is a first successful conversion, create new_bases list and
copy previously encountered bases. */
new_bases = PyList_New(i);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check for error.

if (!_PyList_Extend((PyListObject *)new_bases, new_base)) {
goto error;
}
Py_DECREF(Py_None);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks confusing. Add a comment to this non-obvious code.

Alternatively you can use a public PyList_SetSlice instead of _PyList_Extend if you prefer.

j = PyList_GET_SIZE(new_bases);
if (PyList_SetSlice(new_bases, j, j, new_base) < 0) {
    goto error;
}

@@ -46,12 +47,85 @@ _Py_IDENTIFIER(stderr);

#include "clinic/bltinmodule.c.h"

static PyObject*
update_bases(PyObject* bases, PyObject** args, int nargs)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add the const qualifier. See bpo-32240.

It is more common to add a space before * instead of after it.

result = PyList_AsTuple(new_bases);
Py_DECREF(new_bases);
return result;
error:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just my personal preference, but I prefer to add an empty line after return and before a label (closing brace can replace an empty line). This make the code cleaner to me.

@ilevkivskyi
Copy link
Member Author

@serhiy-storchaka Does this PR look OK to you now? (Taking into account that Guido likes the current implementation "strategy" of __class_getitem__, see https://bugs.python.org/issue32226#msg308171)

I am really sorry for constantly pushing you on this. There are few other things that depend on this PR

Copy link
Member

@serhiy-storchaka serhiy-storchaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. This introduces a small performance regression, but it can be be fixed later.

@ilevkivskyi
Copy link
Member Author

LGTM. This introduces a small performance regression, but it can be be fixed later

OK, thanks! Then I will merge this now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants