Remove type hints in Python source programmatically

type hints python
python typing enum
python type hint docstring
python strip type hints
python 2 type hints
python type indicator
pep 8 type hints
python type specifiers

I have some source code written for Python 3.5 that I want to make executable under Python 3.4. The only feature from 3.5 that I use which is not available in 3.4 are type hints, so I'd like to write a script to remove those entirely.

This seemed easy enough at first glance and I decided to write some regexes to do this, but then I thought about a few edge cases and I wasn't sure how to solve the problem for a more complex function like this:

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

Basically, I'd have to parse the whole thing and rebuild the argument list without type annotations. How would I even approach this?


OK, I got it :D

Use Python's builtin ast module to parse the source code and then the excellent astunparse library to generate source code from the parsed ast again. Then all that's left is to remove the type annotations:

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))

The TypeHintRemover visits all Nodes in the AST and removes all type hints within function arguments, the return type definitions of each function and all import statements that refer to the 'typing' module.

The result is:

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

typing — Support for type hints, This module provides runtime support for type hints as specified by PEP 484, PEP 526 what one would normally do in idiomatic dynamically typed Python code. Deprecated since version 3.8, will be removed in version 3.9: Deprecated the  typing.get_type_hints (obj [, globals [, locals]]) ¶ Return a dictionary containing type hints for a function, method, module or class object. This is often the same as obj.__annotations__. In addition, forward references encoded as string literals are handled by evaluating them in globals and locals namespaces.


You can subclass lib2to3.refactor.RefactoringTool to refactor the code using a fixer that is a subclass of lib2to3.fixer_base.BaseFix with a pattern that looks for either a typed argument, a function declaration with an annotated returning value, or a simple statement that imports or imports from typing, and a transform method that removes the indices of the annotations from the child nodes or replaces the statement node with an empty node:

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, []

so that:

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, ''))

outputs:

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

Demo: https://repl.it/@blhsing/BurlywoodFeistyTrials

As a bonus, lib2to3 also preserves all comments and white spaces after the transformation. You can find the definition of the Python grammar in Grammar.txt of the lib2to3 module.

Type hints cheat sheet (Python 3), This is how you declare the type of a variable type in Python 3.6 age: int = 1 # In remove it again before running the code. reveal_type(1) # -> Revealed type is​  Back up. What are type hints? Here's a simple example of type hints in Python 3.5: def hello_there(name: str) -> str: return 'Hello ' + name Here, the type hints indicate that the name parameter of the hello_there() function is a string, and that the function returns a string.


There are also type hints for local variables (which came from Python 3.6). I've modified @klamann 's code to remove them too. Also, I use astor (https://pypi.org/project/astor/) to generate code.

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()

Type hints cheat sheet (Python 2), Technically many of the type annotations shown below are redundant, because remove it again before running the code. reveal_type(1) # -> Revealed type is​  15 Remove type hints in Python source programmatically Mar 11 '17 15 Remove type hints in Python source programmatically Mar 11 '17 8 Parallelize generators with asyncio Oct 17 '17


strip-hints · PyPI, This package provides a command-line command and a corresponding importable function that strips type hints from Python code files. The stripping process  Klamann. Apparently, this user prefers to keep an air of mystery about them. 15 Remove type hints in Python source programmatically; 15 Remove type hints in


the state of type hints in Python, First, let's see why do we need type hints in Python. that it can have any type (​that is it remains a dynamically typed section). When you run the linter (e.g. mypy) on a type hinted code you'll get errors if there are type miss-matches: Were you to allow them to remove it does break your type linter. Abstract. PEP 484 defines the type Dict[K, V] for uniform dictionaries, where each value has the same type, and arbitrary key values are supported. It doesn't properly support the common pattern where the type of a dictionary value depends on the string value of the key.


Guide: Type Hinting in Python 3.5, Since version 3.5, Python supports type hints: code annotations that, through Since Python remains a dynamically unchecked language, we can still because once a module adds type supports, we can remove it from a  sympy: the Python library for symbolic mathematics. Extra: symfont. Lib/fontTools/t1Lib.py. To get the file creator and type of Macintosh PostScript Type 1 fonts on Python 3 you need to install the following module, as the old MacOS module is no longer included in Mac Python: xattr: Python wrapper for extended filesystem attributes (macOS