-
Notifications
You must be signed in to change notification settings - Fork 4
/
tutorial.py
131 lines (100 loc) · 3.12 KB
/
tutorial.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# Examples from the workshop session, live coding mostly
# Definition with a type signature
flag : bool = True
# Simple function with its typing information on inputs and return
def plus(x : int, y : int) -> int:
return (x + y)
# Some functions return the 'None' value which has type 'None'
# (e.g. print)
def greet(name: str) -> None:
"""Say hello to everyone"""
print("Hi " + name)
greet("Summer School Participants")
# Another example with floats
def myAbs(x : float) -> float:
"""Take the absolute of the floating-point input"""
if x < 0:
return (-x)
else:
return x
# Example with list data structure which is _parameterised_
# by another type: the type of the elements
def greet_all_list(names : list[str]) -> None:
for name in names:
greet(name)
# We can generalise this with the 'super class' of `Iterable`
# objects
from typing import Iterable
def greet_all(names : Iterable[str]) -> None:
for name in names:
greet(name)
greet_all(["Alice", "Baiba", "Cornelius"])
greet_all({"hi":42,"test":55})
greet_all(("A", "B", "C", "D"))
def myDiv(x : float, y : float) -> (float | None):
if y != 0: return x / y
else: return None
myDict : dict[str, float | str] = {"temp" : 273.0, "units": "Kelvin"}
# Classes are used as at type names
class Complex:
def __init__(self, realpart, imagpart):
self.r = realpart
self.i = imagpart
h : Complex = Complex(3.0, -4.5)
# Querying the types
from typing import TYPE_CHECKING
if TYPE_CHECKING:
reveal_type(len)
# A simple example working with parameteric types
def first_str(xs : list[str]) -> str:
return xs[0]
# If we want to do the same thing but on lists of integers
# we might think of doing:
def first_int(xs : list[int]) -> int:
return xs[0]
# But this leads to code duplication...
# We could use 'Any' which types anything
from typing import Any
def first_any(xs : list[Any]) -> Any:
return xs[0]
# But this does not provide much information: the following
# has the same type but does something very different
def notfirst(xs : list[Any]) -> Any:
return 422934809234
# *Instead* we can use parametric polymorphism
# creating a _type variable_ that quantifies over all types
from typing import TypeVar
T = TypeVar("T")
def first(xs : list[T]) -> T:
return xs[0]
# or in python 3.10 and below:
# def first(xs : list[type(T)]) -> type(T):
# Now we can reuse the function at different types as normal:
example0 = first([1,2,3,4])
example1 = first(["hi","hola"])
#reveal_type(example1)
# Example of the Callable interface
from typing import Callable
S = TypeVar('S')
def memo(f : Callable[[S], T], x : S) -> tuple[S,T]:
return (x, f(x))
# What about functions that return multiple outputs?
# It's really just as tuple:
def copy(x : T) -> tuple[T,T]:
return x, x
a, b = copy(42)
# Escape hatch
# borked = 0 / "hello" # type: ignore
# Keyword args can be given types
def flibble(x : int = 0, y : str = "") -> str:
return str(x) + y
flibble(y = "hi", x = 42)
# Variable args have to be homogeneously typed
def many(*args : int) -> int:
y = 0
for arg in args:
y = y + arg
return y
many(10)
many(10,20,40)
# many(10,"true") # ill-typed