简体   繁体   English

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

[英]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处理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:]]

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的方法,并进行了一些改进:

  1. I combined it with the following recipe to get the indentation of each line in a rather agnostic way. 我将其与以下食谱结合使用,以一种不可知的方式获得每行的缩进。
  2. We have to be careful that the functions can be nested and are therefore not always start at column 0 of the line. 我们必须注意,函数可以嵌套,因此并不总是从行的第0列开始。 We can work around that, because we can also determine the indentation of the first line of the function. 我们可以解决此问题,因为我们还可以确定函数第一行的缩进。
  3. Similarly, we can cut that extra-indentation for all lines of the body. 同样,我们可以为身体的所有线条切割该额外的压痕。

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.

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