Using the inspect.getsourcelines
function, I have been able to get a Python function's source code like this:
import inspect
def some_decorator(x):
return x
@some_decorator
def foo():
print("bar")
print(inspect.getsourcelines(foo)[0])
This code will correctly output the source lines of the function as a list:
['@some_decorator\n', 'def foo():\n', ' print("bar")\n']
However, I only want the code inside the function, not the entire function declaration. So I only want this output (noting also the correct indentation):
['print("bar")\n']
I have attempted to do this using a slice and a strip
to remove the first two lines and then remove indentation, but this wouldn't work with many functions and I have to believe there's a better way.
Does the inspect
module, or another module which I can pip install
, have this functionality?
您会发现所有想要的代码之前都为空,因此您可以尝试一下
print filter(lambda x:x.startswith(' '), inspect.getsourcelines(foo)[0])
You can do something like this:
import inspect
from itertools import dropwhile
def get_function_body(func):
source_lines = inspect.getsourcelines(func)[0]
source_lines = dropwhile(lambda x: x.startswith('@'), source_lines)
def_line = next(source_lines).strip()
if def_line.startswith('def ') and def_line.endswith(':'):
# Handle functions that are not one-liners
first_line = next(source_lines)
# Find the indentation of the first line
indentation = len(first_line) - len(first_line.lstrip())
return ''.join([first_line[indentation:]] + [line[indentation:] for line in source_lines])
else:
# Handle single line functions
return def_line.rsplit(':')[-1].strip()
Demo:
def some_decorator(x):
return x
@some_decorator
def foo():
print("bar")
def func():
def inner(a, b='a:b'):
print (100)
a = c + d
print ('woof!')
def inner_inner():
print (200)
print ('spam!')
return inner
def func_one_liner(): print (200); print (a, b, c)
print (get_function_body(foo))
print (get_function_body(func()))
print (get_function_body(func_one_liner))
func_one_liner = some_decorator(func_one_liner)
print (get_function_body(func_one_liner))
Output:
print("bar")
print (100)
a = c + d
print ('woof!')
def inner_inner():
print (200)
print ('spam!')
print (200); print (a, b, c)
print (200); print (a, b, c)
Update:
To handle async
and functions with multiline argument signature get_function_body
should be updated to:
import inspect
import re
from itertools import dropwhile
def get_function_body(func):
print()
print("{func.__name__}'s body:".format(func=func))
source_lines = inspect.getsourcelines(func)[0]
source_lines = dropwhile(lambda x: x.startswith('@'), source_lines)
source = ''.join(source_lines)
pattern = re.compile(r'(async\s+)?def\s+\w+\s*\(.*?\)\s*:\s*(.*)', flags=re.S)
lines = pattern.search(source).group(2).splitlines()
if len(lines) == 1:
return lines[0]
else:
indentation = len(lines[1]) - len(lines[1].lstrip())
return '\n'.join([lines[0]] + [line[indentation:] for line in lines[1:]])
Demo:
def some_decorator(x):
return x
@some_decorator
def foo():
print("bar")
def func():
def inner(a, b='a:b'):
print (100)
a = c + d
print ('woof!')
def inner_inner():
print (200)
print ('spam!')
return inner
def func_one_liner(): print (200); print (a, b, c)
async def async_func_one_liner(): print (200); print (a, b, c)
def multi_line_1(
a=10,
b=100): print (100); print (200)
def multi_line_2(
a=10,
b=100
): print (100); print (200)
def multi_line_3(
a=10,
b=100
):
print (100 + '\n')
print (200)
async def multi_line_4(
a=10,
b=100
):
print (100 + '\n')
print (200)
async def multi_line_5(
a=10,
b=100
): print (100); print (200)
def func_annotate(
a: 'x', b: 5 + 6, c: list
) -> max(2, 9): print (100); print (200)
print (get_function_body(foo))
print (get_function_body(func()))
print (get_function_body(func_one_liner))
print (get_function_body(async_func_one_liner))
func_one_liner = some_decorator(func_one_liner)
print (get_function_body(func_one_liner))
@some_decorator
@some_decorator
def foo():
print("bar")
print (get_function_body(foo))
print (get_function_body(multi_line_1))
print (get_function_body(multi_line_2))
print (get_function_body(multi_line_3))
print (get_function_body(multi_line_4))
print (get_function_body(multi_line_5))
print (get_function_body(func_annotate))
Output:
foo's body:
print("bar")
inner's body:
print (100)
a = c + d
print ('woof!')
def inner_inner():
print (200)
print ('spam!')
func_one_liner's body:
print (200); print (a, b, c)
async_func_one_liner's body:
print (200); print (a, b, c)
func_one_liner's body:
print (200); print (a, b, c)
foo's body:
print("bar")
multi_line_1's body:
print (100); print (200)
multi_line_2's body:
print (100); print (200)
multi_line_3's body:
print (100 + '\n')
print (200)
multi_line_4's body:
print (100 + '\n')
print (200)
multi_line_5's body:
print (100); print (200)
func_annotate's body:
print (100); print (200)
Using re
to handle def
and async def
:
def_regexp = r"^(\s*)(?:async\s+)?def foobar\s*?\:"
def get_func_code(func):
lines = inspect.getsourcelines(foo)[0]
for idx in range(len(lines)): # in py2.X, use range
def_match = re.match(line, def_regexp)
if def_match:
withespace_len = len(def_match.group(1)) # detect leading whitespace
return [sline[whitespace_len:] for sline in lines[idx+1:]]
Note that this will not handle single-line definitions. One would need to match opening and closing brackets after the def and contained colons (to avoid tuples and type hints.)
Original Version:
Just look for the first line containing a def
statement.
def get_func_code(func):
lines = inspect.getsourcelines(foo)[0]
for idx in range(len(lines)): # in py2.X, use range
if line.lstrip().startswith('def %s' % func.__name__) or\
line.lstrip().startswith('async def %s' % func.__name__): # actually should check for `r"^async\s+def\s+%s" % func.__name__` via re
withespace_len = len(line.split('def'), 1)[0] # detect leading whitespace
return [sline[whitespace_len:] for sline in lines[idx+1:]]
This should safely handle both tab and space indentation, even in mixed cases.
The two solutions in the accepted answer break if the definition takes more than one line and annotations are used (since annotations introduce extra ":"). In the following I take care of that case too (but not the async case contained in the accepted answer's second function.
import inspect
from itertools import dropwhile
def get_function_body(func):
source_lines = inspect.getsourcelines(func)[0]
source_lines = dropwhile(lambda x: x.startswith('@'), source_lines)
line = next(source_lines).strip()
if not line.startswith('def '):
return line.rsplit(':')[-1].strip()
elif not line.endswith(':'):
for line in source_lines:
line = line.strip()
if line.endswith(':'):
break
# Handle functions that are not one-liners
first_line = next(source_lines)
# Find the indentation of the first line
indentation = len(first_line) - len(first_line.lstrip())
return ''.join([first_line[indentation:]] + [line[indentation:] for line in source_lines])
For example, applied to:
# A pre comment
def f(a, b: str, c='hello',
d: float=0.0, *args, **kwargs) -> str:
"""The docs"""
return f"{c} {b}: {a + d}"
print(get_function_body(f))
I get
"""The docs"""
return f"{c} {b}: {a + d}"
I like the approach of @Daoctor, with a couple of improvements:
Here is the function (tested):
def get_function_body(func):
"""
Get the body of a function
"""
def indentation(s):
"Get the indentation (spaces of a line)"
return len(s) - len(s.lstrip())
source = inspect.getsourcelines(func)[0]
# print(source)
# get the indentation of the first line
line_0 = source[0]
ind_0 = indentation(line_0)
body = []
for line in source[1:]:
ind = indentation(line)
if ind > ind_0:
# append to the body (minus the extra indentation)
body.append(line[ind_0:])
return ''.join(body)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.