[英]Getting a Python function's source code without the definition lines
Using the inspect.getsourcelines
function, I have been able to get a Python function's source code like this: 使用
inspect.getsourcelines
函数,我已经能够获得Python函数的源代码,如下所示:
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. 我曾尝试使用切片和
strip
来删除前两行,然后删除缩进,但这不适用于许多功能,我必须相信有更好的方法。
Does the inspect
module, or another module which I can pip install
, have this functionality? inspect
模块或我可以pip install
其他模块是否具有此功能?
您会发现所有想要的代码之前都为空,因此您可以尝试一下
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: 要使用多行参数签名处理
async
和函数, get_function_body
更新为:
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
: 使用
re
处理def
和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.)
一个将需要在def和包含冒号后匹配左括号和右括号(以避免元组和类型提示)。
Original Version: 原始版本:
Just look for the first line containing a def
statement. 只需查找包含
def
语句的第一行。
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: 我喜欢@Daoctor的方法,并进行了一些改进:
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)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.