mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	bpo-34890: Make iscoroutinefunction, isgeneratorfunction and isasyncgenfunction work with functools.partial (GH-9903)
inspect.isfunction() processes both inspect.isfunction(func) and inspect.isfunction(partial(func, arg)) correctly but some other functions in the inspect module (iscoroutinefunction, isgeneratorfunction and isasyncgenfunction) lack this functionality. This commits adds a new check in the mentioned functions in the inspect module so they can work correctly with arbitrarily nested partial functions.
This commit is contained in:
		
							parent
							
								
									e483f02423
								
							
						
					
					
						commit
						7cd2543416
					
				
					 6 changed files with 61 additions and 12 deletions
				
			
		|  | @ -298,6 +298,10 @@ attributes: | ||||||
| 
 | 
 | ||||||
|    Return true if the object is a Python generator function. |    Return true if the object is a Python generator function. | ||||||
| 
 | 
 | ||||||
|  |    .. versionchanged:: 3.8 | ||||||
|  |       Functions wrapped in :func:`functools.partial` now return true if the | ||||||
|  |       wrapped function is a Python generator function. | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| .. function:: isgenerator(object) | .. function:: isgenerator(object) | ||||||
| 
 | 
 | ||||||
|  | @ -311,6 +315,10 @@ attributes: | ||||||
| 
 | 
 | ||||||
|    .. versionadded:: 3.5 |    .. versionadded:: 3.5 | ||||||
| 
 | 
 | ||||||
|  |    .. versionchanged:: 3.8 | ||||||
|  |       Functions wrapped in :func:`functools.partial` now return true if the | ||||||
|  |       wrapped function is a :term:`coroutine function`. | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| .. function:: iscoroutine(object) | .. function:: iscoroutine(object) | ||||||
| 
 | 
 | ||||||
|  | @ -352,6 +360,10 @@ attributes: | ||||||
| 
 | 
 | ||||||
|    .. versionadded:: 3.6 |    .. versionadded:: 3.6 | ||||||
| 
 | 
 | ||||||
|  |    .. versionchanged:: 3.8 | ||||||
|  |       Functions wrapped in :func:`functools.partial` now return true if the | ||||||
|  |       wrapped function is a :term:`asynchronous generator` function. | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| .. function:: isasyncgen(object) | .. function:: isasyncgen(object) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -423,6 +423,12 @@ def __get__(self, obj, cls): | ||||||
|     def __isabstractmethod__(self): |     def __isabstractmethod__(self): | ||||||
|         return getattr(self.func, "__isabstractmethod__", False) |         return getattr(self.func, "__isabstractmethod__", False) | ||||||
| 
 | 
 | ||||||
|  | # Helper functions | ||||||
|  | 
 | ||||||
|  | def _unwrap_partial(func): | ||||||
|  |     while isinstance(func, partial): | ||||||
|  |         func = func.func | ||||||
|  |     return func | ||||||
| 
 | 
 | ||||||
| ################################################################################ | ################################################################################ | ||||||
| ### LRU Cache function decorator | ### LRU Cache function decorator | ||||||
|  |  | ||||||
|  | @ -168,30 +168,33 @@ def isfunction(object): | ||||||
|         __kwdefaults__  dict of keyword only parameters with defaults""" |         __kwdefaults__  dict of keyword only parameters with defaults""" | ||||||
|     return isinstance(object, types.FunctionType) |     return isinstance(object, types.FunctionType) | ||||||
| 
 | 
 | ||||||
| def isgeneratorfunction(object): | def isgeneratorfunction(obj): | ||||||
|     """Return true if the object is a user-defined generator function. |     """Return true if the object is a user-defined generator function. | ||||||
| 
 | 
 | ||||||
|     Generator function objects provide the same attributes as functions. |     Generator function objects provide the same attributes as functions. | ||||||
|     See help(isfunction) for a list of attributes.""" |     See help(isfunction) for a list of attributes.""" | ||||||
|     return bool((isfunction(object) or ismethod(object)) and |     obj = functools._unwrap_partial(obj) | ||||||
|                 object.__code__.co_flags & CO_GENERATOR) |     return bool((isfunction(obj) or ismethod(obj)) and | ||||||
|  |                 obj.__code__.co_flags & CO_GENERATOR) | ||||||
| 
 | 
 | ||||||
| def iscoroutinefunction(object): | def iscoroutinefunction(obj): | ||||||
|     """Return true if the object is a coroutine function. |     """Return true if the object is a coroutine function. | ||||||
| 
 | 
 | ||||||
|     Coroutine functions are defined with "async def" syntax. |     Coroutine functions are defined with "async def" syntax. | ||||||
|     """ |     """ | ||||||
|     return bool((isfunction(object) or ismethod(object)) and |     obj = functools._unwrap_partial(obj) | ||||||
|                 object.__code__.co_flags & CO_COROUTINE) |     return bool(((isfunction(obj) or ismethod(obj)) and | ||||||
|  |                 obj.__code__.co_flags & CO_COROUTINE)) | ||||||
| 
 | 
 | ||||||
| def isasyncgenfunction(object): | def isasyncgenfunction(obj): | ||||||
|     """Return true if the object is an asynchronous generator function. |     """Return true if the object is an asynchronous generator function. | ||||||
| 
 | 
 | ||||||
|     Asynchronous generator functions are defined with "async def" |     Asynchronous generator functions are defined with "async def" | ||||||
|     syntax and have "yield" expressions in their body. |     syntax and have "yield" expressions in their body. | ||||||
|     """ |     """ | ||||||
|     return bool((isfunction(object) or ismethod(object)) and |     obj = functools._unwrap_partial(obj) | ||||||
|                 object.__code__.co_flags & CO_ASYNC_GENERATOR) |     return bool((isfunction(obj) or ismethod(obj)) and | ||||||
|  |                 obj.__code__.co_flags & CO_ASYNC_GENERATOR) | ||||||
| 
 | 
 | ||||||
| def isasyncgen(object): | def isasyncgen(object): | ||||||
|     """Return true if the object is an asynchronous generator.""" |     """Return true if the object is an asynchronous generator.""" | ||||||
|  |  | ||||||
|  | @ -440,8 +440,8 @@ async def func(x, y): | ||||||
| 
 | 
 | ||||||
|         coro_repr = repr(task._coro) |         coro_repr = repr(task._coro) | ||||||
|         expected = ( |         expected = ( | ||||||
|             r'<CoroWrapper \w+.test_task_repr_partial_corowrapper' |             r'<coroutine object \w+\.test_task_repr_partial_corowrapper' | ||||||
|             r'\.<locals>\.func\(1\)\(\) running, ' |             r'\.<locals>\.func at' | ||||||
|         ) |         ) | ||||||
|         self.assertRegex(coro_repr, expected) |         self.assertRegex(coro_repr, expected) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -166,26 +166,51 @@ def test_excluding_predicates(self): | ||||||
|             self.assertFalse(inspect.ismemberdescriptor(datetime.timedelta.days)) |             self.assertFalse(inspect.ismemberdescriptor(datetime.timedelta.days)) | ||||||
| 
 | 
 | ||||||
|     def test_iscoroutine(self): |     def test_iscoroutine(self): | ||||||
|  |         async_gen_coro = async_generator_function_example(1) | ||||||
|         gen_coro = gen_coroutine_function_example(1) |         gen_coro = gen_coroutine_function_example(1) | ||||||
|         coro = coroutine_function_example(1) |         coro = coroutine_function_example(1) | ||||||
| 
 | 
 | ||||||
|         self.assertFalse( |         self.assertFalse( | ||||||
|             inspect.iscoroutinefunction(gen_coroutine_function_example)) |             inspect.iscoroutinefunction(gen_coroutine_function_example)) | ||||||
|  |         self.assertFalse( | ||||||
|  |             inspect.iscoroutinefunction( | ||||||
|  |                 functools.partial(functools.partial( | ||||||
|  |                     gen_coroutine_function_example)))) | ||||||
|         self.assertFalse(inspect.iscoroutine(gen_coro)) |         self.assertFalse(inspect.iscoroutine(gen_coro)) | ||||||
| 
 | 
 | ||||||
|         self.assertTrue( |         self.assertTrue( | ||||||
|             inspect.isgeneratorfunction(gen_coroutine_function_example)) |             inspect.isgeneratorfunction(gen_coroutine_function_example)) | ||||||
|  |         self.assertTrue( | ||||||
|  |             inspect.isgeneratorfunction( | ||||||
|  |                 functools.partial(functools.partial( | ||||||
|  |                     gen_coroutine_function_example)))) | ||||||
|         self.assertTrue(inspect.isgenerator(gen_coro)) |         self.assertTrue(inspect.isgenerator(gen_coro)) | ||||||
| 
 | 
 | ||||||
|         self.assertTrue( |         self.assertTrue( | ||||||
|             inspect.iscoroutinefunction(coroutine_function_example)) |             inspect.iscoroutinefunction(coroutine_function_example)) | ||||||
|  |         self.assertTrue( | ||||||
|  |             inspect.iscoroutinefunction( | ||||||
|  |                 functools.partial(functools.partial( | ||||||
|  |                     coroutine_function_example)))) | ||||||
|         self.assertTrue(inspect.iscoroutine(coro)) |         self.assertTrue(inspect.iscoroutine(coro)) | ||||||
| 
 | 
 | ||||||
|         self.assertFalse( |         self.assertFalse( | ||||||
|             inspect.isgeneratorfunction(coroutine_function_example)) |             inspect.isgeneratorfunction(coroutine_function_example)) | ||||||
|  |         self.assertFalse( | ||||||
|  |             inspect.isgeneratorfunction( | ||||||
|  |                 functools.partial(functools.partial( | ||||||
|  |                     coroutine_function_example)))) | ||||||
|         self.assertFalse(inspect.isgenerator(coro)) |         self.assertFalse(inspect.isgenerator(coro)) | ||||||
| 
 | 
 | ||||||
|         coro.close(); gen_coro.close() # silence warnings |         self.assertTrue( | ||||||
|  |             inspect.isasyncgenfunction(async_generator_function_example)) | ||||||
|  |         self.assertTrue( | ||||||
|  |             inspect.isasyncgenfunction( | ||||||
|  |                 functools.partial(functools.partial( | ||||||
|  |                     async_generator_function_example)))) | ||||||
|  |         self.assertTrue(inspect.isasyncgen(async_gen_coro)) | ||||||
|  | 
 | ||||||
|  |         coro.close(); gen_coro.close(); # silence warnings | ||||||
| 
 | 
 | ||||||
|     def test_isawaitable(self): |     def test_isawaitable(self): | ||||||
|         def gen(): yield |         def gen(): yield | ||||||
|  |  | ||||||
|  | @ -0,0 +1,3 @@ | ||||||
|  | Make :func:`inspect.iscoroutinefunction`, | ||||||
|  | :func:`inspect.isgeneratorfunction` and :func:`inspect.isasyncgenfunction` | ||||||
|  | work with :func:`functools.partial`. Patch by Pablo Galindo. | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Pablo Galindo
						Pablo Galindo