Issue 14611: inspect.getargs fails on some anonymous tuples (original) (raw)
How to reproduce
Take the following two functions:
>>> def f(l, (x, y)):
... sup = max(u*x + v*y for u, v in l)
... return ((u, v) for u, v in l if u*x + v*y == sup)
>>> def g((x, y)):
... def h():
... return x + y
... return h
Inspect.getargs will throw an exception on the former and return a wrong result on the latter::
>>> import inspect
>>> inspect.getargs(f.__code__)
Traceback (most recent call last):
...
IndexError: list index out of range
>>> inspect.getargs(g.__code__)
Arguments(args=['h'], varargs=None, keywords=None)
# h is most definitely not an argument of g!
Analysis
If you disassemble the two functions, you'll see that in both cases the anonymous tuples are unpacked using STORE_DEREF::
>>> import dis
>>> dis.disassemble(f.__code__)
1 0 LOAD_FAST 1 (.1)
3 UNPACK_SEQUENCE 2
6 STORE_DEREF 0 (x)
9 STORE_DEREF 2 (y)
<BLANKLINE>
2 12 LOAD_GLOBAL 0 (max)
...
>>> dis.disassemble(g.__code__)
1 0 LOAD_FAST 0 (.0)
3 UNPACK_SEQUENCE 2
6 STORE_DEREF 0 (x)
9 STORE_DEREF 1 (y)
<BLANKLINE>
2 12 LOAD_CLOSURE 1 (y)
15 LOAD_CLOSURE 0 (x)
18 BUILD_TUPLE 2
21 LOAD_CONST 1 (<code object h ...>)
24 MAKE_CLOSURE 0
27 STORE_FAST 3 (h)
<BLANKLINE>
4 30 LOAD_FAST 3 (h)
33 RETURN_VALUE \
However, the implementation of inspect.getargs only looks for UNPACK_TUPLE, UNPACK_SEQUENCE, STORE_FAST.
Notes
The version of Python used is::
>>> import sys
>>> sys.version_info[:3]
(2, 7, 3)
I think this should do.
inspect.getargs is now looking for STORE_DEREF besides STORE_FAST, and is making sure that the appropriate namespace (locals vs cell + free vars) is selected depending on the opcode.
The only changes to the test suite are three additional tests, based on the two examples above.