mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +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. | ||||
| 
 | ||||
|    .. versionchanged:: 3.8 | ||||
|       Functions wrapped in :func:`functools.partial` now return true if the | ||||
|       wrapped function is a Python generator function. | ||||
| 
 | ||||
| 
 | ||||
| .. function:: isgenerator(object) | ||||
| 
 | ||||
|  | @ -311,6 +315,10 @@ attributes: | |||
| 
 | ||||
|    .. 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) | ||||
| 
 | ||||
|  | @ -352,6 +360,10 @@ attributes: | |||
| 
 | ||||
|    .. 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) | ||||
| 
 | ||||
|  |  | |||
|  | @ -423,6 +423,12 @@ def __get__(self, obj, cls): | |||
|     def __isabstractmethod__(self): | ||||
|         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 | ||||
|  |  | |||
|  | @ -168,30 +168,33 @@ def isfunction(object): | |||
|         __kwdefaults__  dict of keyword only parameters with defaults""" | ||||
|     return isinstance(object, types.FunctionType) | ||||
| 
 | ||||
| def isgeneratorfunction(object): | ||||
| def isgeneratorfunction(obj): | ||||
|     """Return true if the object is a user-defined generator function. | ||||
| 
 | ||||
|     Generator function objects provide the same attributes as functions. | ||||
|     See help(isfunction) for a list of attributes.""" | ||||
|     return bool((isfunction(object) or ismethod(object)) and | ||||
|                 object.__code__.co_flags & CO_GENERATOR) | ||||
|     obj = functools._unwrap_partial(obj) | ||||
|     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. | ||||
| 
 | ||||
|     Coroutine functions are defined with "async def" syntax. | ||||
|     """ | ||||
|     return bool((isfunction(object) or ismethod(object)) and | ||||
|                 object.__code__.co_flags & CO_COROUTINE) | ||||
|     obj = functools._unwrap_partial(obj) | ||||
|     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. | ||||
| 
 | ||||
|     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) | ||||
|     obj = functools._unwrap_partial(obj) | ||||
|     return bool((isfunction(obj) or ismethod(obj)) and | ||||
|                 obj.__code__.co_flags & CO_ASYNC_GENERATOR) | ||||
| 
 | ||||
| def isasyncgen(object): | ||||
|     """Return true if the object is an asynchronous generator.""" | ||||
|  |  | |||
|  | @ -440,8 +440,8 @@ async def func(x, y): | |||
| 
 | ||||
|         coro_repr = repr(task._coro) | ||||
|         expected = ( | ||||
|             r'<CoroWrapper \w+.test_task_repr_partial_corowrapper' | ||||
|             r'\.<locals>\.func\(1\)\(\) running, ' | ||||
|             r'<coroutine object \w+\.test_task_repr_partial_corowrapper' | ||||
|             r'\.<locals>\.func at' | ||||
|         ) | ||||
|         self.assertRegex(coro_repr, expected) | ||||
| 
 | ||||
|  |  | |||
|  | @ -166,26 +166,51 @@ def test_excluding_predicates(self): | |||
|             self.assertFalse(inspect.ismemberdescriptor(datetime.timedelta.days)) | ||||
| 
 | ||||
|     def test_iscoroutine(self): | ||||
|         async_gen_coro = async_generator_function_example(1) | ||||
|         gen_coro = gen_coroutine_function_example(1) | ||||
|         coro = coroutine_function_example(1) | ||||
| 
 | ||||
|         self.assertFalse( | ||||
|             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.assertTrue( | ||||
|             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.iscoroutinefunction(coroutine_function_example)) | ||||
|         self.assertTrue( | ||||
|             inspect.iscoroutinefunction( | ||||
|                 functools.partial(functools.partial( | ||||
|                     coroutine_function_example)))) | ||||
|         self.assertTrue(inspect.iscoroutine(coro)) | ||||
| 
 | ||||
|         self.assertFalse( | ||||
|             inspect.isgeneratorfunction(coroutine_function_example)) | ||||
|         self.assertFalse( | ||||
|             inspect.isgeneratorfunction( | ||||
|                 functools.partial(functools.partial( | ||||
|                     coroutine_function_example)))) | ||||
|         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 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