53

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.

Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
Andrew
  • 6,295
  • 11
  • 56
  • 95

3 Answers3

80

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.

Community
  • 1
  • 1
Mijamo
  • 3,436
  • 1
  • 21
  • 21
  • 1
    Hmm ok, should have read the docs further. Do you know if there any tools that currently support this? Not sure if it is just a standard or if there is actual tooling implemented yet. – Andrew Feb 05 '16 at 18:15
  • According to PyCharm "PyCharm supports type hinting in function annotations and type comments using the typing module defined by PEP 484." The reference to TYPE COMMENTS makes it clear that it should be supported. I don't use pyCharm on this computer so I cannot check it right now. EDIT : link to pycharm: https://www.jetbrains.com/pycharm/help/type-hinting-in-pycharm.html#d316912e110 – Mijamo Feb 05 '16 at 18:23
  • Thanks, also found that Emacs Jedi works with it as well. – Andrew Feb 05 '16 at 18:29
  • 1
    A bit off-topic: perhaps you know how could I extract this type annotation in Python 2? It's not included in `__doc__` attr and `__annotations__` attr is not available in Python 2. – Czarek Tomczak Mar 04 '16 at 11:25
  • I am not aware of any automatic tool to do that, but given the PEP they should always be in the first line right after the function declaration and start with # type: so that should be quite easy to get with inspect.getsourcelines (https://docs.python.org/2/library/inspect.html#inspect.getsourcelines), and then parsed. – Mijamo Mar 04 '16 at 13:31
  • @CzarekTomczak Currently, there's a module available that parses Python with type comments (and it seems to support Python 2.7) called [typed-ast](https://pypi.python.org/pypi/typed-ast/). I'm using it only for Python 3.5 though. – mbdevpl Nov 09 '16 at 12:50
  • @mbdevpl Looks like this package can parse Python 2.7 type comments, but it requires Python 3 to run it. Nevertheless useful, thanks. – Czarek Tomczak Nov 10 '16 at 12:50
  • @Andrew: Are you saying that you got PEP-0484 type annotation working in Emacs Jedi with Python *2* ? Doesn't work here. It's advertized as supported for Python *3* only AFAIU (see: "[function annotations (python 3 only; python 2 function annotations with comments in planned but not yet implemented)](https://github.com/davidhalter/jedi/blob/be2a97cd36ce1b7d6f587ef122efc3433b1e8ae2/docs/docs/features.rst#type-hinting)") – Apteryx May 25 '17 at 23:58
  • 1
    Will this work on earlier versions of Python 3 as well? – Rob Rose May 08 '18 at 15:30
  • Thanks. It works in VS Code (1.63.1). – ElpieKay Dec 20 '21 at 08:23
17

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}
encolpe
  • 311
  • 2
  • 3
3

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