以编程方式删除Python源中的类型提示

时间:2017-03-11 10:07:02

标签: python-3.x type-hinting

我有一些为Python 3.5编写的源代码,我想在Python 3.4下创建可执行文件。我使用的3.5中唯一没有在3.4中提供的功能是类型提示,所以我想写一个脚本来完全删除它们。

这看起来很容易乍一看我决定写一些正则表达式来做这个,但后来我想到了一些边缘情况,我不知道如何解决这个问题更复杂的功能,比如这个:

def foo(bar: Dict[T, List[T]],
        baz: Callable[[T], int] = lambda x: (x+3)/7,
        **kwargs) -> List[T]:

基本上,我必须解析整个事情并重建参数列表而不使用类型注释。我怎么会接近这个?

3 个答案:

答案 0 :(得分:13)

好的,我明白了:D

使用Python的内置ast模块解析源代码,然后使用优秀的astunparse库来再次从解析的ast中生成源代码。然后剩下的就是删除类型注释:

import ast
import astunparse

source="""
import typing
from typing import Dict, T, Callable
from typing import List

def foo(bar: Dict[T, List[T]],
        baz: Callable[[T], int] = lambda x: (x+3)/7,
        **kwargs) -> List[T]:
    pass
"""

class TypeHintRemover(ast.NodeTransformer):

    def visit_FunctionDef(self, node):
        # remove the return type defintion
        node.returns = None
        # remove all argument annotations
        if node.args.args:
            for arg in node.args.args:
                arg.annotation = None
        return node

    def visit_Import(self, node):
        node.names = [n for n in node.names if n.name != 'typing']
        return node if node.names else None

    def visit_ImportFrom(self, node):
        return node if node.module != 'typing' else None

# parse the source code into an AST
parsed_source = ast.parse(source)
# remove all type annotations, function return type definitions
# and import statements from 'typing'
transformed = TypeHintRemover().visit(parsed_source)
# convert the AST back to source code
print(astunparse.unparse(transformed))

TypeHintRemover访问AST中的所有节点,并删除函数参数中的所有类型提示,每个函数的返回类型定义以及引用“键入”模块的所有import语句。

结果是:

def foo(bar, baz=(lambda x: ((x + 3) / 7)), **kwargs):
    pass

答案 1 :(得分:3)

还有一些局部变量的类型提示(来自Python 3.6)。我已经修改了@klamann的代码,也将其删除。另外,我使用astor(https://pypi.org/project/astor/)生成代码。

import ast
import astor
import sys


class TypeHintRemover(ast.NodeTransformer):

    def visit_FunctionDef(self, node):
        # remove the return type definition
        node.returns = None
        # remove all argument annotations
        if node.args.args:
            for arg in node.args.args:
                arg.annotation = None
        self.generic_visit(node)
        return node

    def visit_AnnAssign(self, node):
        if node.value is None:
            return None
        return ast.Assign([node.target], node.value)

    def visit_Import(self, node):
        node.names = [n for n in node.names if n.name != 'typing']
        return node if node.names else None

    def visit_ImportFrom(self, node):
        return node if node.module != 'typing' else None

def remove_type_hints(source: str):
    # parse the source code into an AST
    parsed_source = ast.parse(source)
    # remove all type annotations, function return type definitions
    # and import statements from 'typing'
    transformed = TypeHintRemover().visit(parsed_source)
    # convert the AST back to source code
    return astor.to_source(transformed)


def main():
    _, source_name, dest_name = sys.argv
    with open(source_name, "r") as sourceFile:
        source = "\n".join(sourceFile.readlines())
        dest = remove_type_hints(source)
        with open(dest_name, "w") as destFile:
            destFile.write(dest)

if __name__ == "__main__":
    main()

答案 2 :(得分:1)

您可以使用lib2to3.refactor.RefactoringTool的子类,即fixer来子集lib2to3.fixer_base.BaseFix的子类,以使用一种模式来查找类型化的参数,带有返回值的带注释的函数声明或从typing导入或导入的简单语句,以及从子节点删除注释索引或用空节点替换语句节点的transform方法:

from lib2to3 import fixer_base, refactor, fixer_util

class FixParameterAnnotations(fixer_base.BaseFix):
    PATTERN = r'''
        name=tname
        |
        func=funcdef< any+ '->' any+ >
        |
        simple_stmt<
            (
                import_name< 'import' 'typing' >
                |
                import_from< 'from' 'typing' 'import' any+ >
            ) '\n'
        >
    '''

    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:
            return fixer_util.BlankLine() # delete statement that imports typing
        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, []

这样:

source = """
import typing
from typing import Dict, T, Callable
from typing import List

def foo(bar: Dict[T, List[T]],
        baz: Callable[[T], int] = lambda x: (x+3)/7,
        **kwargs) -> List[T]:
    pass    # comments and white spaces are preserved
"""
print(Refactor([FixParameterAnnotations]).refactor_string(source, ''))

输出:

def foo(bar,
        baz = lambda x: (x+3)/7,
        **kwargs):
    pass    # comments and white spaces are preserved

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

作为奖励,lib2to3还保留了转换后的所有注释和空白。您可以在lib2to3模块的Grammar.txt中找到Python语法的定义。