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
inspect.isgeneratorfunction fails on hand-created methods #77442
Comments
The inspect functions isgeneratorfunction, iscoroutinefunction, isasyncgenfunction can fail on methods that do not have a __code__ attribute: >>> from types import MethodType
>>> class Callable:
... def __call__(self, *args):
... return args
>>> m = MethodType(Callable(), 42)
>>> m()
(42,)
>>> import inspect
>>> inspect.iscoroutinefunction(m)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib64/python3.6/inspect.py", line 186, in iscoroutinefunction
object.__code__.co_flags & CO_COROUTINE)
AttributeError: 'Callable' object has no attribute '__code__' This was discovered while working on PEP-575, but it is really an independent issue that should be fixed anyway. |
Nick and Raymond, I added both of you as nosy because, among other reasons, you commented on the latest version of PEP-575. I agree that there is a bug in current CPython, in that the is* functions should return, not raise, but I am not sure where is it, and hence if the PR is the right fix. I am wondering if, instead, the bug is in m, the object returned by MethodType, or in attribute lookup thereupon. MethodType is the type of bound user-defined (Python-coded) functions and ismethod is true for instances thereof. Consider 4 other bound methods, two 'normal'(callable is method of instance class) and two, like Jeroen's, 'odd' (callable not related to int). >>> cm = Callable().__call__
>>> cm.__code__
<code object __call__ at 0x00000202C1C53780, file "<pyshell#5>", line 2>
>>> cm.__name__
'__call__'
>>> cm2 = MethodType(Callable.__call__, Callable())
>>> cm2.__code__
<code object __call__ at 0x00000202C1C53780, file "<pyshell#5>", line 2>
>>> cm2.__name__
'__call__'
>>> m2 = MethodType(Callable.__call__, 42)
>>> m2.__code__
<code object __call__ at 0x00000202C1C53780, file "<pyshell#5>", line 2>
>>> m2.__name__
'__call__'
>>> m2()
()
>>> m3 = MethodType(Callable().__call__, 42)
>>> m3.__code__
<code object __call__ at 0x00000202C1C53780, file "<pyshell#5>", line 2>
>>> m3.__name__
'__call__'
>>> m3()
(42,)
>>> m = MethodType(Callable(), 42)
>>> m.__code__
... AttributeError: 'Callable' object has no attribute '__code__'
>>> m.__name__
... AttributeError: 'Callable' object has no attribute '__name__'
>>> m()
(42,) They all have the same attributes exposed by dir(), which omits both'__name__', promised in the docstring for ismethod*, and '__code__', assumed by the is--- functions under discussion. >>> dir(cm) == dir(cm2) == dir(m) == dir(m2) == dir(m3)
True
>>> ('__code__' in dir(cm)) or ('__name__' in dir(cm))
False However, accessing those names anyway, as (hidden) attributes, works for all but m. Should doing so also work for m? (If not, the ismethod docstring should not 'guarantee' .__name__.)
|
The inspect functions throwing an exception when handed a method wrapping a non-function callable instead of returning False is a definite bug. The behaviour of MethodType when handed a non-function callable is cryptic, but not actually a bug: the problem is that it expects to be able to delegate accesses to __name__ and __code__ to the underlying callable. A docs bug to clarify exactly which accesses are going to get delegated would make sense (and perhaps make MethodType.__dir__ a bit smarter about that delegation), but it's a separate issue from the one Jeroen has reported here. |
What would you expect m.__code__ to return then? Methods support arbitrary callables and certainly not all callables have a meaningful __code__. |
The only attributes that a method is guaranteed to have are __func__ and __self__ (which are the arguments passed to the MethodType constructor). All other attributes are looked up through __func__ by the C version of the following Python code: def __getattr__(self, attr):
return getattr(self.__func__, attr) |
I would like python_coded_callable.__code__ to be the code object executed when python_coded_callable is called, just as expected by the isxyz author(s). It has to exist somewhere. Methods m and m3 both return 42 when called, and both have the same code object. >>> m3.__code__
<code object __call__ at 0x000001431C797E40, file "F:\Python\a\tem2.py", line 3>
>>> m3.__func__.__code__
<code object __call__ at 0x000001431C797E40, file "F:\Python\a\tem2.py", line 3>
>>> m.__func__.__call__.__code__
<code object __call__ at 0x000001431C797E40, file "F:\Python\a\tem2.py", line 3> The fact that m requires an additional level of indirection is an implementation detail that I don't think necessarily has to involve users. |
First of all, that doesn't answer the question of what to do with non-Python coded callables where there is no __code__ object at all. Second, are you *really* sure that you want that? That would mean adding a __code__ attribute to all callable Python classes or adding a __code__ descriptor to "type". |
Terry, if you'd like to continue that discussion, please open a new enhancement request for 3.8+ against the inspect module asking for the affected introspection functions to recursively search for relevant attributes, the same way However, the fact the inspect module doesn't implement that search for relevant attributes today is *not* a bug - it's a past design decision that could potentially stand to be revisited. By contrast, the fact the affected functions throw AttributeError instead of returning False for a missing attribute *is* a bug. |
Can we please go back to the original issue? If you think that __code__ should be an alias for __call__.__code__, that is a different issue. On #6448 (comment) I posted an alternative solution for the problem. Is either the original PR or the new variant acceptable? |
Can somebody please review PR 6448? |
I just reviewed, and I plan to merge it if I don't see any pushback from the others. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: