title: Extract Python function source text from the source code string
body: <p>Suppose I have valid Python source code, as a string:</p>
  
  <pre><code>code_string = """
  # A comment.
  def foo(a, b):
    return a + b
  class Bar(object):
    def __init__(self):
      self.my_list = [
          'a',
          'b',
      ]
  """.strip()
  </code></pre>
  
  <p><strong>Objective:</strong> I would like to obtain the lines containing the source code of the function definitions, preserving whitespace. For the code string above, I would like to get the strings</p>
  
  <pre><code>def foo(a, b):
    return a + b
  </code></pre>
  
  <p>and</p>
  
  <pre><code>  def __init__(self):
      self.my_list = [
          'a',
          'b',
      ]
  </code></pre>
  
  <p>Or, equivalently, I'd be happy to get the line numbers of functions in the code string: <code>foo</code> spans lines 2-3, and <code>__init__</code> spans lines 5-9.</p>
  
  <p><strong>Attempts</strong></p>
  
  <p>I can parse the code string into its AST:</p>
  
  <pre><code>code_ast = ast.parse(code_string)
  </code></pre>
  
  <p>And I can find the <code>FunctionDef</code> nodes, e.g.:</p>
  
  <pre><code>function_def_nodes = [node for node in ast.walk(code_ast)
                        if isinstance(node, ast.FunctionDef)]
  </code></pre>
  
  <p>Each <code>FunctionDef</code> node's <code>lineno</code> attribute tells us the first line for that function. We can estimate the last line of that function with:</p>
  
  <pre><code>last_line = max(node.lineno for node in ast.walk(function_def_node)
                  if hasattr(node, 'lineno'))
  </code></pre>
  
  <p>but this doesn't work perfectly when the function ends with syntactic elements that don't show up as AST nodes, for instance the last <code>]</code> in <code>__init__</code>.</p>
  
  <p>I doubt there is an approach that only uses the AST, because the AST fundamentally does not have enough information in cases like <code>__init__</code>.</p>
  
  <p>I cannot use the <code>inspect</code> module because that only works on "live objects" and I only have the Python code as a string. I cannot <code>eval</code> the code because that's a huge security headache.</p>
  
  <p>In theory I could write a parser for Python but that really seems like overkill.</p>
  
  <p>A heuristic suggested in the comments is to use the leading whitespace of lines. However, that can break for strange but valid functions with weird indentation like:</p>
  
  <pre><code>def baz():
    return [
  1,
    ]
  
  class Baz(object):
    def hello(self, x):
      return self.hello(
  x - 1)
  
  def my_type_annotated_function(
    my_long_argument_name: SomeLongArgumentTypeName
  ) -&gt; SomeLongReturnTypeName:
    # This function's indentation isn't unusual at all.
    pass
  </code></pre>