我有一些为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]:
基本上,我必须解析整个事情并重建参数列表而不使用类型注释。我怎么会接近这个?
答案 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语法的定义。