Удалить программные подсказки типа Python

У меня есть исходный код, написанный для Python 3.5, который я хочу сделать исполняемым в Python 3.4. Единственная функция из 3.5, которую я использую, которая недоступна в 3.4, - это подсказки типа, поэтому я хотел бы написать script, чтобы полностью удалить их.

На первый взгляд это казалось достаточно простым, и я решил написать некоторые регулярные выражения, чтобы сделать это, но потом я подумал о нескольких случаях с краями, и я не был уверен, как решить проблему для более сложной функции:/p >

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

В принципе, мне придется разбирать все это и перестраивать список аргументов без аннотаций типа. Как бы я даже приблизился к этому?

Ответ 1

ОК, я понял: 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

Ответ 2

Вы можете создать подкласс 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)

    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 также сохраняет все комментарии и пробелы после преобразования. Вы можете найти определение грамматики Python в Grammar.txt модуля lib2to3.