Does Python Need Type Checking
oleksii.moskalenko@healthjoy.com
Reveng of the types
In the past the world was very clearly divided between interpreted languages with dynamic typing and ahead of time compiled languages with static typing. This is changing as new trends emerge.
I believe we're moving in a future with powerful type systems. I do not believe that this will be the end of dynamic typing but there appears to be a noticable trend of embracing powerful static typing with local type inference.
Armin Ronacher, author of werkzeug
Not All [Static] Type Systems are bad
If the only type systems I knew were C, C++, Java or Go I’d be Ruby Programmer.
Paul Snively, not a Ruby Programmer
Languages with good type system:
Rust, OCaml, Haskell?, Agda
Benefits of Type Checking
1. Static Analysis
def upgrade_customer(user):
assert isinstance(user, Customer)
user.hinted_method_name()
Benefits of Type Checking
2. Correctness
A type system is the most cost effective unit test you’ll ever have.
Peter Hallam, C# designer
Benefits of Type Checking
3. Documentation
from typing import NamedTuple, Tuple, Dict, List
Name = str
Duties = List[str]
Address = NamedTuple('Address', [('city', str),
('country', str),
('zip_code', str)])
Employee = Tuple[Name, Duties, Address]
EmployeeId = int
Employees = Dict[EmployeeId, Employee]
Competitor Review
Erlang: TypEr & Dialyzer
-record(mam_msg,
{key :: mam_msg_key(),
time :: erlang:timestamp(),
route :: route(),
from :: ljid(),
to :: ljid(),
stanza :: xmlel()}).
...
-spec receive_stanza(jid(), jid(), jid(), xmlel()) -> ok.
receive_stanza(#jid{luser = U, lserver = S} = JID, From, To,
#xmlel{name = <<"message">>} = Stanza) ->
...
Competitor Review
Clojure: core.typed
(ann summarise
(IFn [(U nil (NonEmptyColl Int)) -> Int]
[(U nil (NonEmptyColl Int)) Int -> Int]))
(defn summarise
([nseq] (summarise nseq 0))
([nseq acc] (if nseq
(* (summarise (rest nseq)
(inc acc))
(first nseq))
42)))
PEP 484
x = [] # type: List[Employee]
x, y, z = [], [], [] # type: List[int], List[int], List[str]
x, y, z = [], [], [] # type: (List[int], List[int], List[str])
x = [1, 2] # type: List[int]
with frobnicate() as foo: # type: int
# Here foo is an int
...
for x, y in points: # type: float, float
# Here x and y are floats
...
import http.client
errors = {
'not_found': http.client.NOT_FOUND # type: ignore
}
PEP 484
from typing import TypeVar, Iterable, Tuple, Callable
T = TypeVar('T', int, float, complex)
Vector = Iterable[Tuple[T, T]]
def inproduct(v: Vector) -> T:
return sum(x*y for x, y in v)
...
def async_query(on_success: Callable[[int], None],
on_error: Callable[[int, Exception], None]) -> None:
# Body
...
def first(l: Sequence[T]) -> T: # Generic function
return l[0]
Implementation: MyPy
def parse_statement(self) -> Tuple[Node, bool]:
stmt = Undefined # type: Node
t = self.current()
ts = self.current_str()
is_simple = True # Is this a non-block statement?
if ts == 'if':
stmt = self.parse_if_stmt()
is_simple = False
elif ts == 'def':
stmt = self.parse_function()
is_simple = False
elif ts == 'while':
stmt = self.parse_while_stmt()
is_simple = False
elif ts == 'return':
stmt = self.parse_return_stmt()
elif ts == 'for':
stmt = self.parse_for_stmt()
is_simple = False
elif ts == 'try':
stmt = self.parse_try_stmt()
is_simple = False
elif ts == 'break':
stmt = self.parse_break_stmt()
elif ts == 'continue':
stmt = self.parse_continue_stmt()
elif ts == 'pass':
stmt = self.parse_pass_stmt()
elif ts == 'raise':
stmt = self.parse_raise_stmt()
elif ts == 'import':
stmt = self.parse_import()
elif ts == 'from':
stmt = self.parse_import_from()
elif ts == 'class':
stmt = self.parse_class_def()
is_simple = False
elif ts == 'global':
stmt = self.parse_global_decl()
elif ts == 'nonlocal' and self.pyversion >= 3:
stmt = self.parse_nonlocal_decl()
elif ts == 'assert':
stmt = self.parse_assert_stmt()
elif ts == 'yield':
stmt = self.parse_yield_stmt()
elif ts == 'del':
stmt = self.parse_del_stmt()
elif ts == 'with':
stmt = self.parse_with_stmt()
is_simple = False
elif ts == '@':
stmt = self.parse_decorated_function_or_class()
is_simple = False
elif ts == 'print' and (self.pyversion == 2 and
'print_function' not in self.future_options):
stmt = self.parse_print_stmt()
else:
stmt = self.parse_expression_or_assignment()
MyPy: Cases
import asyncio
from asyncio import Future
@asyncio.coroutine
def compute(x: int, y: int) -> 'Future[int]':
print("Compute %s + %s ..." % (x, y))
yield from asyncio.sleep(0.1)
return x + y # Here the int is wrapped in Future[int]
@asyncio.coroutine
def print_sum(x: int, y: int) -> 'Future[None]':
result = 0 # type: int
result = yield from compute(x, y)
MyPy: Cases
from typing import Undefined
a, b, c = Undefined, Undefined, Undefined # type: (A, B, C)
c = a + c # Fail
a = a + b # Fail
c = b + a # Fail
c = a + b
class A:
def __add__(self, x: 'B') -> 'C':
pass
class B:
pass
class C:
pass
MyPy: Cases
from typing import TypeVar, Generic, Undefined, List
T = TypeVar('T')
class Container(Generic[T]):
def __init__(self) -> None:
pass
class A:
pass
class B(A):
pass
ca = Undefined # type: List[Container[A]]
cb = Undefined # type: List[Container[B]]
ab = Container() # type: Container[B]
ba = Container() # type: Container[A]
ca.append(ab)
cb.append(ab) # Fail
cb.append(ba) # Fail
MyPy: The Irony
- 2195 Test cases
- 3 Main Classes - 5k LOC
- Smells like Java
... I also think that this particular tool may be able to help Dropbox convert our own Python 2-based codebase to Python 3.
Guido Van R., Dropbox engineer
Possible Flaws
- Python 3
- C vs Python types
- No more ASAP driven development
Thank goodness we don't have only serious problems, but ridiculous ones as well.
Links
- http://lucumr.pocoo.org/2014/8/24/revenge-of-the-types/ - Armin Ronacher
- https://www.youtube.com/watch?v=SWTWkYbcWU0 - Type Systems - The Good, Bad and Ugly
- https://www.python.org/dev/peps/pep-0484/
Does Python Nees Type Checking
By Alexey Moskalenko
Does Python Nees Type Checking
- 2,355