-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtype_hints.py
213 lines (155 loc) · 5.49 KB
/
type_hints.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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
from typing import Callable, reveal_type
from collections.abc import Iterable, Iterator
# ___ _
# |_ _|_ __ | |_ _ __ ___
# | || '_ \| __| '__/ _ \
# | || | | | |_| | | (_) |
# |___|_| |_|\__|_| \___/
# Type inference
# mypy will infer the correct type based on what is initially assigned to the
# variable.
i = 1
reveal_type(i)
my_list = [1, 2]
reveal_type(my_list)
# The "name: str" annotation says that the "name" argument should be a string
# The "-> str" annotation says that "greeting" will return a string
def greeting(name: str) -> str:
return "Hello " + name
# No error
greeting("World!")
greeting(3)
greeting(b"Alice")
# Unsupported operand types for * ("str" and "str")
def bad_greeting(name: str) -> str:
return "Hello " * name # type: ignore
# list[str] is a subtype of Iterable[str]
def greet_all(names: Iterable[str]) -> None:
for name in names:
print("Hello " + name)
names = ["Alice", "Bob", "Charlie"]
ages = [10, 20, 30]
greet_all(names) # Ok!
greet_all(ages) # Error due to incompatible types
def normalize_id(user_id: int | str) -> str:
# mypy understands basic isinstance checks and so can infer that the
# user_id variable was of type int in the if-branch and of type str in the
# else-branch.
if isinstance(user_id, int):
return f"user-{100_000 + user_id}"
else:
return user_id
def nums_below(numbers: Iterable[float], limit: float) -> list[float]:
output: list[float] = []
for num in numbers:
if num < limit:
output.append(num)
return output
# ____ _ _ ____ _ _
# / ___| |__ ___ __ _| |_ / ___|| |__ ___ ___| |_
# | | | '_ \ / _ \/ _` | __| \___ \| '_ \ / _ \/ _ \ __|
# | |___| | | | __/ (_| | |_ ___) | | | | __/ __/ |_
# \____|_| |_|\___|\__,_|\__| |____/|_| |_|\___|\___|\__|
# Variables (almost redundant)
age: int = 1
a: int
a = "1" # incompatible types
child: bool
if age < 18:
child = 1 # incompatible types
else:
child = False
# Built-in types
# Note that mypy can usually infer the type of a variable from its value,
# so technically these annotations are redundant
x1: int = 1
x2: float = 1.0
x3: bool = True
x4: str = "Hello"
x5: bytes = b"World"
# Collections
x6: list[int] = [1]
x7: set[int] = {1, 2, 3}
# Mappings
x8: dict[str, float] = {"field": 2.0}
x9: tuple[int, str, float] = (41, "42", 43) # fixed size tuple
# Unions and Optionals
x10: list[int | str] = [3, 5, "test", "fun", True]
x11: int | None = None
# Functions
def untyped(x):
x.anything() + 1 + "string" # no errors
def stringify(num: int) -> str:
return str(num)
_ = stringify(10)
def plus(num1: int, num2: int) -> int:
return num1 + num2
_ = plus(10, 20)
def show(value: str, excitement: int = 10) -> None:
print(value + "!" * excitement)
_ = show("Hello")
x: Callable[[str, int], float] = f
def register(callback: Callable[[str], int]) -> None: ...
def gen(n: int) -> Iterator[int]:
i = 0
while i < n:
yield i
i += 1
def send_email(
address: str | list[str],
sender: str,
cc: list[str] | None = None,
bcc: list[str] | None = None,
subject: str = "",
body: list[str] | None = None,
) -> None:
print(sender, cc, bcc, subject, body)
# def quux1(positional_or_keyword_parameters, *, keyword_only_parameters):
# pass
# def quux2(positional_only_parameters, /, positional_or_keyword_parameters,
# *, keyword_only_parameters):
def quux(x: int, /, *, y: int) -> None:
pass
quux(3, y=5) # Ok
quux(3, 5) # error: Too many positional arguments for "quux"
quux(x=3, y=5) # error: Unexpected keyword argument "x" for "quux"
def call(self, *args: str, **kwargs: str) -> str:
reveal_type(args) # Revealed type is "tuple[str, ...]"
reveal_type(kwargs) # Revealed type is "dict[str, str]"
request = make_request(*args, **kwargs)
return self.do_api_query(request)
# Classes
from typing import ClassVar
class BankAccount:
# The "__init__" method doesn't return anything, so it gets return
# type "None" just like any other method that doesn't return anything
def __init__(self, account_name: str, initial_balance: int = 0) -> None:
# mypy will infer the correct types for these instance variables
# based on the types of the parameters.
self.account_name = account_name
self.balance = initial_balance
# For instance methods, omit type for "self"
def deposit(self, amount: int) -> None:
self.balance += amount
def withdraw(self, amount: int) -> None:
self.balance -= amount
# User-defined classes are valid as types in annotations
account: BankAccount = BankAccount("Alice", 400)
def transfer(src: BankAccount, dst: BankAccount, amount: int) -> None:
src.withdraw(amount)
dst.deposit(amount)
# Functions that accept BankAccount also accept any subclass of BankAccount!
class AuditedBankAccount(BankAccount):
# You can optionally declare instance variables in the class body
audit_log: list[str]
def __init__(self, account_name: str, initial_balance: int = 0) -> None:
super().__init__(account_name, initial_balance)
self.audit_log: list[str] = []
def deposit(self, amount: int) -> None:
self.audit_log.append(f"Deposited {amount}")
self.balance += amount
def withdraw(self, amount: int) -> None:
self.audit_log.append(f"Withdrew {amount}")
self.balance -= amount
audited = AuditedBankAccount("Bob", 300)
transfer(audited, account, 100) # type checks!