Skip to content

Instantly share code, notes, and snippets.

@benrudolph
Last active September 23, 2019 16:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save benrudolph/aff808744ed03d830f431151f6c1e6fa to your computer and use it in GitHub Desktop.
Save benrudolph/aff808744ed03d830f431151f6c1e6fa to your computer and use it in GitHub Desktop.
Python Types
# 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`
# 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