Type Hinting in Python 2

Type hinting in Python 2

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.

How to specify multiple return types using type-hints

From the documentation - Union Type:

A union object holds the value of the | (bitwise or) operation on multiple type objects. These types are intended primarily for type annotations. The union type expression enables cleaner type hinting syntax compared to typing.Union.

This use of | was added in Python 3.10. Hence the proper way to represent more than one return data type is:

def foo(client_id: str) -> list | bool:

For earlier versions, use typing.Union:

from typing import Union

def foo(client_id: str) -> Union[list, bool]:

But do note that typing is not enforced. Python continues to remain a dynamically-typed language. The annotation syntax has been developed to help during the development of the code prior to being released into production. As PEP 484 states, "no type checking happens at runtime."

>>> def foo(a: str) -> list:
... return "Works"
...
>>> foo(1)
'Works'

As you can see I am passing an int value and returning a str. However the __annotations__ will be set to the respective values.

>>> foo.__annotations__ 
{'return': <class 'list'>, 'a': <class 'str'>}

Please go through PEP 483 for more about Type hints. Also see What are type hints in Python 3.5??

Kindly note that this is available only for Python 3.5 and upwards. This is mentioned clearly in PEP 484.

Python 2.7 type hinting callable types in PyCharm

The correct way to document a callable within Pycharm (or within any other tool that understands PEP 484 type hints) is like so:

from typing import Callable

def set_function(self, function):
# type: (Callable[[int], None]) -> None
...

Since you're using Python 2, you'll need to install the typing module from PyPi, if you haven't already. (typing was added to Python's standard library in 3.5, the module on PyPi is a backport).

You can find more information on using the typing module in Python's documentation, and within the documentation for mypy.

(If you're not aware, mypy is a command line tool that also understands PEP 484 type hints and will also statically analyze and typecheck your code. It is an independent effort from Pycharm's built-in type checker. Since both Pycharm and mypy use PEP 484 types, mypy's documentation is often a good place to start looking to learn more about using type hints.)

Type hints in PyCharm for Python 2.7 (named tuple)

Try:

def foo(args):
"""
:type args: CreateVhostDoArgs
"""

In the settings there are different docstring formats which might be of use to you. Settings | Tools | Python Integrated Tools The default which I tested with is reStructredText. The other ones might behave a little differently (or not at all)

In this SO post I try to expand a-lot on type hinting in PyCharm if you want some more details.

I did this a-lot a number of years ago when python first introduced type hinting and pycharm quickly rolled out an update supporting it, but it did not support everything that the PEP outlines, and I think I only found reStructredText to work, but things might have changed since then. When I was doing it back then I remember it being really annoying with maintenance of class names changing and that not getting propagated to the docstrings properly. If that type isn't actually used in the file (not imported) it just won't give an error or warning and I guess it just thinks you are a dumb user writing stuff in there incorrectly. Then you need to do an import just for something in the comments, which it doesn't seem like PyCharm is complaining these days about that, but if you have other linters they might. There are probably other pitfalls to watch out for too, so tread cautiously.

It definitely works, but I don't recommend it to anyone in python 3.5+ (when python introduced type hinting) for reasons detailed in the other post.

I did test locally specifically with namedtuple and it worked.

Is there a way to use type hinting on back-compatible code in Python?

To use type hints in a fully backwards-compatible way, you will need to...

  1. Always use the comment-based syntax: Python 2 does not support function annotations; Python 3.0-3.5 do not support variable annotations.
  2. Install the backport of the typing module when using Python 2.7 and 3.0 - 3.4. The typing module was added to the standard library in Python 3.5 and must be pip-installed for earlier versions of Python.

One additional complication is that the typing module was updated several times with new types since it was added to the standard library in Python 3.5.0 -- types like ClassVar, Deque, Protocol, Text, Type, to name just a few.

If you want to use those types and still support Python 3.5 and 3.6, additionally install the typing_extensions module. You can find a full list of backported types on the github repo.

Basically, if you want to use any of the types listed in the github repo linked above, and support Python 3.5.0 - 3.6.x, always import them from typing_extensions instead of from typing.


Some additional details and caveats that you may or may not care about:

  1. About typing_extensions:

    If you plan on using typing_extensions, also pay careful attention if you need to support Python 3.5.0 - 3.5.2. The typing module went through several, often substantial, internal changes since it was first released in Python 3.5.0.

    The typing_extension module tries to bridge between these different internal APIs in a sane way, but there's always the chance that something was overlooked. The latest minor versions of Python 3.5 and Python 3.6 are much more up-to-date though, and so are far less likely to have issues.

    (You might also be able to get away without using typing_extensions if you want to support only the latest minor versions of Python 3.5 and 3.6: several of the types that were missing in Python 3.5.0 and Python 3.6.0 were added later on. But it's honestly hard to keep track of what was added when, so it might be safer to just default to using typing_extensions and not worry about it.)

  2. About mypy:

    If want to use mypy, keep in mind that mypy can be run using only non EOL'd versions of Python 3. So, at time of writing, Python 3.4+.

    However, mypy itself can be used to analyze Python 2.7+ code.

  3. About typeshed and Python 3.0 - 3.2:

    Mypy, and most other PEP 484-compliant type checking tools, rely on typeshed, a collection of type annotations for the standard library and popular 3rd party libraries.

    Typeshed keeps track of when functions and classes were added to the standard library. That way, you can ask tools like mypy to make sure your code works with specific versions of Python and that you didn't accidentally import anything from the future.

    However, typeshed only keeps track of this info for Python 2.7 and 3.3+. So, you need to be careful if you're targeting specifically Python 3.0 - 3.2.

  4. About unicode_literals and mypy/typeshed:

    Some people recommend using unicode_literals as a technique to help with Python 2/3 compatibility.

    However, I believe using unicode_literals causes a number of issues with either typeshed or mypy. I forget the exact details, but the upshot is that you're probably better off not using it (at least for the time being).

    Instead, avoid unicode-related issues by using the type system to your advantage. Specifically:

    • Use typing.Text when something MUST be unicode. This type is aliased to unicode in Python 2 and str in Python 3.
    • Use bytes (or maybe bytearray?) when something MUST be bytes. Be sure to keep in mind that the bytes behaves slightly differently between Python 2 and 3.
    • Use str when that a value should be whatever str means for that particular version of Python.

    If you need to write a function that needs to work with multiple kinds of strings, you can do so with careful use of Union or AnyStr.

Typing for python2.7?

As you can read in the documentation:

https://github.com/python/typing

This GitHub repo is used for development of the typing module defined
by PEP 484. The module is available in Python since version 3.5.0 on a
provisional basis until Python 3.7.0.

So, no - you cannot use it with 2.7, sadly.

Using typing module in Python 2.7

typing is a module that was introduced in Python 3.5 . The examples in PEP 484 rely on a Python 3+, and __annotations__ is a Python 3 concept. The backport can only allow to use the types of functions defined in the typing module, but it does not change the Python engine to magically understand all Python 3 concepts.

A discussion in that other SO post suggests that the annotations should be accessible by using inspect.getsourcelines to research the first line right after the function declaration and starting with # type. A typed-ast module exists on pypi and should be able to parse Python 2.7 style annotations. Unfortunately it is only declared at beta level and only compatible with Python 3.



Related Topics



Leave a reply



Submit