我有一些为 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]:
基本上,我必须解析整个内容并重建没有类型注释的参数列表。我该如何处理这个问题?
好的,我明白了: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
还有局部变量的类型提示(来自 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()
您可以对
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 语法的定义。
有一个 CLI 工具可以执行此操作,称为 strip-hints
strip-hints --inplace --to-empty my_file.py
请注意,需要使用像
black
这样的格式化程序进行另一遍才能使最终结果看起来不错。