Last active
September 23, 2019 16:42
-
-
Save benrudolph/aff808744ed03d830f431151f6c1e6fa to your computer and use it in GitHub Desktop.
Python Types
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Resources | |
# - https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html | |
# - https://mypy.readthedocs.io/en/latest/introduction.html | |
# | |
# Typing None is useful because mypy default to dynamic typing and a return value of Any when the return value is left off. Therefore, calling something like `1 + hello()` where hello returns `None` will result in an error: | |
# | |
# demo.py:5: error: "hello" does not return a value | |
def hello() -> None: | |
print('Hello') | |
# Default arguments | |
def hello(name: str = 'Will') -> None: | |
print(f'Hello {name}') | |
# The typing module provides a bunch more useful compound types. | |
from typing import List | |
def hello_all(names: List[str]) -> None: | |
for name in names: | |
hello(name) | |
# The example of List above is can be too restrictive and we want a higher level type to allow for more types. | |
from typing import Iterable | |
def hello_all(names: Iterable[str]) -> None: | |
for name in names: | |
hello(name) | |
# Using Optional | |
# It's often better to use Optional than to use a type like `Union` instead (Union[str, None]). | |
from typing import Optional | |
def hello_stranger(name: Optional[str] = None) -> None: | |
if name is None: | |
name = 'stranger' | |
hello(name) | |
# Union | |
# Sometimes you need to combine different types together. | |
from typing import Union | |
def hello_there(name: Union[str, int]) -> None: | |
print('hello {name}') | |
# Type guarding | |
# Type guarding kind of sucks in mypy compared to something like TS. Right now the only supported typeguard (I know of) is isinstance | |
def hello_number(name: Union[str, int]) -> None: | |
if isinstance(name, int): | |
for i in range(name): | |
print(f'hello {i}') | |
else: | |
print(f'hi string {name}') | |
# TYPE_CHECKING | |
# This is very useful in django to avoid import cycles | |
from typing import TYPE_CHECKING | |
if TYPE_CHECKING: | |
from apps.api import models | |
def print_entity_name(entity: 'models.Entity): | |
print(entity.name) | |
# Defining type aliases | |
# Type aliases are easily defined by just assigning a type to variable | |
StrIntFloat = Union[str, int, float] | |
# TypedDict | |
from mypy_extensions import TypedDict | |
Movie = TypedDict('Movie', {'name': str, 'year': int}) | |
movie = {'name': 'Blade Runner', 'year': 1982} # type: Movie | |
# Total=False | |
# Allows you to have optional keys (no way to specify for certain keys) | |
Movie = TypedDict('Movie', {'name': str, 'year': int}, total=False) | |
movie = {'name': 'Blade Runner' } # OK: because total=False | |
# Proposal: Types get defined in `apps/<module>/types.py` | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Python classes | |
# Resources | |
# - https://mypy.readthedocs.io/en/latest/class_basics.html | |
# Basic inference | |
class A: | |
def __init__(self, x: int) -> None: | |
self.x = x # Aha, attribute 'x' of type 'int' | |
a = A(1) | |
a.x = 2 # OK! | |
a.y = 3 # Error: 'A' has no attribute 'y' | |
# ClassVar | |
from typing import ClassVar | |
class A: | |
x: ClassVar[int] = 0 # Class variable only | |
A.x += 1 # OK | |
a = A() | |
a.x = 1 # Error: Cannot assign to class variable "x" via instance | |
print(a.x) # OK -- can be read through an instance | |
# Overrides | |
class Base: | |
def f(self, x: int) -> None: | |
... | |
class Derived1(Base): | |
def f(self, x: str) -> None: # Error: type of 'x' incompatible | |
... | |
class Derived2(Base): | |
def f(self, x: int, y: int) -> None: # Error: too many arguments | |
... | |
class Derived3(Base): | |
def f(self, x: int) -> None: # OK | |
... | |
class Derived4(Base): | |
def f(self, x: float) -> None: # OK: mypy treats int as a subtype of float | |
... | |
class Derived5(Base): | |
def f(self, x: int, y: int = 0) -> None: # OK: accepts more than the base | |
... # class method | |
# Real abstract classes! | |
from abc import ABCMeta, abstractmethod | |
class Animal(metaclass=ABCMeta): | |
@abstractmethod | |
def eat(self, food: str) -> None: pass | |
@property | |
@abstractmethod | |
def can_walk(self) -> bool: pass | |
class Cat(Animal): | |
def eat(self, food: str) -> None: | |
... # Body omitted | |
@property | |
def can_walk(self) -> bool: | |
return True | |
x = Animal() # Error: 'Animal' is abstract due to 'eat' and 'can_walk' | |
y = Cat() # OK |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment