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

问题描述 投票:0回答:4

我有一些为 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]:

基本上,我必须解析整个内容并重建没有类型注释的参数列表。我该如何处理这个问题?

python-3.x type-hinting
4个回答
27
投票

好的,我明白了: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 中的所有节点,并删除函数参数中的所有类型提示、每个函数的返回类型定义以及引用“打字”模块的所有导入语句。

结果是:

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

6
投票

还有局部变量的类型提示(来自 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()

3
投票

您可以对

lib2to3.refactor.RefactoringTool
进行子类化,以使用作为
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, {'print_function': True})

    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]:
    print(line, end="")    # comments and white spaces are preserved
"""
print(Refactor([FixParameterAnnotations]).refactor_string(source, ''))

输出:

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

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

作为奖励,

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


0
投票

有一个 CLI 工具可以执行此操作,称为 strip-hints

strip-hints --inplace --to-empty my_file.py

请注意,需要使用像

black
这样的格式化程序进行另一遍才能使最终结果看起来不错。

© www.soinside.com 2019 - 2024. All rights reserved.