Skip to content

Commit 284e082

Browse files
committed
Adds a new type annotation chapter
1 parent 69763fe commit 284e082

File tree

2 files changed

+277
-0
lines changed

2 files changed

+277
-0
lines changed

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Contents:
3232
pep8
3333
igd
3434
virtualenv
35+
typehinting
3536
testing
3637
projectstructure
3738
click

docs/typehinting.rst

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
==============================
2+
Type hinting and annotations
3+
==============================
4+
5+
This is one of the new feature of the language. We can do the similar kind of
6+
work in Python2 also, but with different syntax. Please remember that Python
7+
will stay as a dynamically typed language, this type hinting does not effect
8+
your code anyway.
9+
10+
The major benefit of having type hints in your codebase is about future
11+
maintenance of the codebase. When a new developer will try to contribute to
12+
your project, having type hints will save a lot of time for that new person.
13+
It can also help to detect some of the runtime issues we see due to passing
14+
of wrong variable types in different function calls.
15+
16+
First example of type annotation
17+
==================================
18+
19+
Let us start with a simple example, adding of two integers.
20+
21+
::
22+
23+
def add(a, b):
24+
return a + b
25+
26+
Now, the above example will work for any object which supports *+* operator.
27+
But, we want to specify that it is expecting only Integers as parameters, and
28+
the function call will return another Integer.
29+
30+
::
31+
32+
def add(a: int, b: int) -> int:
33+
return a + b
34+
35+
You can see that the return type of the function call is defined after *->*.
36+
We can do the same in Python2 using a comment (before any docstring).
37+
::
38+
39+
def add(a, b):
40+
# type: (int, int) -> int
41+
return a + b
42+
43+
44+
Using mypy and more examples
45+
=============================
46+
47+
`Mypy <https://mypy.rtfd.io>`_ is a static type checker written for Python. If we use the type
48+
annotations as explained above, mypy can help by finding common problems in
49+
our code. You can use mypy in various ways in your development workflow, may
50+
be in CI as a proper test.
51+
52+
Installing mypy
53+
---------------
54+
55+
We can install mypy inside of a virtual environment.
56+
57+
::
58+
59+
$ python3 -m venv env
60+
$ source env/bin/activate
61+
(env) $ pip install mypy
62+
Collecting mypy
63+
Downloading mypy-0.511-py3-none-any.whl (1.0MB)
64+
100% |################################| 1.0MB 965kB/s
65+
Collecting typed-ast<1.1.0,>=1.0.3 (from mypy)
66+
Downloading typed_ast-1.0.3-cp36-cp36m-macosx_10_11_x86_64.whl (214kB)
67+
100% |################################| 215kB 682kB/s
68+
Installing collected packages: typed-ast, mypy
69+
Successfully installed mypy-0.511 typed-ast-1.0.3
70+
71+
72+
Our example code
73+
-----------------
74+
75+
We wil working on the following example code. This does not do much useful things, but we can use this to learn about type annotations and mypy.
76+
77+
::
78+
79+
class Student:
80+
81+
def __init__(self, name, batch, branch, roll):
82+
self.name = name
83+
self.batch = batch
84+
self.branch = branch
85+
self.roll = roll
86+
self.semester = None
87+
self.papers = {}
88+
89+
def is_passed(self):
90+
"To find if the student has pass the exam in the current semester"
91+
for k, v in self.papers.items():
92+
if v < 34:
93+
return False
94+
95+
return True
96+
97+
98+
def total_score(self):
99+
"Returns the total score of the student"
100+
total = 0
101+
for k, v in self.papers.items():
102+
total += v
103+
104+
return total
105+
106+
107+
std1 = Student("Kushal", 2005, "cse", "123")
108+
std2 = Student("Sayan", 2005, "cse", 121)
109+
std3 = Student("Anwesha", 2005, "law", 122)
110+
111+
std1.papers = {"english": 78, "math": 82, "science": 77}
112+
std2.papers = {"english": 80, "math": 92, "science": "78"}
113+
std3.papers = {"english": 82, "math": 87, "science": 77}
114+
115+
for std in [std1, std2, std3]:
116+
print("Passed: {0}. The toral score of {1} is {2}".format(std.is_passed(), std.name, std.total_score()))
117+
118+
119+
You may find some errors in the code, but in case of a large codebase we can not detet the similar issues unless we see the runtime errors.
120+
121+
Using mypy
122+
-----------
123+
124+
We can just call mypy on our source file, I named it as *students2.py*
125+
126+
::
127+
128+
$ mypy studets2.py
129+
130+
Enabling the first few type annotations
131+
----------------------------------------
132+
133+
We will add some type annotations to the *__init__* method. For reducing the
134+
code length, I am only showing the changed code below.
135+
136+
::
137+
138+
class Student:
139+
140+
def __init__(self, name: str, batch: int, branch: str, roll: int) -> None:
141+
self.name = name
142+
self.batch = batch
143+
self.branch = branch
144+
self.roll = roll
145+
self.semester = None
146+
self.papers = {}
147+
148+
149+
::
150+
151+
$ mypy students2.py
152+
students2.py:11: error: Need type annotation for variable
153+
students2.py:31: error: Argument 4 to "Student" has incompatible type "str"; expected "int"
154+
155+
You can see mypy is complaing about variable which does not have type
156+
annotations, and also found that in line 31, as argument 4 we are passing
157+
*str*, where as we were supposed to send in an Integer for the rull number.
158+
Let us fix these.
159+
160+
::
161+
162+
from typing import Dict
163+
164+
class Student:
165+
166+
def __init__(self, name: str, batch: int, branch: str, roll: int) -> None:
167+
self.name = name
168+
self.batch = batch
169+
self.branch = branch
170+
self.roll = roll
171+
self.semester: str = None
172+
self.papers: Dict[str, int] = {}
173+
174+
def is_passed(self) -> bool:
175+
"To find if the student has pass the exam in the current semester"
176+
for k, v in self.papers.items():
177+
if v < 34:
178+
return False
179+
180+
return True
181+
182+
183+
def total_score(self) -> int:
184+
"Returns the total score of the student"
185+
total = 0
186+
for k, v in self.papers.items():
187+
total += v
188+
189+
return total
190+
191+
192+
std1: Student = Student("Kushal", 2005, "cse", 123)
193+
std2: Student = Student("Sayan", 2005, "cse", 121)
194+
std3: Student = Student("Anwesha", 2005, "law", 122)
195+
196+
std1.papers = {"english": 78, "math": 82, "science": 77}
197+
std2: Student.papers = {"english": 80, "math": 92, "science": 78}
198+
std3.papers = {"english": 82, "math": 87, "science": 77}
199+
200+
for std in [std1, std2, std3]:
201+
print("Passed: {0}. The toral score of {1} is {2}".format(std.is_passed(), std.name, std.total_score()))
202+
203+
::
204+
205+
$ mypy students2.py
206+
207+
Now, it does not complain about any error. You can see that in line 1, we
208+
imported Dict from the typing module. And, then using the same we added the
209+
type annotation of the *self.paper* variable. We are saying that it is a
210+
dictionary which has string keys, and Integers as values. We also used our
211+
*Student* class as type of std1, std2, and std3 variables.
212+
213+
Now let us say we by mistake assign a new list to the papers variable.
214+
215+
::
216+
217+
std1.papers = ["English", "Math"]
218+
219+
220+
Or maybe assigned a wrong kind of dictionary.
221+
222+
::
223+
224+
std2.papers = {1: "Engish", 2: "Math"}
225+
226+
We can see what mypy says in these cases
227+
228+
::
229+
230+
$ mypy students2.py
231+
students2.py:35: error: Incompatible types in assignment (expression has type List[str], variable has type Dict[str, int])
232+
students2.py:36: error: Dict entry 0 has incompatible type "int": "str"
233+
students2.py:36: error: Dict entry 1 has incompatible type "int": "str"
234+
235+
236+
More examples of type annotations
237+
==================================
238+
239+
::
240+
241+
from typing import List, Tuple, Sequence, Optional
242+
243+
values: List[int] = []
244+
city: int = 350 # The city code, not a name
245+
246+
247+
# This function returns a Tuple of two values, a str and an int
248+
def get_details() -> Tuple[str, int]:
249+
return "Python", 5
250+
251+
# The following is an example of Tuple unpacking
252+
name: str
253+
marks: int
254+
name, marks = get_details()
255+
256+
257+
def print_all(values: Sequence) -> None:
258+
for v in values:
259+
print(v)
260+
261+
262+
print_all([1,2,3])
263+
print_all({"name": "kushal", "class": 5})
264+
# alltypes.py:23: error: Argument 1 to "print_all" has incompatible type Dict[str, object]; expected Sequence[Any]
265+
# But running the code will give us no error with wrong output
266+
267+
def add_ten(number: Optional[int] = None) -> int:
268+
if number:
269+
return number + 10
270+
else:
271+
return 42
272+
273+
print(add_ten())
274+
print(add_ten(12))
275+
276+
You can learn more about types from `PEP 484 <https://www.python.org/dev/peps/pep-0484/>`_.

0 commit comments

Comments
 (0)