给定对函数的引用,生成它的源代码而不在签名中注释

Given a reference to function, produce it's source code without annotations in the signature

应保留默认值。

装饰器也应该被删除,但这不是什么大问题。

原始出处:

# Comments
@decorator1
@decorator2(
    a=1,
    b=1,
)
def my_func(
    a: typing.List = 14,
    b: 'CustomType' = None,
    c: Whatever('foo') = 42,
    d: Whatever('foo') = some_function,
) -> typing.NamedTuple(
    'Dummy',
    [
      ('r1': 'CustomType'),
      ('21': 'CustomType2'),
    ]
):
    ...

所需来源:

def my_func(
    a = 14,
    b = None,
    c = 42,
    d = some_function,
):
    ...

我可以使用 inspect.getsource(my_func) 获取源代码,但我需要删除注释。 我怎样才能做到这一点?最好使用标准模块。

可以使用ast.parse将source解析成AST,如果是arg节点则使用ast.walk遍历树使annotation无效化returnsdecorator_list 如果它是 FunctionDef 节点。使用 astunparse 将树解析回源代码:

import inspect
import ast
import typing
import astunparse
from unittest.mock import patch

@patch('builtins.print')
def my_func(
        a: int = 1,
        b: typing.List = []
) -> bool:
    pass

tree = ast.parse(inspect.getsource(my_func), '', 'exec')
for node in ast.walk(tree):
    if isinstance(node, ast.arg):
        node.annotation = None
    elif isinstance(node, ast.FunctionDef):
        node.returns = None
        node.decorator_list = []
print(astunparse.unparse(tree))

这输出:

def my_func(a=1, b=[]):
    pass

演示:https://repl.it/repls/WaterloggedFunnyQuotient

您可以子类化 lib2to3.refactor.RefactoringTool 以使用作为 lib2to3.fixer_base.BaseFix 子类的修复器重构代码,其模式查找类型化参数、带注释返回值的函数声明、或装饰定义,以及从子节点中删除注释和装饰器索引的 transform 方法:

from lib2to3 import fixer_base, refactor

class FixParameterAnnotations(fixer_base.BaseFix):
    PATTERN = "name=tname | func=funcdef< any+ '->' any+ > | decorated"

    def transform(self, node, results):
        if 'name' in results:
            del node.children[1:] # delete annotation to typed argument
        elif 'func' in results:
            del node.children[-4:-2] # delete annotation to function declaration
        else:
            del node.children[0] # delete decorators
        return node

class Refactor(refactor.RefactoringTool):
    def __init__(self, fixers):
        self._fixers= [cls(None, None) for cls in fixers]
        super().__init__(None)

    def get_fixers(self):
        return self._fixers, []

这样:

import inspect
import typing
from unittest.mock import patch

@patch('builtins.print')
def my_func(
        a: int = 1, # some comment
        b: typing.List = []     # more comment
) -> bool:
    ''' some docstring'''
    pass

print(Refactor([FixParameterAnnotations]).refactor_string(inspect.getsource(my_func), ''))

输出:

def my_func(
        a = 1, # some comment
        b = []     # more comment
):
    ''' some docstring'''
    pass

演示:https://repl.it/@blhsing/BrightWhirlwindBoolean

lib2to3 是双向稳定的,因此在转换后保留所有注释和空格。您可以在 lib2to3 模块的 Grammar.txt 中找到 Python 语法的定义。