The main problem is that you need to analyze the Python source code, which TeX can't (easily) do. Luckily Python is capable of analyzing itself, using abstract syntax trees (see also some more thorough documentation). The idea is that we have the Python ast analyze the code and write what it finds to a TeX file.
#!/usr/local/bin/python3
import ast
""" The python file we want to analyze. Happens to be itself """
pythonfilename = 'pythonlinenumbers.py'
newcommands = []
def makenewcommand(command,
output):
""" Turns the command and line number into the appropriate command.
The signature is split onto two lines to make it more complicated.
We have to play tricks with the trailing \, because we can't end a string
with a single backslash. """
return r'\newcommand{''\\'+command+'}{'+str(output)+'}\n'
class FuncLister(ast.NodeVisitor):
def visit_FunctionDef(self, node):
""" Recursively visit all functions, determining where each function
starts, where its signature ends, and where the function ends. Store
these in the TeX variables \firstline@funcname, \sigend@funcname,
\docend@funcname, and \lastline@funcname. """
newname=node.name.replace('_','@') # _ isn't allowed in a TeX command
newcommands.append(makenewcommand('firstline@'+newname,node.lineno))
sigend = max(node.lineno,lastline(node.args))
newcommands.append(makenewcommand('sigend@'+newname,sigend))
docstring = ast.get_docstring(node)
docstringlength = len(docstring.split('\n')) if docstring else -1
newcommands.append(makenewcommand('docend@'+newname,sigend+docstringlength))
newcommands.append(makenewcommand('lastline@'+newname,lastline(node)))
self.generic_visit(node)
def lastline(node):
""" Recursively find the last line of a node """
return max( [ node.lineno if hasattr(node,'lineno') else -1 , ]
+[lastline(child) for child in ast.iter_child_nodes(node)] )
with open(pythonfilename) as f:
code = f.read()
FuncLister().visit(ast.parse(code))
with open('linenumbers.tex','w') as f:
for newcommand in newcommands:
f.write(newcommand)
Creates the file linenumbers.tex:
\newcommand{\firstline@makenewcommand}{10}
\newcommand{\sigend@makenewcommand}{11}
\newcommand{\docend@makenewcommand}{15}
\newcommand{\lastline@makenewcommand}{16}
\newcommand{\firstline@visit@FunctionDef}{19}
\newcommand{\sigend@visit@FunctionDef}{19}
\newcommand{\docend@visit@FunctionDef}{23}
\newcommand{\lastline@visit@FunctionDef}{32}
\newcommand{\firstline@lastline}{34}
\newcommand{\sigend@lastline}{34}
\newcommand{\docend@lastline}{35}
\newcommand{\lastline@lastline}{37}
Then, you can have your main TeX file input the derived TeX file, and use what it found:
\documentclass{article}
\usepackage{listings}
\immediate\write18{python3 pythonlinenumbers.py}
\makeatletter
\input{linenumbers}
\newcommand{\showfunc}[1]{%
#1 signature:
\lstinputlisting[
firstline=\csname firstline@#1\endcsname,
lastline=\csname sigend@#1\endcsname,
language=Python]
{pythonlinenumbers.py}
#1 in its entirety:
\lstinputlisting[
firstline=\csname firstline@#1\endcsname,
lastline=\csname lastline@#1\endcsname,
language=Python]
{pythonlinenumbers.py}}
\makeatother
\begin{document}
Here's the lastline function:
\showfunc{lastline}
\bigskip
Here's the makenewcommand function (also has a multi-line signature):
\showfunc{makenewcommand}
\end{document}
This results in

\lstinputlisting[language=Python,include=myfunction]{filename.py}Where
– Jul 18 '16 at 14:19include=...is some function that finds the line numbers fordef myfunction(...):and the last line in the function and then expands tofirstline=#,lastline=#? Interesting question!