繁体   English   中英

在没有定义行的情况下获取Python函数的源代码

[英]Getting a Python function's source code without the definition lines

使用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其他模块是否具有此功能?

您会发现所有想要的代码之前都为空,因此您可以尝试一下

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

您可以执行以下操作:

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)

使用re处理defasync 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:]]

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

如果定义占用多行并且使用了注释,则接受的答案中的两种解决方案都会中断(因为注释会引入额外的“:”)。 在下面的内容中,我也会注意这种情况(但是接受的答案的第二个函数中不包含异步情况)。

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}"

我喜欢@Daoctor的方法,并进行了一些改进:

  1. 我将其与以下食谱结合使用,以一种不可知的方式获得每行的缩进。
  2. 我们必须注意,函数可以嵌套,因此并不总是从行的第0列开始。 我们可以解决此问题,因为我们还可以确定函数第一行的缩进。
  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)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM