簡體   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