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-34890: Make iscoroutinefunction, isgeneratorfunction and isasyncgenfunction work with functools.partial #9903

Merged
merged 8 commits into from
Oct 26, 2018

Conversation

pablogsal
Copy link
Member

@pablogsal pablogsal commented Oct 15, 2018

I have added some extra test cases for isasyncgenfunction and isasyncgen in test_iscoroutine for symmetry, but I am not sure if that is the place for it. Tell me if I should reallocate these tests in other place.

https://bugs.python.org/issue34890

@pablogsal pablogsal self-assigned this Oct 15, 2018
@pablogsal pablogsal requested review from 1st1 and asvetlov October 15, 2018 22:14
@pablogsal pablogsal changed the title Make iscoroutinefunction, isgeneratorfunction and isasyncgenfunction work with functools.partial bpo-34890: Make iscoroutinefunction, isgeneratorfunction and isasyncgenfunction work with functools.partial Oct 15, 2018
Lib/inspect.py Outdated
object.__code__.co_flags & CO_GENERATOR)
is_partial_generator = bool((isinstance(object, functools.partial) and
object.func.__code__.co_flags & CO_GENERATOR))
return is_generator_function or is_partial_generator
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't it be better to do this:

while isinstance(object, functools.partial):
    object = object.func

? The benefit would be support of nested partials and the code will probably look less ugly.

Copy link
Member

Choose a reason for hiding this comment

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

Also, while you're here, please rename object to obj or o.

@pablogsal
Copy link
Member Author

pablogsal commented Oct 15, 2018

@1st1 Done in 41f7f60. Thanks for the review :)

Lib/inspect.py Outdated
"""Return true if the object is an asynchronous generator function.

Asynchronous generator functions are defined with "async def"
syntax and have "yield" expressions in their body.
"""
return bool((isfunction(object) or ismethod(object)) and
object.__code__.co_flags & CO_ASYNC_GENERATOR)
while isinstance(obj, functools.partial):
Copy link
Member

Choose a reason for hiding this comment

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

Why don't we add an _unwrap_partial helper. Or.. should we maybe use inspect.unwrap() too?

Copy link
Member

Choose a reason for hiding this comment

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

Actually, let's not use inspect.unwrap(). Decorating generators and coroutines isn't that common.

Copy link
Member Author

Choose a reason for hiding this comment

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

Do we still want the helper?

Copy link
Member Author

Choose a reason for hiding this comment

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

I have done the refactor in 3b841ac

@@ -2,6 +2,7 @@
import collections
import datetime
import functools
from functools import partial
Copy link
Member

Choose a reason for hiding this comment

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

Please use module import (i.e. import functools).

Copy link
Member Author

Choose a reason for hiding this comment

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

Done in adb5db6. I did that originally to simplify the double functools.partial wrapping in the tests.

@pablogsal
Copy link
Member Author

pablogsal commented Oct 15, 2018

Hummmm, it seems that this changes the behaviour of coroutine in Lib/asyncio/coroutines.py:

def coroutine(func):
    """Decorator to mark coroutines.

    If the coroutine is not yielded from before it is destroyed,
    an error message is logged.
    """
    if inspect.iscoroutinefunction(func):
        # In Python 3.5 that's all we need to do for coroutines
        # defined with "async def".
        return func

    if inspect.isgeneratorfunction(func):
        coro = func
    else:
        @functools.wraps(func)
        def coro(*args, **kw):

this makes the function not being wrapped in CoroWrapper anymore.

@1st1 I don't know if this has a bit more impact than expected. Should I fix this by adding an extra check for partial in coroutine() (check commit 617688e to see what I mean), or should we approach this differently?

Copy link
Member

@1st1 1st1 left a comment

Choose a reason for hiding this comment

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

Please remove new checks from asyncio.coroutine; AFAICT we don't need them.

@@ -107,12 +107,14 @@ def coroutine(func):
If the coroutine is not yielded from before it is destroyed,
an error message is logged.
"""
if inspect.iscoroutinefunction(func):
if (inspect.iscoroutinefunction(func)
and not isinstance(func, functools.partial)):
Copy link
Member

Choose a reason for hiding this comment

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

We don't need extra checks in this function. Here, we check for an async def coroutine -- we don't need to wrap them in a CoroWrapper ever.

# In Python 3.5 that's all we need to do for coroutines
# defined with "async def".
return func

if inspect.isgeneratorfunction(func):
if (inspect.isgeneratorfunction(func)
and not isinstance (func, functools.partial)):
Copy link
Member

Choose a reason for hiding this comment

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

We don't need this extra check either -- if func is a generator function, it means that we don't need to wrap it into a proxy to force it to return a generator.

@bedevere-bot
Copy link

When you're done making the requested changes, leave the comment: I have made the requested changes; please review again.

@pablogsal
Copy link
Member Author

pablogsal commented Oct 16, 2018

@1st1 If I remove those checks then https://github.com/python/cpython/blob/master/Lib/test/test_asyncio/test_tasks.py#L425 will fail because the partials are not wrapped anymore and the __reprl__ will be different. I was not sure if this would be a problem or not (notice commit 617688e was just to show what I mean with "fix this by adding an extra check for partial in coroutine()" in my previous comment). Should I change the tests now that we don't need to wrap these (proposal of that change in commit caddd21)?

This is an example failure of that test for clarity:

======================================================================
FAIL: test_task_repr_partial_corowrapper (test.test_asyncio.test_tasks.PyTask_PyFuture_Tests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/pablogsal/github/cpython/Lib/test/test_asyncio/test_tasks.py", line 446, in test_task_repr_partial_corowrapper
    self.assertRegex(coro_repr, expected)
AssertionError: Regex didn't match: '<CoroWrapper \\w+.test_task_repr_partial_corowrapper\\.<locals>\\.func\\(1\\)\\(\\) running, ' not found in '<coroutine object BaseTaskTests.test_task_repr_partial_corowrapper.<locals>.func at 0x7f30c10b9788>'

----------------------------------------------------------------------

Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

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

I like the overall change.

@1st1: Would you mind to review this change?

Lib/inspect.py Outdated
def _unwrap_partial(func):
while isinstance(func, functools.partial):
func = func.func
return func
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 move this helper function to Lib/functools.py.

@@ -0,0 +1,3 @@
Make :func:`inspect.iscoroutinefunction`,
:func:`inspect.isgeneratorfunction` and :func:`inspect.isasyncgenfunction`
work with :func:`functools.partial`. Patch by Pablo Galindo.
Copy link
Member

Choose a reason for hiding this comment

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

Maybe also document the change with a ".. versionchanged:: 3.8" markup in https://docs.python.org/dev/library/inspect.html#inspect.isgeneratorfunction ?

@vstinner
Copy link
Member

(Oh, @1st1 already started to review this change, good ;-))

@pablogsal pablogsal requested a review from rhettinger as a code owner October 25, 2018 22:29
Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

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

LGTM, but I would prefer to see a second review from a core (@1st1 maybe?).

@1st1
Copy link
Member

1st1 commented Oct 26, 2018

@1st1 If I remove those checks then https://github.com/python/cpython/blob/master/Lib/test/test_asyncio/test_tasks.py#L425 will fail because the partials are not wrapped anymore and the reprl will be different. I was not sure if this would be a problem or not

It's not a problem. Now that inspect.isgeneratorfunction works with functools.partial we no longer need to wrap partial objects in a proxy generator "just in case".

Copy link
Member

@1st1 1st1 left a comment

Choose a reason for hiding this comment

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

Sorry for the delayed review; I've been extremely busy last couple of weeks.

@pablogsal pablogsal merged commit 7cd2543 into python:master Oct 26, 2018
@pablogsal pablogsal deleted the bpo34890 branch October 26, 2018 11:19
@pablogsal
Copy link
Member Author

@1st1 Thank you very much for the review and the advice, Yuri! :)

@jdemeyer
Copy link
Contributor

jdemeyer commented Nov 5, 2018

See also #6448 for a very related problem.

# Helper functions

def _unwrap_partial(func):
while isinstance(func, partial):
Copy link
Member

Choose a reason for hiding this comment

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

@pablogsal BTW this doesn't handle the case of partialmethod. Should we add that?

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.

6 participants