Type hinting in Python 2

Question:

In PEP 484, type hinting was added to Python 3 with the inclusion of the typing module. Is there any way to do this in Python 2? All I can think of is having a decorator to add to methods to check types, but this would fail at runtime and not be caught earlier like the hinting would allow.

Asked By: Andrew

||

Answers:

According to Suggested syntax for Python 2.7 and straddling code in PEP 484 which defined type hinting, there is an alternative syntax for compatibility with Python 2.7. It is however not mandatory so I don’t know how well supported it is, but quoting the PEP:

Some tools may want to support type annotations in code that must be compatible with Python 2.7. For this purpose this PEP has a suggested (but not mandatory) extension where function annotations are placed in a # type: comment. Such a comment must be placed immediately following the function header (before the docstring). An example: the following Python 3 code:

def embezzle(self, account: str, funds: int = 1000000, *fake_receipts: str) -> None:
    """Embezzle funds from account using fake receipts."""
    <code goes here>

is equivalent to the following:

def embezzle(self, account, funds=1000000, *fake_receipts):
    # type: (str, int, *str) -> None
    """Embezzle funds from account using fake receipts."""
    <code goes here>

For mypy support, see Type checking Python 2 code.

Answered By: Mijamo

At this point the recommended and python3 compatible way to do is to follow the python2 to 3 guide : http://python-future.org/func_annotations.html

def embezzle(self, account: str, funds: int = 1000000, *fake_receipts: str) -> None:
    """Embezzle funds from account using fake receipts."""
    pass

Become:

def embezzle(self, account, funds = 1000000, *fake_receipts):
    """Embezzle funds from account using fake receipts."""
    pass
embezzle.__annotations__ = {'account': str, 'funds': int, 'fake_receipts': str, 'return': None}
Answered By: encolpe

Here is a function i wrote to parse the Python 2 type comment and get a tuple of input types and the return type. It would need some work to work with complex type definitions from the typing library (Any, Optional, List, etc.):

class InvalidTypeHint(Exception):
    pass    

PYTHON_2_TYPE_HINT_REGEX = "s*#s*type:s*((.+))s*->s*(.+)s*"

def parse_python_2_type_hint(typehint_string):
    # type: (str) -> (tuple, type)
    pattern = re.compile(PYTHON_2_TYPE_HINT_REGEX)
    search_results = pattern.search(typehint_string)
    if not search_results:
        raise InvalidTypeHint('%s does not match type hint spec regex %s' % (typehint_string, PYTHON_2_TYPE_HINT_REGEX))
    arg_types_str = search_results.group(1)
    return_type_str = search_results.group(2)
    try:
        arg_types_tuple = eval(arg_types_str)
        assert isinstance(arg_types_tuple, tuple)
        return_type = eval(return_type_str)
        assert isinstance(return_type, type)
    except Exception as e:
        raise InvalidTypeHint(e)
    return arg_types_tuple, return_type


def parse_arg_types_for_callable(func):
    # type:(callable)->tuple
    """

    :param func:
    :return: list of parameter types if successfully parsed, else None
    """

    # todo make this compatible with python 3 type hints
    # python 2.7 type hint
    source_lines = inspect.getsource(func).split("n")
    def_statements = 0
    for source_line in source_lines:
        try:
            arg_types_tuple, return_type = parse_python_2_type_hint(source_line)
            return arg_types_tuple
        except InvalidTypeHint:
            if source_line.strip().startswith("def "):
                def_statements += 1
            if def_statements > 1:
                return None
Answered By: Thomas Belluscio