获取没有定义行的Python函数源代码

问题描述 投票:0回答:6

使用

inspect.getsourcelines
函数,我已经能够获得如下 Python 函数的源代码:

import inspect    

def some_decorator(x):
    return x

@some_decorator
def foo():
    print("bar")

print(inspect.getsourcelines(foo)[0])

此代码将正确地将函数的源代码行输出为列表:

['@some_decorator\n', 'def foo():\n', '    print("bar")\n']

但是,我只想要函数内部的代码,而不是整个函数声明。所以我只想要这个输出(还要注意正确的缩进): ['print("bar")\n']

我尝试使用切片和
strip

来删除前两行,然后删除缩进,但这不适用于许多函数,我必须相信有更好的方法。


inspect

模块或我可以

pip install
的其他模块是否具有此功能?
    

python function
6个回答
5
投票
并且使用注释(因为注释引入了额外的“:”)。在下面,我也处理这种情况(但不处理已接受答案的第二个函数中包含的异步情况。

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])

例如应用于:

# 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))

我明白了

"""The docs""" return f"{c} {b}: {a + d}"

你可以发现你想要的代码前面都是空白的,所以你可以
试试这个

3
投票
print filter(lambda x:x.startswith(' '), inspect.getsourcelines(foo)[0])

你可以这样做:

2
投票
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()

演示:

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))

输出:

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)

更新:

要处理 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:]])

演示:

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))

输出:

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)

使用 

1
投票
处理

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:]]

请注意,这将
不会
处理单行定义。需要在 def 和包含的冒号之后匹配左括号和右括号(以避免元组和类型提示。)

原版:


只需查找包含

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:]]

即使在混合情况下,这也应该安全地处理制表符和空格缩进。
    

我喜欢@Daoctor 的方法,有一些改进:

0
投票

我将它与

以下食谱
    结合起来,以一种相当不可知的方式获得每行的缩进。
  1. 我们必须小心,函数可以嵌套,因此并不总是从该行的第 0 列开始。我们可以解决这个问题,因为我们还可以确定函数第一行的缩进。
  2. 同样,我们可以为身体的所有线条剪掉额外的凹痕。
  3. 这是函数(已测试):
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)

自 Python 3.8 起,

0
投票
模块中的所有 AST 节点在它们已有的

end_lineno

 属性之上都获得了 
lineno
 属性,因此您可以简单地将函数定义解析为 AST 并将行限制为函数体第一条语句的起始行号和最后一条语句的结束行号。要处理单行函数,请检查第一个语句是否位于第一行,然后从语句的列偏移量中分割该行:
import ast
import inspect

def get_function_body(func):
    lines = inspect.getsourcelines(func)[0]
    func_def = ast.parse(''.join(lines)).body[0].body
    lines = lines[(first := func_def[0]).lineno - 1: func_def[-1].end_lineno]
    if first.lineno == 1:
        lines[0] = lines[0][first.col_offset:]
    return ''.join(lines)

这样:
def some_decorator(x):
    return x

@some_decorator
def foo():
    print("bar")
    print("baz")

def bar(): print(''
                 '')

print(get_function_body(foo))
print(get_function_body(bar))

输出:
    print("bar")
    print("baz")

print(''
                 '')

演示
这里

© www.soinside.com 2019 - 2024. All rights reserved.