1

I think I'm aware of the usual causes for IndentationError like described in IndentationError: unindent does not match any outer indentation level for example. This doesn't apply here.

Also, I know about textwrap.dedent but it doesn't feel like it's the right approach here?


If I have a "regular" function, I can do ast.parse and ast.walk like this:

import ast
import inspect

def a():
    pass

code = inspect.getsource(a)
nodes = ast.walk(ast.parse(code))
for node in nodes:
    ...

However, if the function is a method inside a class like:

class B:
    def c(self):
        pass

code = inspect.getsource(B.c)
nodes = ast.walk(ast.parse(code))

I get:

IndentationError: unexpected indent

Which makes sense, I guess, since B.c is indented by one level. So how do I ast.parse and ast.walk here instead?

finefoot
  • 9,914
  • 7
  • 59
  • 102
  • Possible duplicate of [IndentationError: unindent does not match any outer indentation level](https://stackoverflow.com/questions/492387/indentationerror-unindent-does-not-match-any-outer-indentation-level) – ivan_pozdeev Jun 06 '19 at 14:17
  • 2
    You could parse the entire class and then find the matching function definition inside that. But that function definition may not correspond to the value of `B.c` at runtime, which may be a good thing or bad thing. – Alex Hall Jun 06 '19 at 14:43
  • OK, I retract my previous support for `dedent` as it doesn't work in all cases. I've actually seen a library that used it and people who used that library ran into the edge case and reported a bug that took a while to figure out. Basically it's easy for B.c to contain some code which is less indented than the `def c` part. So if you want something that works in all cases you'll need another solution. But you'll also need to explain your use case some more as the solution will depend on that. See my comment above, for example. – Alex Hall Jan 06 '20 at 13:29

1 Answers1

8

Its because you grabbed the method than tried walking it without undoing the indents. Your class is:

class B:
    def c(self):
        pass

code = inspect.getsource(B.c)
nodes = ast.walk(ast.parse(code))

If you print code you see:

    def c(self):
        pass

Note: The above code has one indent. You need to un-indent it:

import inspect
import ast
import textwrap
class B:
    def c(self):
        pass
code = textwrap.dedent(inspect.getsource(B.c))
nodes = ast.walk(ast.parse(code))
  • 2
    `ast` is designed to read valid python code from text. You parsed a subset of valid code then passed it to `ast` as text, but since you only grabbed part of the code it is no longer valid (bad indenting). – Error - Syntactical Remorse Jun 06 '19 at 14:32
  • 2
    I should add that this will work for the problem you have presented, but if you want to make modifications to the AST and compile it into a new function, then the context such as the class will matter and this solution won't be enough. – Alex Hall Jun 06 '19 at 14:40