Skip to content
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

bpo-30721: Show correct syntax hint in Py3 when using Py2 redirection syntax #2345

Merged
merged 11 commits into from
Aug 18, 2017

Conversation

CuriousLearner
Copy link
Member

@CuriousLearner CuriousLearner commented Jun 23, 2017

This is a work in progress for issue: http://bugs.python.org/issue30721

@ncoghlan I'm not sure how should I get the value that is being entered on the command line. What I know is it would be in PyObject *w, but I'm unable to get the right method from the doc to read the buffer.

So, for now, I've hard-coded the value just to make the test case pass. Could you please help me with the former problem and give me a pointer on it.

Also, after writing this patch, I realize, that binary_op is a generic function, and this code should be placed in function binop_type_error. Would you please guide me about these things?

Thanks!

https://bugs.python.org/issue30721

@mention-bot
Copy link

@CuriousLearner, thanks for your PR! By analyzing the history of the files in this pull request, we identified @serhiy-storchaka, @benjaminp and @nascheme to be potential reviewers.

Copy link
Contributor

@ncoghlan ncoghlan left a comment

Choose a reason for hiding this comment

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

Comments inline regarding some lower level details. I haven't looked at whether or not this is the right place to make the change yet, though.

@@ -819,6 +819,21 @@ binary_op(PyObject *v, PyObject *w, const int op_slot, const char *op_name)
PyObject *result = binary_op1(v, w, op_slot);
if (result == Py_NotImplemented) {
Py_DECREF(result);

if (strcmp(op_name, ">>") == 0 && \
Copy link
Contributor

Choose a reason for hiding this comment

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

I need to re-read the whole file myself to see if there may be a better place to inject this check, but in the mean time, at least compare against the numeric op_slot here, rather than the string value.

PyErr_Format(PyExc_TypeError,
"unsupported operand type(s) for %.100s: "
"'%.100s' and '%.100s'. Did you mean \"print(%s, "
"file={:100!r}).format(%s))\"",
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't include an actual format call in the error message - that was just what I used in the issue tracker to represent PyErr_Format pllaceholders. Instead, you'll want to use the %R format code to invoke PyObject_Repr on the operands directly:

PyErr_Format(PyExc_TypeError,
    "unsupported operand type(s) for %.100s: "
    "'%.100s' and '%.100s'. Did you mean \"print(%.100R, "
    "file={.100R})\"",
    op_name,
    v->ob_type->tp_name,
    w->ob_type->tp_name,
    w, v);

In the general case, you won't be able to recreate the exact text of the original input, as that's no longer available by the time we get here - we only have a reference to the final runtime objects.

We might decide it's worthwhile adding a special-case check for the RHS being exactly PySys_GetObject("stderr") and displaying file=sys.stderr in that case.

Alternatively, we could dispense with the idea of interpolating values entirely, and just use the hardcoded output "Did you mean \"print(<message>, file=<output_stream>)\""

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm leaning towards the "hardcoded placeholders" option, as it occurs to me that we don't actually have access to the message to be displayed when raising the exception:

print >> sys.stderr, 1, 2, 3

The LHS passed to >> is print, and the RHS is sys.stderr. I think that's why I just had <expr> as a placeholder in my example on the issue tracker.

However, the repr of a standard stream isn't particular helpful in this context either:

>>> sys.stderr
<_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>

The generic <output_stream> placeholder would be clearer than displaying that.

So yeah, forget about interpolating the inputs at all, and just append a string with generic placeholders in it.

@CuriousLearner
Copy link
Member Author

@ncoghlan I just noticed, I cannot move this snippet to binop_type_error function if I need to check for op_slot because that is not accessible there.

So, I formatted the error message in the binary_op function and let it return NULL as binop_type_error function does.

I've made the changes to include generic statement, but I think if there is a way to access op_slot with op_name, then this patch could be simplified. What are your thoughts on this?

@terryjreedy
Copy link
Member

test_string_with_stream_redirection (test.test_print.TestPy2MigrationHint) ... FAIL

FAIL: test_string_with_stream_redirection (test.test_print.TestPy2MigrationHint)

Traceback (most recent call last):
File "C:\projects\cpython\lib\test\test_print.py", line 165, in test_string_with_stream_redirection
'file=<output_stream>)', str(context.exception))
AssertionError: 'Did you mean "print(, file=<output_stream>)' not found in "unsupported operand type(s) for >>: 'builtin_function_or_method' and '_io.StringIO'"

@CuriousLearner
Copy link
Member Author

@terryjreedy The test passes on my local setup. I'm not sure why this is happening on CI.

I will have a look at it.

@CuriousLearner
Copy link
Member Author

CuriousLearner commented Jul 25, 2017

@terryjreedy Here is the result of test run on Mac OS

Run tests sequentially
0:00:00 load avg: 2.19 [1/1] test_print
1 test OK.

Total duration: 58 ms
Tests result: SUCCESS

@terryjreedy
Copy link
Member

@CuriousLearner Travis, where it also passed, run linux. Appveyor, where it failed runs Windows. I asked on the issue for a Windows expert to take a look.

@@ -819,6 +819,20 @@ binary_op(PyObject *v, PyObject *w, const int op_slot, const char *op_name)
PyObject *result = binary_op1(v, w, op_slot);
if (result == Py_NotImplemented) {
Py_DECREF(result);

if (op_slot == 96 && \
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be op_slot == NB_SLOT(nb_rshift)

@CuriousLearner
Copy link
Member Author

@ncoghlan @terryjreedy Please have a look now. The issue is resolved now.

@CuriousLearner CuriousLearner changed the title bpo-30721: [WIP] for showing correct syntax hint in Py3 when using Py2 redirection syntax bpo-30721: Show correct syntax hint in Py3 when using Py2 redirection syntax Jul 31, 2017
@@ -819,6 +819,20 @@ binary_op(PyObject *v, PyObject *w, const int op_slot, const char *op_name)
PyObject *result = binary_op1(v, w, op_slot);
if (result == Py_NotImplemented) {
Py_DECREF(result);

if (op_slot == 96 && \
strcmp(v->ob_type->tp_name, "builtin_function_or_method") == 0) {
Copy link
Member

Choose a reason for hiding this comment

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

Don't test type's name. Test the print object itself.

Copy link
Contributor

Choose a reason for hiding this comment

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

Aye, you'll want to do something like:

_Py_IDENTIFIER(print);
builtins = PyEval_GetBuiltins();
print_ref = _PyDict_GetItemId(builtins, &PyId_print);

and then check for v == print_ref.

(Only don't lay it out exactly like that, adjust it to fit the code - the above is just to show the key APIs you need)

@@ -819,6 +819,20 @@ binary_op(PyObject *v, PyObject *w, const int op_slot, const char *op_name)
PyObject *result = binary_op1(v, w, op_slot);
if (result == Py_NotImplemented) {
Py_DECREF(result);

if (op_slot == 96 && \
strcmp(v->ob_type->tp_name, "builtin_function_or_method") == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Aye, you'll want to do something like:

_Py_IDENTIFIER(print);
builtins = PyEval_GetBuiltins();
print_ref = _PyDict_GetItemId(builtins, &PyId_print);

and then check for v == print_ref.

(Only don't lay it out exactly like that, adjust it to fit the code - the above is just to show the key APIs you need)

@@ -819,6 +819,20 @@ binary_op(PyObject *v, PyObject *w, const int op_slot, const char *op_name)
PyObject *result = binary_op1(v, w, op_slot);
if (result == Py_NotImplemented) {
Py_DECREF(result);

if (op_slot == NB_SLOT(nb_rshift) && \
strcmp(v->ob_type->tp_name, "builtin_function_or_method") == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Moving the review comment about checking for exactly the print builtin to the latest commit:

_Py_IDENTIFIER(print);
builtins = PyEval_GetBuiltins();
print_ref = _PyDict_GetItemId(builtins, &PyId_print);

and then check for v == print_ref.

(Only don't lay it out exactly like that, adjust it to fit the code - the above is just to show the key APIs you need)

Copy link
Member

Choose a reason for hiding this comment

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

Yes, but since this code would be slow and complex, I think it can be replaced by something like

PyCFunction_Check(v) && PyCFunction_GET_FUNCTION(v) == builtin_print

or

PyCFunction_Check(v) && strcmp(((PyCFunctionObject *)v) -> m_ml -> ml_name, "print") == 0

Copy link
Contributor

Choose a reason for hiding this comment

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

Fair point. Since builtin_print is a static symbol in bltinmodule, your second suggestion looks good to me.

Copy link
Member

@serhiy-storchaka serhiy-storchaka Jul 31, 2017

Choose a reason for hiding this comment

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

There are many ways to get around this constraint. Export builtin_print or implement the check in bltinmodule.c and export the checking code for using in abstract.c.

But testing the function name may be enough.

Copy link
Contributor

Choose a reason for hiding this comment

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

Right, that was my reasoning too - while we could make the actual symbol available here, "It's a function called 'print' implemented in C" is a robust enough check, and it means the C level changes can stay contained to this module.


self.assertIn('Did you mean "print(<message>, '
'file=<output_stream>)', str(context.exception))

Copy link
Member

Choose a reason for hiding this comment

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

Add following tests:

  1. max >> sys.stderr.
  2. print >> with right argument implemented __rrshift__.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hi @serhiy-storchaka

Can you please explain a little about the above cases?

For 1, As far as I understand the max function when redirected to sys.stderr should not cause the Did you mean ... statement to be executed.

So, a test case can be

    def test_stream_redirection_using_max(self):
        import sys
        python2_print_str = 'max >> sys.stderr'
        with self.assertRaises(TypeError) as context:
            exec(python2_print_str)

        self.assertNotIn('Did you mean "print(<message>, '
                'file=<output_stream>)', str(context.exception))

For the 2, I don't quite understand if we just want to test print >> with the current patch. In that case again the Did you mean... statement would not be executed.

Is this what we want to test here?

Please correct me.

Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

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

@CuriousLearner Correct for the first case. For the second case, you'll want to implement a dummy class that does something like this:

class OverrideRRShift:
    def __rrshift__(self, lhs):
        return 42 # Force result independent of LHS

self.assertEqual(print >> OverrideRRShift(), 42)

That will specifically ensure that the special casing doesn't trigger when the rshift operation produces a sensible answer.

It would also be good to test that print >> 5 does produce the new exception message (that covers the case where the RHS implements __rrshift__ but returns NotImplemented)

Copy link
Member Author

Choose a reason for hiding this comment

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

@ncoghlan Can you please take a look at the changes?

Copy link
Contributor

@ncoghlan ncoghlan left a comment

Choose a reason for hiding this comment

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

This is starting to look pretty good to me, just one suggestion for another test case, as well as some suggestions for rearranging and renaming the existing test cases a bit.

@@ -155,7 +155,7 @@ def test_string_with_excessive_whitespace(self):

self.assertIn('print("Hello World", end=" ")', str(context.exception))

def test_string_with_stream_redirection(self):
def test_print_string_with_stream_redirection(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested test name: test_stream_redirection_hint_for_py2_migration

@@ -164,6 +164,32 @@ def test_string_with_stream_redirection(self):
self.assertIn('Did you mean "print(<message>, '
'file=<output_stream>)', str(context.exception))

def test_print_with_stream_redirection_using_max(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested test name: test_stream_redirection_hint_is_specific_to_print

Copy link
Contributor

Choose a reason for hiding this comment

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

Prompted by that, I'd also suggest another test case: test_stream_redirection_hint_is_specific_to_rshift that checks:

print << sys.stderr

doesn't produce the new hint in its error message.


self.assertEqual(print >> OverrideRRShift(), 0)

python2_print_str = 'print >> 42'
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a situation where we expect to see the hint, so it would fit better up in the first test case.

Copy link
Member

Choose a reason for hiding this comment

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

Add a comment about what case is covered by this test. It looks as a duplicate of the first case.

with self.assertRaises(TypeError) as context:
exec(python2_print_str)

self.assertNotIn('Did you mean "print(<message>, '
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd shorten the test string here to just 'Did you mean'

@@ -155,7 +155,7 @@ def test_string_with_excessive_whitespace(self):

self.assertIn('print("Hello World", end=" ")', str(context.exception))

def test_string_with_stream_redirection(self):
def test_print_string_with_stream_redirection(self):
import sys
python2_print_str = 'print >> sys.stderr, "message"'
Copy link
Contributor

Choose a reason for hiding this comment

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

This is legal Python 3 syntax, so there's no need to use exec for these test cases - you can just put the expression in the body of the with statement.

Copy link
Member Author

Choose a reason for hiding this comment

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

@ncoghlan But that is just a string, we would need to execute it in the context manager, no?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, that's what I'm saying - the code that checked for the custom SyntaxError needed to be tested with exec, because putting it outside a string would cause the entire module to fail to compile. That isn't the case here, so you can just do print >> sys.stderr, "message" directly in the body of the context manager (and similarly for the other cases).

self.assertNotIn('Did you mean "print(<message>, '
'file=<output_stream>)', str(context.exception))

def test_print_with_stream_redirection_using_rrshift(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested test name: test_no_stream_redirection_hint_when_rshift_succeeds

@@ -821,7 +821,7 @@ binary_op(PyObject *v, PyObject *w, const int op_slot, const char *op_name)
Py_DECREF(result);

if (op_slot == NB_SLOT(nb_rshift) && \
strcmp(v->ob_type->tp_name, "builtin_function_or_method") == 0) {
PyCFunction_Check(v) && strcmp(((PyCFunctionObject *)v) -> m_ml -> ml_name, "print") == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

When working with user supplied strings, we generally prefer strncmp to strcmp, which would give:

strncmp(((PyCFunctionObject *)v) -> m_ml -> ml_name, "print", 6)

for the comparison subexpression.

Copy link
Member

@serhiy-storchaka serhiy-storchaka Aug 18, 2017

Choose a reason for hiding this comment

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

What are reasons for using strncmp() instead of strcmp() here? "print" is NUL terminated, the comparison is limited to 6 chars in any case.

@bedevere-bot
Copy link

A Python core developer, ncoghlan, has requested some changes be
made to your pull request before we can consider merging it. If you
could please address their requests along with any other requests in
other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment
on this pull request containing the phrase I didn't expect the Spanish Inquisition!
I will then notify ncoghlan along with any other core developers
who have left a review that you're ready for them to take another look
at this pull request.

self.assertIn('Did you mean "print(<message>, '
'file=<output_stream>)', str(context.exception))

def test_print_with_stream_redirection_using_max(self):
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 it would be better to use the single test method for all these tests. Use empty lines and/or comments for separating them.

Copy link
Contributor

Choose a reason for hiding this comment

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

That also works - the suggested test_stream_redirection_hint_for_py2_migration name is sufficiently broad to cover all of them, and then the comment on each case can explain which particular aspect it is checking.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, I'll do that.


def test_print_with_stream_redirection_using_rrshift(self):
import sys

Copy link
Member

Choose a reason for hiding this comment

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

Too much empty lines. Use empty lines only for separating different tests in one method.

class OverrideRRShift:

def __rrshift__(self, lhs):
return 0 # Force result independent of LHS
Copy link
Member

Choose a reason for hiding this comment

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

Use something more unique. Like 42 or 'spam'.

@@ -819,6 +819,20 @@ binary_op(PyObject *v, PyObject *w, const int op_slot, const char *op_name)
PyObject *result = binary_op1(v, w, op_slot);
if (result == Py_NotImplemented) {
Py_DECREF(result);

if (op_slot == NB_SLOT(nb_rshift) && \
PyCFunction_Check(v) && strcmp(((PyCFunctionObject *)v) -> m_ml -> ml_name, "print") == 0) {
Copy link
Member

Choose a reason for hiding this comment

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

Move strcmp to the next line. Align PyCFunction_Check and strcmp to op_slot.

According to new rules of PEP 7 the opening { after multiline condition should be at new line.

@@ -819,6 +819,20 @@ binary_op(PyObject *v, PyObject *w, const int op_slot, const char *op_name)
PyObject *result = binary_op1(v, w, op_slot);
if (result == Py_NotImplemented) {
Py_DECREF(result);

if (op_slot == NB_SLOT(nb_rshift) && \
Copy link
Member

Choose a reason for hiding this comment

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

\ is not needed.


self.assertEqual(print >> OverrideRRShift(), 0)

python2_print_str = 'print >> 42'
Copy link
Member

Choose a reason for hiding this comment

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

Add a comment about what case is covered by this test. It looks as a duplicate of the first case.

Copy link
Contributor

@ncoghlan ncoghlan left a comment

Choose a reason for hiding this comment

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

Almost there :)

The NEWS entry needs to be adjusted to follow the new process for that, and I spotted a not-quite-right comment in the tests that I missed last time.

self.assertNotIn('Did you mean "print(<message>, '
'file=<output_stream>)', str(context.exception))
print << sys.stderr
self.assertNotIn('Did you mean', str(context.exception))

# Test stream redirection with right argument implemented __rrshift__
Copy link
Contributor

@ncoghlan ncoghlan Aug 18, 2017

Choose a reason for hiding this comment

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

Sorry, I missed this comment on my last review: it should say something like "Ensure right operand implementing __rrshift__ still works"

Misc/NEWS Outdated
@@ -10,6 +10,9 @@ What's New in Python 3.7.0 alpha 1?
Core and Builtins
-----------------

- bpo-30721: ``print`` now shows correct usage hint for using Python 2
redirection syntax. Patch by Sanyam Khurana.

Copy link
Contributor

Choose a reason for hiding this comment

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

We don't edit Misc/NEWS directly any more. Instead, you'll want to add a blurb entry under https://github.com/python/cpython/tree/master/Misc/NEWS.d/next/Core%20and%20Builtins

See the existing entries and https://docs.python.org/devguide/committing.html#what-s-new-and-news-entries for more details

Copy link
Member Author

Choose a reason for hiding this comment

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

Blurb tool looks great! I faced a lot of merge conflicts earlier while rebasing my patches from the latest upstream changes.

This would greatly solve the problem.

@bedevere-bot
Copy link

A Python core developer, ncoghlan, has requested some changes be
made to your pull request before we can consider merging it. If you
could please address their requests along with any other requests in
other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment
on this pull request containing the phrase I didn't expect the Spanish Inquisition!
I will then notify ncoghlan along with any other core developers
who have left a review that you're ready for them to take another look
at this pull request.

@@ -155,6 +156,39 @@ def test_string_with_excessive_whitespace(self):

self.assertIn('print("Hello World", end=" ")', str(context.exception))

def test_stream_redirection_hint_for_py2_migration(self):

Copy link
Member

Choose a reason for hiding this comment

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

This one empty line is redundant.

@@ -821,7 +821,7 @@ binary_op(PyObject *v, PyObject *w, const int op_slot, const char *op_name)
Py_DECREF(result);

if (op_slot == NB_SLOT(nb_rshift) && \
strcmp(v->ob_type->tp_name, "builtin_function_or_method") == 0) {
PyCFunction_Check(v) && strcmp(((PyCFunctionObject *)v) -> m_ml -> ml_name, "print") == 0) {
Copy link
Member

@serhiy-storchaka serhiy-storchaka Aug 18, 2017

Choose a reason for hiding this comment

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

What are reasons for using strncmp() instead of strcmp() here? "print" is NUL terminated, the comparison is limited to 6 chars in any case.

@CuriousLearner
Copy link
Member Author

@serhiy-storchaka using strncmp was suggested by Nick in this comment

@ncoghlan
Copy link
Contributor

Right, that was my suggestion, but I was getting my string comparison algorithms mixed up - strcmp doesn't include CPython's length-based pre-check (since without an O(1) length query it would just waste time), so it's OK to just let it terminate when it hits the end of the "print" literal. strncmp is only necessary when both inputs are potentially arbitrary length.

So yeah, you should change it back to what you had originally - sorry about that :)

@ncoghlan
Copy link
Contributor

Aside from needing to revert my less-than-helpful strncmp suggestion though, this looks good to me now :)

@CuriousLearner
Copy link
Member Author

Alright, fixed everything! :)

I didn't expect the Spanish Inquisition!

@bedevere-bot
Copy link

Nobody expects the Spanish Inquisition!

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

@@ -822,7 +822,7 @@ binary_op(PyObject *v, PyObject *w, const int op_slot, const char *op_name)

if (op_slot == NB_SLOT(nb_rshift) &&
PyCFunction_Check(v) &&
strncmp(((PyCFunctionObject *)v)->m_ml->ml_name, "print", 6) == 0)
strcmp(((PyCFunctionObject *)v) -> m_ml -> ml_name, "print") == 0)
Copy link
Contributor

Choose a reason for hiding this comment

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

No spaces needed around ->.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

@CuriousLearner
Copy link
Member Author

I didn't expect the Spanish Inquisition!

@bedevere-bot
Copy link

Nobody expects the Spanish Inquisition!

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

Copy link
Contributor

@ncoghlan ncoghlan left a comment

Choose a reason for hiding this comment

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

This looks good to me now, so over to @serhiy-storchaka for the final review :)

@serhiy-storchaka serhiy-storchaka merged commit 5e2eb35 into python:master Aug 18, 2017
@CuriousLearner
Copy link
Member Author

Thanks @serhiy-storchaka @ncoghlan :) 🥇

@ncoghlan
Copy link
Contributor

Thanks @CuriousLearner for persevering with the change! I foresee a lot of folks making the Py2->Py3 migration benefiting from your work over the next few years :)

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.

8 participants