Types my way: Static typing in Python
Joe Cabrera
Dynamically typing
The Python interpreter checks types as your code runs and the type of variable
can change during it’s lifetime
>>> thing = "hello"
>>> type(thing)
<class 'str'>
>>> thing = 29.0
>>> type(thing)
<class 'float'>
Static Typing
Static typing checks run without even running the program, usually when the code
is compiled (C++ and Java)
String thing;
thing = "Hello";
Duck Typing
“If it walks like a duck and it quacks like a duck, this it must be a duck”
You do not check types at all, instead you check if an object has a method or
attribute
>>> class Malort:
... def __str__(self):
... return "Jeppson's Malört"
... def __format__(self, format):
... if(format == 'where'):
... return "only in chicago"
... return "None"
...
>>> print(format(Malort(), "where"))
only in chicago
def get_caching_header(args=None):
"""
Get cache header dictionary.
:param args: Query arguments for used to build surrogate key
:type args: dict
:return: Cache header dictionary
"""
def get_caching_header(args=None):
"""
Get cache header dictionary.
"""
assert type(args) is dict
Optional Type Checking
- Compile-time type checking
- Easier to find bugs
- Less debugging
- Easier maintenance
- Machine-checked documentation
- Easier to understand code
- Grow from dynamic to static typing
- You can add static typing existing codebases after your code has matured
- You can gradually add type hints with Any
Why should you start type checking now?
● Great for complex and confusing code
● Good for open-source code
● Before migrating or refactoring code
PEP 484: Type Hints
Python will remain a dynamically typed language, and the authors have no desire
to ever make type hints mandatory, even by convention.
Type hints have no runtime effect
>>> def shot(location: str) -> str:
... if location == "new york":
... return "hennessy"
... elif location == "chicago":
... return "evan williams"
... else:
... return "jack daniels"
Function annotations
Functions can have annotation arguments and return value
>>> def beer(location: str, with_shot: bool = True) -> str:
...
>>> beer.__annotations__
{'location': <class 'str'>, 'with_shot': <class 'bool'>}
Variable annotations
Available since Python 3.6
>>> speed_of_sound: float = 343.0
Type & Variable Comments
Annotations are great, but have not been backported to Python 2.x
Type comments can be used in any version of Python
>>> def beer(location, with_shot):
>>> # type: (str, bool) -> str
>>> ...
>>> speed_of_sound = 343.0 # type: float
Sequences & Mappings
Great we can do primitives but what about composite types. Also not an issue…
>>> beers: list = ["Coors Light", "Budweiser", "Corona"]
>>> shots: tuple = ("B-52", "Irish Car Bomb", "Lemon Drop")
>>> beer_shot: dict = {"Coors Light": "Jack Daniels", "Corona": "Tequila"}
Typing module
Composite types are great, but what about the individual values
>>> from typing import Dict, List, Tuple
>>> beers: List[str] = ["Coors Light", "Budweiser", "Corona"]
>>> shots: Tuple[str, str, str] = ("B-52", "Irish Car Bomb", "Lemon
Drop")
>>> beer_shot: Dict[str, str] = {"Coors Light": "Jack Daniels",
>>> ... "Corona": "Tequila"}
Sequences
Many times you expect some kind of sequence but do not care if it’s a list or a
tuple
>>> from typing import List, Sequence
>>>
>>> def take_shot(liquors: Sequence[str]) -> str:
>>> return random.choice(liquors)
Type Aliases
Instead of
Write this
>>> from typing import List, Tuple
>>>
>>> def take_shots(beer_shot: List[Tuple[str, str]]) -> Tuple[
>>> List[Tuple[str, str]],
>>> List[Tuple[str, str]],
>>> List[Tuple[str, str]],
>>> ]:
>>> return (beer_shot[0::3], beer_shot[1::3], beer_shot[2::3])
>>> from typing import List, Tuple
>>> Boilermaker = Tuple[str, str]
Functions without return values
>>> def shot(location: str) -> None:
... if location == "new york":
... print("hennessy")
... elif location == "chicago":
... print("evan williams")
... else:
... print("jack daniels")
>>> from typing import NoReturn
>>>
>>> def the_darkness() -> NoReturn:
raise Exception("It's too late")
The magically Any type
>>> from typing import Any, Sequence
>>>
>>> def take_shot(liquors: Sequence[Any]) -> Any:
>>> return random.choice(liquors)
The Optional Type
For functions that have a default value for an argument
>>> from typing import Sequence, Optional
>>>
>>> def beer_order(names: Sequence[str],
>>> ... start: Optional[str] = None) -> Sequence[str]:
The Union Type
When you need arguments of several types in a composite
>>> from typing import Union
>>>
>>> def beer_order(nashville_beer: Union[str, int]) -> Sequence[str]:
Annonating *args and **kwargs
>>> class Bar
>>> def __init__(self, beers: List[Beer],
>>> *customer: str,
>>> **drink_types: str) -> None:
>>> self.beers = beers
Callables
>>> from typing import Callable
>>>
>>> def take_shot(func: Callable[[str], str], argument: str) -> None:
>>> print(func(argument))
>>>
>>> def create_response(liquor: str) -> str:
>>> return f"A shot of {liquor}"
>>>
>>> take_shot(create_response, "Jack Daniels")
Protocols
>>> class Bar:
>>> def meth(self) -> int:
>>> return 0
>>>
>>> def func(x: Proto) -> int:
>>> return x.meth()
Classes as Types
>>> class Bar
>>> def __init__(self, beers: List[Beer]) -> None:
>>> self.beers = beers
>>>
>>> @classmethod
>>> def open(cls, restock: bool = False) -> "Bar":
return cls(beers)
Coming in Python 4.0
>>> from __future__ import annotations
>>>
>>> class Bar
>>>
>>> @classmethod
>>> def open(cls, restock: bool = False) -> Bar:
>>> return cls(beers)
Type-checkers
Static
● MyPy (Dropbox)
● PyType (Google)
● Pyre (Facebook)
● Pyright (Microsoft)
● PyCharm
Dynamic
● Enforce, Typeguard, Typo
Thanks!
@greedoshotlast

Types my way: Static Typing in Python

  • 1.
    Types my way:Static typing in Python Joe Cabrera
  • 2.
    Dynamically typing The Pythoninterpreter checks types as your code runs and the type of variable can change during it’s lifetime >>> thing = "hello" >>> type(thing) <class 'str'> >>> thing = 29.0 >>> type(thing) <class 'float'>
  • 3.
    Static Typing Static typingchecks run without even running the program, usually when the code is compiled (C++ and Java) String thing; thing = "Hello";
  • 4.
    Duck Typing “If itwalks like a duck and it quacks like a duck, this it must be a duck” You do not check types at all, instead you check if an object has a method or attribute >>> class Malort: ... def __str__(self): ... return "Jeppson's Malört" ... def __format__(self, format): ... if(format == 'where'): ... return "only in chicago" ... return "None" ... >>> print(format(Malort(), "where")) only in chicago
  • 5.
    def get_caching_header(args=None): """ Get cacheheader dictionary. :param args: Query arguments for used to build surrogate key :type args: dict :return: Cache header dictionary """
  • 6.
    def get_caching_header(args=None): """ Get cacheheader dictionary. """ assert type(args) is dict
  • 7.
    Optional Type Checking -Compile-time type checking - Easier to find bugs - Less debugging - Easier maintenance - Machine-checked documentation - Easier to understand code - Grow from dynamic to static typing - You can add static typing existing codebases after your code has matured - You can gradually add type hints with Any
  • 8.
    Why should youstart type checking now? ● Great for complex and confusing code ● Good for open-source code ● Before migrating or refactoring code
  • 9.
    PEP 484: TypeHints Python will remain a dynamically typed language, and the authors have no desire to ever make type hints mandatory, even by convention. Type hints have no runtime effect >>> def shot(location: str) -> str: ... if location == "new york": ... return "hennessy" ... elif location == "chicago": ... return "evan williams" ... else: ... return "jack daniels"
  • 10.
    Function annotations Functions canhave annotation arguments and return value >>> def beer(location: str, with_shot: bool = True) -> str: ... >>> beer.__annotations__ {'location': <class 'str'>, 'with_shot': <class 'bool'>}
  • 11.
    Variable annotations Available sincePython 3.6 >>> speed_of_sound: float = 343.0
  • 12.
    Type & VariableComments Annotations are great, but have not been backported to Python 2.x Type comments can be used in any version of Python >>> def beer(location, with_shot): >>> # type: (str, bool) -> str >>> ... >>> speed_of_sound = 343.0 # type: float
  • 13.
    Sequences & Mappings Greatwe can do primitives but what about composite types. Also not an issue… >>> beers: list = ["Coors Light", "Budweiser", "Corona"] >>> shots: tuple = ("B-52", "Irish Car Bomb", "Lemon Drop") >>> beer_shot: dict = {"Coors Light": "Jack Daniels", "Corona": "Tequila"}
  • 14.
    Typing module Composite typesare great, but what about the individual values >>> from typing import Dict, List, Tuple >>> beers: List[str] = ["Coors Light", "Budweiser", "Corona"] >>> shots: Tuple[str, str, str] = ("B-52", "Irish Car Bomb", "Lemon Drop") >>> beer_shot: Dict[str, str] = {"Coors Light": "Jack Daniels", >>> ... "Corona": "Tequila"}
  • 15.
    Sequences Many times youexpect some kind of sequence but do not care if it’s a list or a tuple >>> from typing import List, Sequence >>> >>> def take_shot(liquors: Sequence[str]) -> str: >>> return random.choice(liquors)
  • 16.
    Type Aliases Instead of Writethis >>> from typing import List, Tuple >>> >>> def take_shots(beer_shot: List[Tuple[str, str]]) -> Tuple[ >>> List[Tuple[str, str]], >>> List[Tuple[str, str]], >>> List[Tuple[str, str]], >>> ]: >>> return (beer_shot[0::3], beer_shot[1::3], beer_shot[2::3]) >>> from typing import List, Tuple >>> Boilermaker = Tuple[str, str]
  • 17.
    Functions without returnvalues >>> def shot(location: str) -> None: ... if location == "new york": ... print("hennessy") ... elif location == "chicago": ... print("evan williams") ... else: ... print("jack daniels") >>> from typing import NoReturn >>> >>> def the_darkness() -> NoReturn: raise Exception("It's too late")
  • 18.
    The magically Anytype >>> from typing import Any, Sequence >>> >>> def take_shot(liquors: Sequence[Any]) -> Any: >>> return random.choice(liquors)
  • 19.
    The Optional Type Forfunctions that have a default value for an argument >>> from typing import Sequence, Optional >>> >>> def beer_order(names: Sequence[str], >>> ... start: Optional[str] = None) -> Sequence[str]:
  • 20.
    The Union Type Whenyou need arguments of several types in a composite >>> from typing import Union >>> >>> def beer_order(nashville_beer: Union[str, int]) -> Sequence[str]:
  • 21.
    Annonating *args and**kwargs >>> class Bar >>> def __init__(self, beers: List[Beer], >>> *customer: str, >>> **drink_types: str) -> None: >>> self.beers = beers
  • 22.
    Callables >>> from typingimport Callable >>> >>> def take_shot(func: Callable[[str], str], argument: str) -> None: >>> print(func(argument)) >>> >>> def create_response(liquor: str) -> str: >>> return f"A shot of {liquor}" >>> >>> take_shot(create_response, "Jack Daniels")
  • 23.
    Protocols >>> class Bar: >>>def meth(self) -> int: >>> return 0 >>> >>> def func(x: Proto) -> int: >>> return x.meth()
  • 24.
    Classes as Types >>>class Bar >>> def __init__(self, beers: List[Beer]) -> None: >>> self.beers = beers >>> >>> @classmethod >>> def open(cls, restock: bool = False) -> "Bar": return cls(beers)
  • 25.
    Coming in Python4.0 >>> from __future__ import annotations >>> >>> class Bar >>> >>> @classmethod >>> def open(cls, restock: bool = False) -> Bar: >>> return cls(beers)
  • 26.
    Type-checkers Static ● MyPy (Dropbox) ●PyType (Google) ● Pyre (Facebook) ● Pyright (Microsoft) ● PyCharm Dynamic ● Enforce, Typeguard, Typo
  • 27.