Type Hinting in Python 3.5

This is supposed to be a concise tutorial and you should not spend longer than 10 minutes to go through it.

Why Type Hinting?

If you have experienced with statically-typed languages e.g. Java, you may probably flavor a feature that can spot errors before code execution. Also, you may probably want to provide type hint information in your code without looking inside the code. Python's PEP 484 brings optional type hinting to Python 3.5. Using typing module, you can provide 'hint' information to your code.

Quick Example

Let me use an example from Python 3.5's typing module as follows.

def greeting(name):
    return 'Hello, {}'.format(name)

It's pretty clear that the above code does not provide any information about it e.g. what types the function return? Now, we will add a little information into the code for describing that 'name' is a kind of string and 'greeting' returns a string.

def greeting(name: str) -> str:
    return 'Hello, {}'.format(name)

With this hint, you will be provided with more information by reading a method's signature (cf. the bold fonts). For some good IDEs, you can fetch the hint information at auto-completion or can be notified by the IDEs if errors might occur. The following figure shows that PyCharm can help us to spot errors by using the hint information.

More about Hint Types

List of somethings

from typing import List
 
def greeting(names: List[str]) -> str:
    return 'Hello, {}'.format(', '.join(names))
 
greeting(['jane', 'john', 'judy'])

Here, we provide hints to the method by saying that (1) its argument must be a list of string and (2) it will return a string. PEP 484 defines that List hint is generic i.e. a hint that can contain other hints.

Dict of somethings

from typing import Dict
 
# Let's pass in a dictionary
def greeting(names: Dict[str, int]) -> str:
    keys = names.keys()
    return 'Hello, {}'.format(', '.join(keys))
 
greeting({'jane': 10, 'john': 11, 'judy': 12})

Here, 'Dict[str, int]' hints that 'names ' must receive a dict, in which the fist argument indicates the string key and the second argument indicates the int value.

Multiple types

from typing import Union
 
class Greeter:
    def __init__(self, fmt: Union[str, bool]):
        if fmt:
            self.fmt = fmt
        else:
            self.fmt = 'Hi there, {}'
 
    def greet(self, name: str) -> str:
        return self.fmt.format(name)
 
greeting = Greeter(False)
print(greeting.greet('jane'))

The type 'Union' indicates that its type can be either string or boolean.

Optional Type

from typing import Optional
 
class Greeter:
    def __init__(self, fmt: Optional[str] = None):
        if fmt:
            self.fmt = fmt
        else:
            self.fmt = 'Hi there, {}'
 
    def greet(self, name: str) -> str:
        return self.fmt.format(name)
 
greeting = Greeter()
print(greeting.greet('jane'))

The type 'Optional' indicates an optional value.

Forward Reference for Class Type

Python does not allow references to a class object before the class is defined. To deal with this, we provide a hint as a string as follows:

class ToDo(Base):
    __tablename__ = 'todo'
    id = Column(Integer, primary_key=True)
    title = Column(Text)
 
    @classmethod
    def list(cls) -> List['ToDo']:
        return session.query(cls).order_by(cls.title)

This lets the Python interpreter parse the module and is able to handle the type hinting about a container class.

Type Aliases

Sometimes a function's arguments and returned types are big (see the following example).

from typing import Union, List, Dict
 
def greeting(names: Union[List[str], Dict[int, List[str]]]) -> Union[
        List[str], Dict[int, List[str]]]:
    fmt = 'Hello, {}'
    if isinstance(names, dict):
        return [(k, fmt.format(', '.join(v))) for k, v in
                names.items()]
    else:
        return fmt.format(', '.join(names))
 
print(greeting(['jane', 'john', 'judy']))
print(greeting(
    {10: ['jane', 'judy'],
     11: ['john'],
     12: ['judy', 'john']
     }
))

To deal with this, we can create an alias in one place and let other places refer to it as follows.

from typing import Union, List, Dict
 
GreetingType = Union[List[str], Dict[int, List[str]]]
 
def greeting(names: GreetingType) -> GreetingType:
    fmt = 'Hello, {}'
    if isinstance(names, dict):
        return [(k, fmt.format(', '.join(v))) for k, v in
                names.items()]
    else:
        return fmt.format(', '.join(names))
 
print(greeting(['jane', 'john', 'judy']))
print(greeting(
    {10: ['jane', 'judy'],
     11: ['john'],
     12: ['judy', 'john']
     }
))

Some Remarks

  1. It is not static typing. The language does not change anything (e.g. to Java, etc.)
  2. It does not speed up compilation time.
  3. It is not required. Python interpreter itself ignores type hinting at run time.
  4. It helps users of your code and can be considered as another way to documenting into the language.

References

This tutorial is summarized and is revised from:

For more information about Type Hinting, you should also take a look at:

Other blogged posts >> Tutorials