how to make a person with a class
The factory and say_hello functions use three of the same inputs
first_namelast_nameyear_of_birth
I want to give those values once, and get a representation for a person. I can do that with a class.
I think of classes as attributes (variables) and methods (functions) that belong together (a classification).
preview
I have these tests by the end of the chapter
1import src.person
2
3
4def test_joe():
5 first_name = 'joe'
6 last_name = 'blow'
7 sex = 'M'
8 year_of_birth = 1996
9
10 reality = src.person.factory(
11 first_name=first_name,
12 last_name=last_name,
13 sex=sex,
14 year_of_birth=year_of_birth,
15 )
16 my_expectation = (
17 f'{first_name}, {last_name},'
18 f' {sex}, {year_of_birth}'
19 )
20 assert reality == my_expectation
21
22 reality = src.person.say_hello(
23 first_name=first_name,
24 last_name=last_name,
25 year_of_birth=year_of_birth,
26 )
27 my_expectation = (
28 f'Hello, my name is {first_name}'
29 f' {last_name} and I am'
30 f' {2026-year_of_birth}.'
31 )
32 assert reality == my_expectation
33
34 joe = src.person.Person(
35 first_name=first_name,
36 last_name=last_name,
37 sex=sex,
38 year_of_birth=year_of_birth,
39 )
40
41 reality = joe.say_hello()
42 assert reality == my_expectation
43
44
45def test_jane():
46 first_name = 'jane'
47 last_name = 'doe'
48 sex = 'F'
49 year_of_birth = 1991
50
51 reality = src.person.factory(
52 first_name=first_name,
53 last_name=last_name,
54 sex=sex,
55 year_of_birth=year_of_birth,
56 )
57 my_expectation = (
58 f'{first_name}, {last_name},'
59 f' {sex}, {year_of_birth}'
60 )
61 assert reality == my_expectation
62
63 reality = src.person.say_hello(
64 first_name=first_name,
65 last_name=last_name,
66 year_of_birth=year_of_birth,
67 )
68 my_expectation = (
69 f'Hello, my name is {first_name}'
70 f' {last_name} and I am'
71 f' {2026-year_of_birth}.'
72 )
73 assert reality == my_expectation
74
75 jane = src.person.Person(
76 first_name=first_name,
77 last_name=last_name,
78 sex=sex,
79 year_of_birth=year_of_birth,
80 )
81
82 reality = jane.say_hello()
83 assert reality == my_expectation
84
85
86def test_john():
87 first_name = 'john'
88 last_name = 'smith'
89 sex = 'M'
90 year_of_birth = 1580
91
92 reality = src.person.factory(
93 first_name=first_name,
94 last_name=last_name,
95 sex=sex,
96 year_of_birth=year_of_birth,
97 )
98 my_expectation = (
99 f'{first_name}, {last_name},'
100 f' {sex}, {year_of_birth}'
101 )
102 assert reality == my_expectation
103
104 reality = src.person.say_hello(
105 first_name=first_name,
106 last_name=last_name,
107 year_of_birth=year_of_birth,
108 )
109 my_expectation = (
110 f'Hello, my name is {first_name}'
111 f' {last_name} and I am'
112 f' {2026-year_of_birth}.'
113 )
114 assert reality == my_expectation
115
116 john = src.person.Person(
117 first_name=first_name,
118 last_name=last_name,
119 sex=sex,
120 year_of_birth=year_of_birth,
121 )
122
123 reality = john.say_hello()
124 assert reality == my_expectation
125
126
127def test_mary():
128 first_name = 'mary'
129 last_name = 'public'
130 sex = 'F'
131 year_of_birth = 2000
132
133 reality = src.person.factory(
134 first_name=first_name,
135 last_name=last_name,
136 sex=sex,
137 year_of_birth=year_of_birth,
138 )
139 my_expectation = (
140 f'{first_name}, {last_name},'
141 f' {sex}, {year_of_birth}'
142 )
143 assert reality == my_expectation
144
145 reality = src.person.say_hello(
146 first_name=first_name,
147 last_name=last_name,
148 year_of_birth=year_of_birth,
149 )
150 my_expectation = (
151 f'Hello, my name is {first_name}'
152 f' {last_name} and I am'
153 f' {2026-year_of_birth}.'
154 )
155 assert reality == my_expectation
156
157 mary = src.person.Person(
158 first_name=first_name,
159 last_name=last_name,
160 sex=sex,
161 year_of_birth=year_of_birth,
162 )
163
164 reality = mary.say_hello()
165 assert reality == my_expectation
166
167
168def test_dir_person_class():
169 reality = dir(src.person.Person)
170 my_expectation = [
171 '__class__',
172 '__delattr__',
173 '__dict__',
174 '__dir__',
175 '__doc__',
176 '__eq__',
177 '__firstlineno__',
178 '__format__',
179 '__ge__',
180 '__getattribute__',
181 '__getstate__',
182 '__gt__',
183 '__hash__',
184 '__init__',
185 '__init_subclass__',
186 '__le__',
187 '__lt__',
188 '__module__',
189 '__ne__',
190 '__new__',
191 '__reduce__',
192 '__reduce_ex__',
193 '__repr__',
194 '__setattr__',
195 '__sizeof__',
196 '__static_attributes__',
197 '__str__',
198 '__subclasshook__',
199 '__weakref__',
200 'say_hello'
201 ]
202 assert reality == my_expectation
203
204
205def test_dir_person_instance():
206 an_instance_of_person = src.person.Person(
207 first_name='first_name',
208 last_name='last_name',
209 sex='M',
210 year_of_birth=2026,
211 )
212
213 reality = dir(an_instance_of_person)
214 my_expectation = [
215 '__class__',
216 '__delattr__',
217 '__dict__',
218 '__dir__',
219 '__doc__',
220 '__eq__',
221 '__firstlineno__',
222 '__format__',
223 '__ge__',
224 '__getattribute__',
225 '__getstate__',
226 '__gt__',
227 '__hash__',
228 '__init__',
229 '__init_subclass__',
230 '__le__',
231 '__lt__',
232 '__module__',
233 '__ne__',
234 '__new__',
235 '__reduce__',
236 '__reduce_ex__',
237 '__repr__',
238 '__setattr__',
239 '__sizeof__',
240 '__static_attributes__',
241 '__str__',
242 '__subclasshook__',
243 '__weakref__',
244 'first_name',
245 'last_name',
246 'say_hello',
247 'sex',
248 'year_of_birth',
249 ]
250 assert reality == my_expectation
251
252
253# Exceptions seen
254# AssertionError
255# NameError
256# TypeError
257# AttributeError
258# SyntaxError
open the project
I open a terminal
I change directory to the project
cd personthe terminal shows I am in the
personfolder.../pumping_python/personI open
test_person.pyfrom thetestsfolderI use pytest-watcher to run the tests automatically
uv run pytest-watcher . --nowthe terminal shows
tests/test_person.py .... [100%] =================== 4 passed in A.BCs ====================
test Person class
I made a function that makes a string to represent a person when I give it first_name, last_name, sex and year_of_birth. I can also represent a person with a class because it is attributes and methods that belong together.
RED: make it fail
I make a copy of a class to represent joe in test_joe in test_person.py
4def test_joe():
5 first_name = 'joe'
6 last_name = 'blow'
7 sex = 'M'
8 year_of_birth = 1996
9
10 reality = src.person.factory(
11 first_name=first_name,
12 last_name=last_name,
13 sex=sex,
14 year_of_birth=year_of_birth,
15 )
16 my_expectation = (
17 f'{first_name}, {last_name},'
18 f' {sex}, {year_of_birth}'
19 )
20 assert reality == my_expectation
21
22 reality = src.person.say_hello(
23 first_name=first_name,
24 last_name=last_name,
25 year_of_birth=year_of_birth,
26 )
27 my_expectation = (
28 f'Hello, my name is {first_name}'
29 f' {last_name} and I am'
30 f' {2026-year_of_birth}.'
31 )
32 assert reality == my_expectation
33
34 joe = Person(
35 first_name=first_name,
36 last_name=last_name,
37 sex=sex,
38 year_of_birth=year_of_birth,
39 )
40
41
42def test_jane():
the terminal is my friend, and shows NameError
NameError: name 'Person' is not defined
because there is no definition for Person in test_person.py.
GREEN: make it pass
I add a class definition for
Person1import src.person 2 3 4class Person: 5 6 pass 7 8 9def test_joe():The terminal is my friend, and shows TypeError
TypeError: Person() takes no argumentsbecause this happens when
joe = Person(first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth)runsjoe = Person( first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) └── Person( first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) # has no constructor methodwhich raises TypeError since classes do not take arguments like a function without a method that handles those arguments and I called this one with four arguments.
the constructor method
A constructor method is used to define what happens when an instance (a copy) of a class is made.
I add a constructor method to the Person class so it can take arguments
4class Person: 5 6 # pass 7 def __init__(): 8 return None 9 10 11def test_joe():the terminal is my friend, and shows TypeError
TypeError: Person.__init__() got an unexpected keyword argument 'first_name'Here is what is happens when the
joeinstance of thePersonclass runsjoe = Person( first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) └── Person.__init__( first_name='joe', last_name='blow', sex='M', year_of_birth=1996, )which raises TypeError since the
__init__method got called with a name (first_name) that is not in the parentheses of its definition.I am violating the method signature when I call it in a way that it was not designed to be called.
I add the name in parentheses so that the __init__ constructor method can take input
4class Person: 5 6 # pass 7 # def __init__(): 8 def __init__(first_name): 9 return None 10 11 12def test_joe():the terminal is my friend, and shows TypeError
TypeError: Person.__init__() got multiple values for argument 'first_name'because a method of an instance takes the instance of the class (
self) it belongs to as the first argument.The test calls the function with four keyword arguments
(first_name, last_name, sex and year_of_birth'). How does Python know which value to use for the first argument if I use the position and a keyword?I add
selfas the first argument4class Person: 5 6 # pass 7 # def __init__(): 8 # def __init__(first_name): 9 def __init__(self, first_name): 10 return None 11 12 13def test_joe():selfis Python convention, I can use any name I want.selfis the instance of the class.The terminal is my friend, and shows TypeError
TypeError: Person.__init__() got an unexpected keyword argument 'last_name'. Did you mean 'first_name'?because this happens when
joe = Person(first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth)runsjoe = Person( first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) └── Person.__init__( self, first_name='joe', last_name='blow', # not in definition sex='M', year_of_birth=1996, )which raises TypeError since the
__init__method got called with a name (first_name) that is not in the parentheses of its definition.selfis the instance of the class.I am violating the method signature when I call it in a way that it was not designed to be called.
I have seen this before, so far it is the same as making the factory function.
I add
last_nameto the definition of__init__4class Person: 5 6 # pass 7 # def __init__(): 8 # def __init__(first_name): 9 # def __init__(self, first_name): 10 def __init__(self, first_name, last_name): 11 return None 12 13 14def test_joe():the terminal is my friend, and shows TypeError
TypeError: Person.__init__() got an unexpected keyword argument 'sex'because this happens when
joe = Person(first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth)runsjoe = Person( first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) └── Person.__init__( self, first_name='joe', last_name='blow', sex='M', # not in definition year_of_birth=1996, )which raises TypeError since the
__init__method got called with a name (sex) that is not in the parentheses of its definition.selfis the instance of the class.I am violating the method signature when I call it in a way that it was not designed to be called.
Still the same as making the factory function.
I add
sexto the definition of the __init__ method4class Person: 5 6 # pass 7 # def __init__(): 8 # def __init__(first_name): 9 # def __init__(self, first_name): 10 # def __init__(self, first_name, last_name): 11 def __init__( 12 self, first_name, last_name, 13 sex, 14 ): 15 return None 16 17 18def test_joe():the terminal is my friend, and shows TypeError
TypeError: Person.__init__() got an unexpected keyword argument 'year_of_birth'because this happens when
joe = Person(first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth)runsjoe = Person( first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) └── Person.__init__( self, first_name='joe', last_name='blow', sex='M', year_of_birth=1996, # not in definition )which raises TypeError because the
__init__method got called with a name (year_of_birth) that is not in the parentheses of its definition.selfis the instance of the class.I am violating the method signature when I call it in a way that it was not designed to be called.
Same as with the factory function.
I add
year_of_birthto the definition of the __init__ constructor method4class Person: 5 6 # pass 7 # def __init__(): 8 # def __init__(first_name): 9 # def __init__(self, first_name): 10 # def __init__(self, first_name, last_name): 11 def __init__( 12 self, first_name, last_name, 13 # sex, 14 sex, year_of_birth 15 ): 16 return None 17 18 19def test_joe():the test passes.
REFACTOR: make it better
I remove the commented lines
4class Person: 5 6 def __init__( 7 self, first_name, last_name, 8 sex, year_of_birth, 9 ): 10 return None 11 12 13def test_joe():I open a new terminal then change directories to
personcd personI add a git commit message in the new terminal
git commit -am 'add Person class'
test say_hello method
I made a person say hi with a function, I can also do the same thing with a class because it is attributes and methods that belong together.
RED: make it fail
I add an assertion with a call to the say_hello function with the attributes of
joein test_joe43 joe = Person( 44 first_name=first_name, 45 last_name=last_name, 46 sex=sex, 47 year_of_birth=year_of_birth, 48 ) 49 50 reality = src.person.say_hello( 51 first_name=joe.first_name, 52 last_name=joe.last_name, 53 year_of_birth=joe.year_of_birth, 54 ) 55 assert reality == my_expectation 56 57 58def test_jane():the terminal is my friend, and shows AttributeError
AttributeError: 'Person' object has no attribute 'first_name'because there is nothing named
first_namein the Person class
GREEN: make it pass
I add
self.first_nameto the definition of the __init__ constructor method4class Person: 5 6 def __init__( 7 self, first_name, last_name, 8 sex, year_of_birth, 9 ): 10 self.first_name 11 return None 12 13 14def test_joe():the terminal still shows AttributeError because this is just a reference to the name, not a definition.
I point
self.first_nameto the value forfirst_namewhen the __init__ method is called4class Person: 5 6 def __init__( 7 self, first_name, last_name, 8 sex, year_of_birth, 9 ): 10 # self.first_name 11 self.first_name = first_name 12 return None 13 14 15def test_joe():the terminal is my friend, and shows AttributeError
AttributeError: 'Person' object has no attribute 'last_name'. Did you mean: 'first_name'?I add
self.last_nameand point it to the value forlast_namewhen the __init__ method is called4class Person: 5 6 def __init__( 7 self, first_name, last_name, 8 sex, year_of_birth, 9 ): 10 # self.first_name 11 self.first_name = first_name 12 self.last_name = last_name 13 return None 14 15 16def test_joe():the terminal is my friend, and shows AttributeError
AttributeError: 'Person' object has no attribute 'year_of_birth'I add
self.year_of_birthand point it to the value foryear_of_birthwhen the __init__ constructor method is called4class Person: 5 6 def __init__( 7 self, first_name, last_name, 8 sex, year_of_birth, 9 ): 10 # self.first_name 11 self.first_name = first_name 12 self.last_name = last_name 13 self.year_of_birth = year_of_birth 14 return None 15 16 17def test_joe():the test passes, because
given
first_name = 'joe' last_name = 'blow' sex = 'M' year_of_birth = 1996joe = Person( first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) └── Person.__init__( self, first_name='joe', last_name='blow', sex='M', year_of_birth=1996, ) ├── self.first_name = 'joe' ├── self.last_name = 'blow' └── self.year_of_birth = 1996selfis the instance of the class.reality = src.person.say_hello( first_name=joe.first_name, last_name=joe.last_name, year_of_birth=joe.year_of_birth, ) └── src.person.say_hello( first_name='joe', last_name='blow', year_of_birth=1996, )Python follows this path
src.person.say_hello src └── person.py └── def say_hello( first_name, last_name, year_of_birth, ): └── return ( f'Hello, my name is {first_name}' f' {last_name} and I am' f' {2026-year_of_birth}.' )
and the result is
'Hello, my name is joe blow and I am 30.'
REFACTOR: make it better
I remove the commented line
4class Person: 5 6 def __init__( 7 self, first_name, last_name, 8 sex, year_of_birth, 9 ): 10 self.first_name = first_name 11 self.last_name = last_name 12 self.year_of_birth = year_of_birth 13 return None 14 15 16def test_joe():I change the call to
src.person.say_helloin test_joe to a call to the say_hello method of the Person class47 joe = Person( 48 first_name=first_name, 49 last_name=last_name, 50 sex=sex, 51 year_of_birth=year_of_birth, 52 ) 53 54 # reality = src.person.say_hello( 55 reality = Person.say_hello( 56 first_name=joe.first_name, 57 last_name=joe.last_name, 58 year_of_birth=joe.year_of_birth, 59 ) 60 assert reality == my_expectation 61 62 63def test_jane():the terminal is my friend, and shows AttributeError
AttributeError: type object 'Person' has no attribute 'say_hello'because the test calls the say_hello method which does not yet exist in the Person class.
I add a method definition for it to the Person class
4class Person: 5 6 def __init__( 7 self, first_name, last_name, 8 sex, year_of_birth, 9 ): 10 self.first_name = first_name 11 self.last_name = last_name 12 self.year_of_birth = year_of_birth 13 return None 14 15 def say_hello(): 16 return None 17 18 19def test_joe():the terminal is my friend, and shows TypeError
TypeError: Person.say_hello() got an unexpected keyword argument 'first_name'because the
say_hellomethod got called with a name (first_name) that is not in the parentheses of its definition..I add
first_nameto the method definition15 # def say_hello(): 16 def say_hello(first_name): 17 return None 18 19 20def test_joe():the terminal is my friend, and shows TypeError
TypeError: Person.say_hello() got an unexpected keyword argument 'last_name'. Did you mean 'first_name'?because the
say_hellomethod got called with a name (first_name) that is not in the parentheses of its definition.I add
last_nameto the method definition15 # def say_hello(): 16 # def say_hello(first_name): 17 def say_hello(first_name, last_name): 18 return None 19 20 21def test_joe():the terminal is my friend, and shows TypeError
TypeError: Person.say_hello() got an unexpected keyword argument 'year_of_birth'because the
say_hellomethod got called with a name (year_of_birth) that is not in the parentheses of its definition.I add
year_of_birthto the method definition15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 def say_hello(first_name, last_name, year_of_birth): 19 return None 20 21 22def test_joe():the terminal is my friend, and shows AssertionError
AssertionError: assert None == 'Hello, my name is joe blow and I am 30.'I change the return statement to match
15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 def say_hello(first_name, last_name, year_of_birth): 19 # return None 20 return 'Hello, my name is joe blow and I am 30.' 21 22 23def test_joe():the test passes.
I add a call to the Person class and say_hello method in test_jane
69def test_jane(): 70 first_name = 'jane' 71 last_name = 'doe' 72 sex = 'F' 73 year_of_birth = 1991 74 75 reality = src.person.factory( 76 first_name=first_name, 77 last_name=last_name, 78 sex=sex, 79 year_of_birth=year_of_birth, 80 ) 81 my_expectation = ( 82 f'{first_name}, {last_name},' 83 f' {sex}, {year_of_birth}' 84 ) 85 assert reality == my_expectation 86 87 reality = src.person.say_hello( 88 first_name=first_name, 89 last_name=last_name, 90 year_of_birth=year_of_birth, 91 ) 92 my_expectation = ( 93 f'Hello, my name is {first_name}' 94 f' {last_name} and I am' 95 f' {2026-year_of_birth}.' 96 ) 97 assert reality == my_expectation 98 99 jane = Person( 100 first_name=first_name, 101 last_name=last_name, 102 sex=sex, 103 year_of_birth=year_of_birth, 104 ) 105 106 reality = Person.say_hello( 107 first_name=jane.first_name, 108 last_name=jane.last_name, 109 year_of_birth=jane.year_of_birth, 110 ) 111 assert reality == my_expectation 112 113 114def test_john():the terminal is my friend, and shows AssertionError
AssertionError: assert 'Hello, my name ... and I am 30.' == 'Hello, my name ... and I am 35.'I change the return statement to an f-string with the input like the say_hello function in
person.pyin thesrcfolder15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 def say_hello(first_name, last_name, year_of_birth): 19 # return None 20 # return 'Hello, my name is joe blow and I am 30.' 21 return ( 22 f'Hello, my name is {first_name}' 23 f' {last_name} and I am' 24 f' {2026-year_of_birth}.' 25 ) 26 27 28def test_joe():the test passes. This is still repeating the values for
first_name,last_nameandyear_of_birth.I change the call to the say_hello method in test_joe to take in an instance (copy) of the Person class since it will already have the attributes
65 # reality = src.person.say_hello( 66 reality = Person.say_hello( 67 person=joe, 68 first_name=joe.first_name, 69 last_name=joe.last_name, 70 year_of_birth=joe.year_of_birth, 71 ) 72 assert reality == my_expectation 73 74 75def test_jane():the terminal is my friend, and shows TypeError
TypeError: Person.say_hello() got an unexpected keyword argument 'person'because the
say_hellomethod got called with a name (person) that is not in the parentheses of its definition.I add
personto the method definition for say_hello15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 # def say_hello(first_name, last_name, year_of_birth): 19 def say_hello( 20 person, first_name, last_name, year_of_birth, 21 ): 22 # return None 23 # return 'Hello, my name is joe blow and I am 30.' 24 return ( 25 f'Hello, my name is {first_name}' 26 f' {last_name} and I am' 27 f' {2026-year_of_birth}.' 28 ) 29 30 31def test_joe():the terminal is my friend, and shows TypeError
TypeError: Person.say_hello() missing 1 required positional argument: 'person'I have to make the same change to test_jane
I add the
personkeyword argument to the call to the say_hello method in test_jane116 reality = Person.say_hello( 117 person=jane, 118 first_name=jane.first_name, 119 last_name=jane.last_name, 120 year_of_birth=jane.year_of_birth, 121 ) 122 assert reality == my_expectation 123 124 125def test_john():the test passes.
I change the return statement of the say_hello method to use the attributes of the class instance it receives as input
15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 # def say_hello(first_name, last_name, year_of_birth): 19 def say_hello( 20 person, first_name, last_name, year_of_birth, 21 ): 22 # return None 23 # return 'Hello, my name is joe blow and I am 30.' 24 return ( 25 # f'Hello, my name is {first_name}' 26 # f' {last_name} and I am' 27 # f' {2026-year_of_birth}.' 28 f'Hello, my name is {person.first_name}' 29 f' {person.last_name} and I am' 30 f' {2026-person.year_of_birth}.' 31 ) 32 33 34def test_joe():the test passes because
given
first_name = 'joe' last_name = 'blow' sex = 'M' year_of_birth = 1996joe = Person( first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) └── Person.__init__( self, first_name='joe', last_name='blow', sex='M', year_of_birth=1996, ) ├── self.first_name = 'joe' ├── self.last_name = 'blow' └── self.year_of_birth = 1996selfis the instance of the class akajoe.reality = Person.say_hello( person=joe, first_name=joe.first_name, last_name=joe.last_name, year_of_birth=joe.year_of_birth, ) └── Person.say_hello( person=joe, first_name='joe', last_name='blow', year_of_birth=1996, ) └── return ( f'Hello, my name is {person.first_name}' f' {person.last_name} and I am' f' {2026-person.year_of_birth}.' ) return ( f'Hello, my name is {joe.first_name}' f' {joe.last_name} and I am' f' {2026-joe.year_of_birth}.' )
and the result is
'Hello, my name is joe blow and I am 30.'I remove the
first_name,last_nameandyear_of_birtharguments from the call in test_joe since they are repetitions of the class attributes71 # reality = src.person.say_hello( 72 reality = Person.say_hello( 73 person=joe, 74 # first_name=joe.first_name, 75 # last_name=joe.last_name, 76 # year_of_birth=joe.year_of_birth, 77 ) 78 assert reality == my_expectation 79 80 81def test_jane():the terminal is my friend, and shows TypeError
TypeError: Person.say_hello() missing 3 required positional arguments: 'first_name', 'last_name', and 'year_of_birth'I remove
first_name,last_nameandyear_of_birthfrom the definition of the say_hello method15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 # def say_hello(first_name, last_name, year_of_birth): 19 def say_hello( 20 # person, first_name, last_name, year_of_birth, 21 person 22 ): 23 # return None 24 # return 'Hello, my name is joe blow and I am 30.' 25 return ( 26 # f'Hello, my name is {first_name}' 27 # f' {last_name} and I am' 28 # f' {2026-year_of_birth}.' 29 f'Hello, my name is {person.first_name}' 30 f' {person.last_name} and I am' 31 f' {2026-person.year_of_birth}.' 32 ) 33 34 35def test_joe():the terminal is my friend, and shows TypeError
TypeError: Person.say_hello() got an unexpected keyword argument 'first_name'because the
say_hellomethod got called with a name (first_name) that is not in the parentheses of its definition.I remove the
first_name,last_nameandyear_of_birtharguments from the call in test_jane119 reality = Person.say_hello( 120 person=jane, 121 # first_name=jane.first_name, 122 # last_name=jane.last_name, 123 # year_of_birth=jane.year_of_birth, 124 ) 125 assert reality == my_expectation 126 127 128def test_john():the test passes. This is still a repetition. I give an instance (copy) of the Person class as input to the say_hello method of the Person class (
Person.say_hello).I change the call to the say_hello method in test_jane because the say_hello method is in the Person class so its copies also have the say_hello method
119 # reality = Person.say_hello( 120 reality = jane.say_hello( 121 person=jane, 122 # first_name=jane.first_name, 123 # last_name=jane.last_name, 124 # year_of_birth=jane.year_of_birth, 125 ) 126 assert reality == my_expectation 127 128 129def test_john():the terminal is my friend, and shows TypeError
TypeError: Person.say_hello() got multiple values for argument 'person'because a method of an instance takes the instance of the class (
self) it belongs to as the first argument.
what is the staticmethod decorator?
I can use the staticmethod decorator if I do not want to add
selfto the method definition whenit does not use anything that belongs to the class that way I am not sending more information than what the method needs. I add@staticmethodto the say_hello method15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 # def say_hello(first_name, last_name, year_of_birth): 19 @staticmethod 20 def say_hello( 21 # person, first_name, last_name, year_of_birth, 22 person 23 ): 24 # return None 25 # return 'Hello, my name is joe blow and I am 30.' 26 return ( 27 # f'Hello, my name is {first_name}' 28 # f' {last_name} and I am' 29 # f' {2026-year_of_birth}.' 30 f'Hello, my name is {person.first_name}' 31 f' {person.last_name} and I am' 32 f' {2026-person.year_of_birth}.' 33 ) 34 35 36def test_joe():the test passes.
I change the call to
Person.say_helloin test_joe because the say_hello method is in the Person class, there is no need for it to take a copy of the Person class as input since it should be able to use its own attributes73 # reality = src.person.say_hello( 74 # reality = Person.say_hello( 75 reality = joe.say_hello( 76 # person=joe, 77 # first_name=joe.first_name, 78 # last_name=joe.last_name, 79 # year_of_birth=joe.year_of_birth, 80 ) 81 assert reality == my_expectation 82 83 84def test_jane():the terminal is my friend, and shows TypeError
TypeError: Person.say_hello() missing 1 required positional argument: 'person'I make
personoptional in the say_hello method15# def say_hello(): 16# def say_hello(first_name): 17# def say_hello(first_name, last_name): 18# def say_hello(first_name, last_name, year_of_birth): 19@staticmethod 20def say_hello( 21 # person, first_name, last_name, year_of_birth, 22 # person 23 person=None 24):the terminal is my friend, and shows AttributeError
AttributeError: 'NoneType' object has no attribute 'first_name'I change
person.toself.in the return statement15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 # def say_hello(first_name, last_name, year_of_birth): 19 @staticmethod 20 def say_hello( 21 # person, first_name, last_name, year_of_birth, 22 # person 23 person=None 24 ): 25 # return None 26 # return 'Hello, my name is joe blow and I am 30.' 27 return ( 28 # f'Hello, my name is {first_name}' 29 # f' {last_name} and I am' 30 # f' {2026-year_of_birth}.' 31 # f'Hello, my name is {person.first_name}' 32 # f' {person.last_name} and I am' 33 # f' {2026-person.year_of_birth}.' 34 f'Hello, my name is {self.first_name}' 35 f' {self.last_name} and I am' 36 f' {2026-self.year_of_birth}.' 37 ) 38 39 40def test_joe():the terminal is my friend, and shows NameError
NameError: name 'self' is not definedI add
selfto the parentheses15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 # def say_hello(first_name, last_name, year_of_birth): 19 @staticmethod 20 def say_hello( 21 # person, first_name, last_name, year_of_birth, 22 # person 23 # person=None 24 self, person=None 25 ):the terminal is my friend, and shows TypeError
TypeError: Person.say_hello() missing 1 required positional argument: 'self'I remove the staticmethod decorator because I no longer need it since the say_hello method is using class attributes
15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 # def say_hello(first_name, last_name, year_of_birth): 19 # @staticmethod 20 def say_hello( 21 # person, first_name, last_name, year_of_birth, 22 # person 23 # person=None 24 self, person=None 25 ):the test passes because
given
first_name = 'joe' last_name = 'blow' sex = 'M' year_of_birth = 1996joe = Person( first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) └── Person.__init__( self, first_name='joe', last_name='blow', sex='M', year_of_birth=1996, ) ├── self.first_name = 'joe' ├── self.last_name = 'blow' └── self.year_of_birth = 1996selfis the instance of the class akajoe.reality = joe.say_hello() └── return ( f'Hello, my name is {self.first_name}' f' {self.last_name} and I am' f' {2026-self.year_of_birth}.' ) # inside joe, self == joe return ( f'Hello, my name is {joe.first_name}' f' {joe.last_name} and I am' f' {2026-joe.year_of_birth}.' )and the result is
'Hello, my name is joe blow and I am 30.'a simple way to think of
joe.say_hello()isjoe.say_hello() == Person().say_hello() joe.say_hello() == joe.say_hello(Person()) joe.say_hello() == joe.say_hello(joe)I do not need to pass
joeas input to the say_hello method since it isself.
I remove
person=janefrom the call to the say_hello method in test_jane because the say_hello method is in the Person class126 # reality = Person.say_hello( 127 reality = jane.say_hello( 128 # person=jane, 129 # first_name=jane.first_name, 130 # last_name=jane.last_name, 131 # year_of_birth=jane.year_of_birth, 132 ) 133 assert reality == my_expectation 134 135 136def test_john():the test is still green.
I add an assertion to test_john for the say_hello method
154 reality = src.person.say_hello( 155 first_name=first_name, 156 last_name=last_name, 157 year_of_birth=year_of_birth, 158 ) 159 my_expectation = ( 160 f'Hello, my name is {first_name}' 161 f' {last_name} and I am' 162 f' {2026-year_of_birth}.' 163 ) 164 assert reality == my_expectation 165 166 john = Person( 167 first_name=first_name, 168 last_name=last_name, 169 sex=sex, 170 year_of_birth=year_of_birth, 171 ) 172 173 reality = john.say_hello() 174 assert reality == None 175 176 177def test_mary():the terminal is my friend, and shows AssertionError
AssertionError: assert 'Hello, my name is john smith and I am 446.' == NoneI change my expectation to match
reality166 john = Person( 167 first_name=first_name, 168 last_name=last_name, 169 sex=sex, 170 year_of_birth=year_of_birth, 171 ) 172 173 reality = john.say_hello() 174 # assert reality == None 175 assert reality == my_expectation 176 177 178def test_mary():the test passes.
I add an assertion to test_mary for the say_hello method
196 reality = src.person.say_hello( 197 first_name=first_name, 198 last_name=last_name, 199 year_of_birth=year_of_birth, 200 ) 201 my_expectation = ( 202 f'Hello, my name is {first_name}' 203 f' {last_name} and I am' 204 f' {2026-year_of_birth}.' 205 ) 206 assert reality == my_expectation 207 208 mary = Person( 209 first_name=first_name, 210 last_name=last_name, 211 sex=sex, 212 year_of_birth=year_of_birth, 213 ) 214 215 reality = mary.say_hello() 216 assert reality == None 217 218 219# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: assert 'Hello, my name is mary public and I am 26.' == NoneI change my expectation to match
reality208 mary = Person( 209 first_name=first_name, 210 last_name=last_name, 211 sex=sex, 212 year_of_birth=year_of_birth, 213 ) 214 215 reality = mary.say_hello() 216 # assert reality == None 217 assert reality == my_expectation 218 219 220# Exceptions seenthe test passes because
given
first_name = 'mary' last_name = 'public' sex = 'F' year_of_birth = 2000mary = Person( first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) └── Person.__init__( self, first_name='mary', last_name='public', sex='F', year_of_birth=2000, ) ├── self.first_name = 'mary' ├── self.last_name = 'public' └── self.year_of_birth = 2000selfis the instance of the class akamary.reality = mary.say_hello() └── return ( f'Hello, my name is {self.first_name}' f' {self.last_name} and I am' f' {2026-self.year_of_birth}.' ) # inside mary, self == mary return ( f'Hello, my name is {mary.first_name}' f' {mary.last_name} and I am' f' {2026-mary.year_of_birth}.' )and the result is
'Hello, my name is mary public and I am 26.'a simple way to think of
mary.say_hello()ismary.say_hello() == Person().say_hello() mary.say_hello() == mary.say_hello(Person()) mary.say_hello() == mary.say_hello(mary)I do not need to pass
maryas input to the say_hello method since it isself.
I add a git commit message in the other terminal
git commit -am 'add say_hello method'
separate and equal Person class
RED: make it fail
I go back to the terminal where the tests are running
I change
maryin test_mary to be the result of a call to the Person class of thepersonmodule in thesrcfolder instead of a call to the Person class intest_person.py196 reality = src.person.say_hello( 197 first_name=first_name, 198 last_name=last_name, 199 year_of_birth=year_of_birth, 200 ) 201 my_expectation = ( 202 f'Hello, my name is {first_name}' 203 f' {last_name} and I am' 204 f' {2026-year_of_birth}.' 205 ) 206 assert reality == my_expectation 207 208 # mary = Person( 209 mary = src.person.Person( 210 first_name=first_name, 211 last_name=last_name, 212 sex=sex, 213 year_of_birth=year_of_birth, 214 ) 215 216 reality = mary.say_hello() 217 # assert reality == None 218 assert reality == my_expectation 219 220 221# Exceptions seenthe terminal is my friend, and shows AttributeError
AttributeError: module 'src.person' has no attribute 'Person'because there is nothing with that name in the
person.pyfile in thesrcfolder.
GREEN: make it pass
I open
person/__init__.pyfrom thesrcfolderI add the name to
person.py1Person 2 3 4def say_hello( 5 first_name, last_name, year_of_birth, 6):the terminal is my friend, and shows NameError
NameError: name 'Person' is not definedI point it to None to define it
1# Person 2Person = None 3 4 5def say_hello( 6 first_name, last_name, year_of_birth, 7):the terminal is my friend, and shows TypeError
TypeError: 'NoneType' object is not callablebecause I cannot call None like a function.
I change
Personto a function1# Person 2# Person = None 3def Person(): 4 return None 5 6 7def say_hello( 8 first_name, last_name, year_of_birth, 9):the terminal is my friend, and shows TypeError
TypeError: Person() got an unexpected keyword argument 'first_name'because the
Personfunction got called with a name (first_name) that is not in the parentheses of its definition.I add
first_nameto the parentheses1# Person 2# Person = None 3# def Person(): 4def Person(first_name): 5 return None 6 7 8def say_hello( 9 first_name, last_name, year_of_birth, 10):the terminal is my friend, and shows TypeError
TypeError: Person() got an unexpected keyword argument 'last_name'. Did you mean 'first_name'?because the
Personfunction got called with a name (last_name) that is not in the parentheses of its definition.I add
last_nameto the parentheses1# Person 2# Person = None 3# def Person(): 4# def Person(first_name): 5def Person(first_name, last_name): 6 return None 7 8 9def say_hello( 10 first_name, last_name, year_of_birth, 11):the terminal is my friend, and shows TypeError
TypeError: Person() got an unexpected keyword argument 'sex'because the
Personfunction got called with a name (sex) that is not in the parentheses of its definition.I add
sexto the parentheses1# Person 2# Person = None 3# def Person(): 4# def Person(first_name): 5# def Person(first_name, last_name): 6def Person(first_name, last_name, sex): 7 return None 8 9 10def say_hello( 11 first_name, last_name, year_of_birth, 12):the terminal is my friend, and shows TypeError
TypeError: Person() got an unexpected keyword argument 'year_of_birth'because the
Personfunction got called with a name (year_of_birth) that is not in the parentheses of its definition.I add
year_of_birthto the parentheses1# Person 2# Person = None 3# def Person(): 4# def Person(first_name): 5# def Person(first_name, last_name): 6# def Person(first_name, last_name, sex): 7def Person( 8 first_name, last_name, 9 sex, year_of_birth, 10): 11 return None 12 13 14def say_hello( 15 first_name, last_name, year_of_birth, 16):the terminal is my friend, and shows AttributeError
AttributeError: 'NoneType' object has no attribute 'say_hello'because the function I just made returns None
given
first_name = 'mary' last_name = 'public' sex = 'F' year_of_birth = 2000mary = Person( first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) └── Person( first_name='mary', last_name='public', sex='F', year_of_birth=2000, ) └── return Nonereality = mary.say_hello() reality = None.say_hello()
which raises AttributeError since None does not have anything named
say_helloin it.I change
Personto a class1# Person 2# Person = None 3# def Person(): 4# def Person(first_name): 5# def Person(first_name, last_name): 6# def Person(first_name, last_name, sex): 7# def Person( 8class Person( 9 first_name, last_name, 10 sex, year_of_birth, 11): 12 return None 13 14 15def say_hello( 16 first_name, last_name, year_of_birth, 17):the terminal is my friend, and shows SyntaxError
SyntaxError: 'return' outside functionI add SyntaxError to the list of Exceptions seen, in
test_person.py221# Exceptions seen 222# AssertionError 223# NameError 224# TypeError 225# AttributeError 226# SyntaxErrorI change the return statement to the pass keyword, in
person.py1# Person 2# Person = None 3# def Person(): 4# def Person(first_name): 5# def Person(first_name, last_name): 6# def Person(first_name, last_name, sex): 7# def Person( 8class Person( 9 first_name, last_name, 10 sex, year_of_birth, 11): 12 # return None 13 pass 14 15 16def say_hello( 17 first_name, last_name, year_of_birth, 18):the terminal is my friend, and shows NameError
NameError: name 'first_name' is not definedbecause the only definitions for
first_nameare in the say_hello and factory functions inperson.py.I add the constructor method to handle the inputs
1# Person 2# Person = None 3# def Person(): 4# def Person(first_name): 5# def Person(first_name, last_name): 6# def Person(first_name, last_name, sex): 7# def Person( 8# class Person( 9# first_name, last_name, 10# sex, year_of_birth, 11# ): 12class Person: 13 14 def __init__( 15 first_name, last_name, 16 sex, year_of_birth, 17 ): 18 # return None 19 pass 20 21 22def say_hello( 23 first_name, last_name, year_of_birth, 24):the terminal is my friend, and shows TypeError
TypeError: Person.__init__() got multiple values for argument 'first_name'because a method of an instance takes the instance of the class (
self) it belongs to as the first argument.I add
selfto the constructor method12class Person: 13 14 def __init__( 15 # first_name, last_name, 16 self, first_name, last_name, 17 sex, year_of_birth, 18 ): 19 # return None 20 pass 21 22 23def say_hello( 24 first_name, last_name, year_of_birth, 25):the terminal is my friend, and shows AttributeError
AttributeError: 'Person' object has no attribute 'say_hello'I add the name to the Person class
12class Person: 13 14 say_hello 15 16 def __init__( 17 # first_name, last_name, 18 self, first_name, last_name, 19 sex, year_of_birth, 20 ):the terminal is my friend, and shows NameError
NameError: name 'say_hello' is not definedI point
say_helloto None to define it12class Person: 13 14 # say_hello 15 say_hello = None 16 17 def __init__( 18 # first_name, last_name, 19 self, first_name, last_name, 20 sex, year_of_birth, 21 ):the terminal is my friend, and shows TypeError
TypeError: 'NoneType' object is not callablebecause I cannot call None like a function.
I change it to a method
12class Person: 13 14 # say_hello 15 # say_hello = None 16 17 def __init__( 18 # first_name, last_name, 19 self, first_name, last_name, 20 sex, year_of_birth, 21 ): 22 # return None 23 pass 24 25 def say_hello(): 26 return None 27 28 29def say_hello( 30 first_name, last_name, year_of_birth, 31):TypeError: Person.say_hello() takes 0 positional arguments but 1 was givenI add a name to the parentheses
25 # def say_hello(): 26 def say_hello(argument): 27 return None 28 29 30def say_hello( 31 first_name, last_name, year_of_birth, 32):the terminal is my friend, and shows AssertionError
AssertionError: assert None == 'Hello, my name is mary public and I am 26.'I copy and paste the string from the terminal to use as the return statement
25 # def say_hello(): 26 def say_hello(argument): 27 # return None 28 return 'Hello, my name is mary public and I am 26.' 29 30 31def say_hello( 32 first_name, last_name, year_of_birth, 33):the test passes.
REFACTOR: make it better
I remove the commented lines from test_mary in
test_person.py178def test_mary(): 179 first_name = 'mary' 180 last_name = 'public' 181 sex = 'F' 182 year_of_birth = 2000 183 184 reality = src.person.factory( 185 first_name=first_name, 186 last_name=last_name, 187 sex=sex, 188 year_of_birth=year_of_birth, 189 ) 190 my_expectation = ( 191 f'{first_name}, {last_name},' 192 f' {sex}, {year_of_birth}' 193 ) 194 assert reality == my_expectation 195 196 reality = src.person.say_hello( 197 first_name=first_name, 198 last_name=last_name, 199 year_of_birth=year_of_birth, 200 ) 201 my_expectation = ( 202 f'Hello, my name is {first_name}' 203 f' {last_name} and I am' 204 f' {2026-year_of_birth}.' 205 ) 206 assert reality == my_expectation 207 208 mary = src.person.Person( 209 first_name=first_name, 210 last_name=last_name, 211 sex=sex, 212 year_of_birth=year_of_birth, 213 ) 214 215 reality = mary.say_hello() 216 assert reality == my_expectation 217 218 219# Exceptions seen 220# AssertionError 221# NameError 222# TypeError 223# AttributeError 224# SyntaxErrorI change
johnin test_john to be the result of a call to the Person class of thepersonmodule in thesrcfolder154 reality = src.person.say_hello( 155 first_name=first_name, 156 last_name=last_name, 157 year_of_birth=year_of_birth, 158 ) 159 my_expectation = ( 160 f'Hello, my name is {first_name}' 161 f' {last_name} and I am' 162 f' {2026-year_of_birth}.' 163 ) 164 assert reality == my_expectation 165 166 # john = Person( 167 john = src.person.Person( 168 first_name=first_name, 169 last_name=last_name, 170 sex=sex, 171 year_of_birth=year_of_birth, 172 ) 173 174 reality = john.say_hello() 175 # assert reality == None 176 assert reality == my_expectation 177 178 179def test_mary():the terminal is my friend, and shows AssertionError
AssertionError: assert 'Hello, my name ... and I am 26.' == 'Hello, my name ...and I am 446.'I change the return statement of the say_hello method to return the input, in
person.py25 # def say_hello(): 26 def say_hello(argument): 27 # return None 28 # return 'Hello, my name is mary public and I am 26.' 29 return argument 30 31 32def say_hello( 33 first_name, last_name, year_of_birth, 34):the terminal is my friend, and shows AssertionError
AssertionError: assert <src.person.Person object at 0xffffb012cd34> == 'Hello, my name is mary public and I am 26.'because
argumentis an instance (a copy) of the Person class.I change the return statement to use class attributes in an f-string
25 # def say_hello(): 26 def say_hello(argument): 27 # return None 28 # return 'Hello, my name is mary public and I am 26.' 29 # return argument 30 return ( 31 f'Hello, my name is {argument.first_name}' 32 f' {argument.last_name} and I am' 33 f' {2026-argument.year_of_birth}.' 34 ) 35 36 37def say_hello( 38 first_name, last_name, year_of_birth, 39):the terminal is my friend, and shows AttributeError
AttributeError: 'Person' object has no attribute 'first_name'because I have not defined a class attribute named
first_name.I add
self.first_nameto the __init__ constructor method12class Person: 13 14 # say_hello 15 # say_hello = None 16 17 def __init__( 18 # first_name, last_name, 19 self, first_name, last_name, 20 sex, year_of_birth, 21 ): 22 # return None 23 # pass 24 self.first_name = first_name 25 26 # def say_hello():the terminal is my friend, and shows AttributeError
AttributeError: 'Person' object has no attribute 'last_name'. Did you mean: 'first_name'?because I have not defined a class attribute named
last_name.I add
self.last_nameto the __init__ constructor method17 def __init__( 18 # first_name, last_name, 19 self, first_name, last_name, 20 sex, year_of_birth, 21 ): 22 # return None 23 # pass 24 self.first_name = first_name 25 self.last_name = last_name 26 27 # def say_hello():the terminal is my friend, and shows AttributeError
AttributeError: 'Person' object has no attribute 'year_of_birth'because I have not defined a class attribute named
year_of_birth.I add
self.year_of_birthto the __init__ constructor method17 def __init__( 18 # first_name, last_name, 19 self, first_name, last_name, 20 sex, year_of_birth, 21 ): 22 # return None 23 # pass 24 self.first_name = first_name 25 self.last_name = last_name 26 self.year_of_birth = year_of_birth 27 28 # def say_hello():the test passes.
I change
argumenttoselfin the say_hello method to follow Python convention28 # def say_hello(): 29 # def say_hello(argument): 30 def say_hello(self): 31 # return None 32 # return 'Hello, my name is mary public and I am 26.' 33 # return argument 34 return ( 35 # f'Hello, my name is {argument.first_name}' 36 # f' {argument.last_name} and I am' 37 # f' {2026-argument.year_of_birth}.' 38 f'Hello, my name is {self.first_name}' 39 f' {self.last_name} and I am' 40 f' {2026-self.year_of_birth}.' 41 ) 42 43 44def say_hello( 45 first_name, last_name, year_of_birth, 46):the test is still green because a method of an instance takes the instance of the class (
self) it belongs to as the first argument which meansinstance = Person() instance.say_hello() == Person().say_hello() instance.say_hello() == instance.say_hello(Person()) instance.say_hello() == instance.say_hello(instance)I do not need to pass the instance as input to the say_hello method since it is
self.I remove the commented lines from test_john in
test_person.py136def test_john(): 137 first_name = 'john' 138 last_name = 'smith' 139 sex = 'M' 140 year_of_birth = 1580 141 142 reality = src.person.factory( 143 first_name=first_name, 144 last_name=last_name, 145 sex=sex, 146 year_of_birth=year_of_birth, 147 ) 148 my_expectation = ( 149 f'{first_name}, {last_name},' 150 f' {sex}, {year_of_birth}' 151 ) 152 assert reality == my_expectation 153 154 reality = src.person.say_hello( 155 first_name=first_name, 156 last_name=last_name, 157 year_of_birth=year_of_birth, 158 ) 159 my_expectation = ( 160 f'Hello, my name is {first_name}' 161 f' {last_name} and I am' 162 f' {2026-year_of_birth}.' 163 ) 164 assert reality == my_expectation 165 166 john = src.person.Person( 167 first_name=first_name, 168 last_name=last_name, 169 sex=sex, 170 year_of_birth=year_of_birth, 171 ) 172 173 reality = john.say_hello() 174 assert reality == my_expectation 175 176 177def test_mary():I change
janein test_jane to be the result of a call to the Person class of thepersonmodule in thesrcfolder107 reality = src.person.say_hello( 108 first_name=first_name, 109 last_name=last_name, 110 year_of_birth=year_of_birth, 111 ) 112 my_expectation = ( 113 f'Hello, my name is {first_name}' 114 f' {last_name} and I am' 115 f' {2026-year_of_birth}.' 116 ) 117 assert reality == my_expectation 118 119 # jane = Person( 120 jane = src.person.Person( 121 first_name=first_name, 122 last_name=last_name, 123 sex=sex, 124 year_of_birth=year_of_birth, 125 ) 126 127 # reality = Person.say_hello( 128 reality = jane.say_hello( 129 # person=jane, 130 # first_name=jane.first_name, 131 # last_name=jane.last_name, 132 # year_of_birth=jane.year_of_birth, 133 ) 134 assert reality == my_expectation 135 136 137def test_john():the test is still green.
I remove the commented lines from test_jane
89def test_jane(): 90 first_name = 'jane' 91 last_name = 'doe' 92 sex = 'F' 93 year_of_birth = 1991 94 95 reality = src.person.factory( 96 first_name=first_name, 97 last_name=last_name, 98 sex=sex, 99 year_of_birth=year_of_birth, 100 ) 101 my_expectation = ( 102 f'{first_name}, {last_name},' 103 f' {sex}, {year_of_birth}' 104 ) 105 assert reality == my_expectation 106 107 reality = src.person.say_hello( 108 first_name=first_name, 109 last_name=last_name, 110 year_of_birth=year_of_birth, 111 ) 112 my_expectation = ( 113 f'Hello, my name is {first_name}' 114 f' {last_name} and I am' 115 f' {2026-year_of_birth}.' 116 ) 117 assert reality == my_expectation 118 119 jane = src.person.Person( 120 first_name=first_name, 121 last_name=last_name, 122 sex=sex, 123 year_of_birth=year_of_birth, 124 ) 125 126 reality = jane.say_hello() 127 assert reality == my_expectation 128 129 130def test_john():I change
joein test_joe to be the result of a call to the Person class of thepersonmodule in thesrcfolder59 reality = src.person.say_hello( 60 first_name=first_name, 61 last_name=last_name, 62 year_of_birth=year_of_birth, 63 ) 64 my_expectation = ( 65 f'Hello, my name is {first_name}' 66 f' {last_name} and I am' 67 f' {2026-year_of_birth}.' 68 ) 69 assert reality == my_expectation 70 71 # joe = Person( 72 joe = src.person.Person( 73 first_name=first_name, 74 last_name=last_name, 75 sex=sex, 76 year_of_birth=year_of_birth, 77 ) 78 79 # reality = src.person.say_hello( 80 # reality = Person.say_hello( 81 reality = joe.say_hello( 82 # person=joe, 83 # first_name=joe.first_name, 84 # last_name=joe.last_name, 85 # year_of_birth=joe.year_of_birth, 86 ) 87 assert reality == my_expectation 88 89 90def test_jane():the test is still green.
I remove the commented lines from test_joe
41def test_joe(): 42 first_name = 'joe' 43 last_name = 'blow' 44 sex = 'M' 45 year_of_birth = 1996 46 47 reality = src.person.factory( 48 first_name=first_name, 49 last_name=last_name, 50 sex=sex, 51 year_of_birth=year_of_birth, 52 ) 53 my_expectation = ( 54 f'{first_name}, {last_name},' 55 f' {sex}, {year_of_birth}' 56 ) 57 assert reality == my_expectation 58 59 reality = src.person.say_hello( 60 first_name=first_name, 61 last_name=last_name, 62 year_of_birth=year_of_birth, 63 ) 64 my_expectation = ( 65 f'Hello, my name is {first_name}' 66 f' {last_name} and I am' 67 f' {2026-year_of_birth}.' 68 ) 69 assert reality == my_expectation 70 71 joe = src.person.Person( 72 first_name=first_name, 73 last_name=last_name, 74 sex=sex, 75 year_of_birth=year_of_birth, 76 ) 77 78 reality = joe.say_hello() 79 assert reality == my_expectation 80 81 82def test_jane():I remove the Person class from
test_person.pyimport src.person def test_joe():all the tests are still green because the calls that were made to the Person class that was in
test_person.pyare now made to the Person class inperson.pyin thesrcfolder. Whensrc.person.Personis called with input, Python follows this pathsrc.person.Person src └── person.py └── class Person: └── def __init__( self, first_name, last_name, sex, year_of_birth, ): ├── self.first_name = first_name ├── self.last_name = last_name └── self.year_of_birth = year_of_birthI add a git commit message in the other terminal
git commit -am \ 'move Person class to person.py'the terminal shows a summary of the changes then goes back to the command line.
test_dir_person_class
Python has the dir built-in function which shows the attributes and methods of the object it is given in parentheses. It allows me to see what makes up an object without looking at the code or reading the documentation. I can then run tests to see what each thing does.
RED: make it fail
I go back to the terminal where the tests are running
I add a new test with the dir built-in function in
test_person.py157 mary = src.person.Person( 158 first_name=first_name, 159 last_name=last_name, 160 sex=sex, 161 year_of_birth=year_of_birth, 162 ) 163 164 reality = mary.say_hello() 165 assert reality == my_expectation 166 167 168def test_dir_person_class(): 169 reality = dir(src.person.Person) 170 my_expectation = None 171 assert reality == my_expectation 172 173 174# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: assert ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', ...] == Nonebecause dir returned a list (anything in square brackets
[ ]) andmy_expectationis None.
GREEN: make it pass
I copy (ctrl/command+c) the values from the terminal and paste (ctrl/command+v) them as
my_expectation168def test_dir_person_class(): 169 reality = dir(src.person.Person) 170 # my_expectation = None 171 my_expectation = [ 172 '__class__', '__delattr__', '__dict__', 173 '__dir__', '__doc__', '__eq__', ... 174 ] 175 assert reality == my_expectation 176 177 178# Exceptions seenthe terminal is my friend, and shows AssertionError
E AssertionError: assert ['__class__',...'__eq__', ...] == ['__class__',...'__eq__', ...] E E At index 6 diff: '__firstlineno__' != Ellipsis E Left contains 23 more items, first extra item: '__format__' E Use -v to get more diffI click in the terminal where the tests are running then press v on the keyboard for pytest-watcher to show me more of the difference between
realityandmy_expectationand it shows AssertionErrorE ...Full output truncated (31 lines hidden), use '-vv' to showI press w on the keyboard in the terminal where the tests are running, to show the menu for pytest-watcher and it shows
[pytest-watcher] Current runner args: [-v] Controls: > Enter : Invoke test runner > r : reset all runner args > c : change runner args > f : run only failed tests (--lf) > p : drop to pdb on fail (--pdb) > v : increase verbosity (-v) > e : Erase terminal screen > q : quit pytest-watcherI press c on the keyboard to change runner args, and the terminal shows
[pytest-watcher] Current runner args: [] Controls: > Enter : Invoke test runner > r : reset all runner args > c : change runner args > f : run only failed tests (--lf) > p : drop to pdb on fail (--pdb) > v : increase verbosity (-v) > e : Erase terminal screen > q : quit pytest-watcher Enter new runner args: -vvI type
-vvthen press enter to show the full difference, and the terminal shows AssertionError with the full list.I copy (ctrl/command+c) the values from the terminal and paste (ctrl/command+v) them as
my_expectation168def test_dir_person_class(): 169 reality = dir(src.person.Person) 170 my_expectation = [ 171 '__class__', 172 '__delattr__', 173 '__dict__', 174 '__dir__', 175 '__doc__', 176 '__eq__', 177 '__firstlineno__', 178 '__format__', 179 '__ge__', 180 '__getattribute__', 181 '__getstate__', 182 '__gt__', 183 '__hash__', 184 '__init__', 185 '__init_subclass__', 186 '__le__', 187 '__lt__', 188 '__module__', 189 '__ne__', 190 '__new__', 191 '__reduce__', 192 '__reduce_ex__', 193 '__repr__', 194 '__setattr__', 195 '__sizeof__', 196 '__static_attributes__', 197 '__str__', 198 '__subclasshook__', 199 '__weakref__', 200 'say_hello' 201 ] 202 assert reality == my_expectation 203 204 205# Exceptions seenthe test passes.
The __init__ and say_hello methods I defined are in the list of attributes and methods.
There are names in the list that I did not define, which leads to the question of where did they come from?
The attributes I defined in the __init__ method are not in the list, because the test called dir on
src.person.Personwhich is the class, not an instance of the class.
I add a git commit message in the other terminal
git commit -am \ 'add test_dir_person_class'
Caution
Your list of attributes and methods can be different because of your Python version.
test_dir_person_instance
RED: make it fail
I add a test to see the difference between the attributes and methods of an instance and the actual class
197 '__str__',
198 '__subclasshook__',
199 '__weakref__',
200 'say_hello'
201 ]
202 assert reality == my_expectation
203
204
205 def test_dir_person_instance():
206 an_instance_of_person = src.person.Person(
207 first_name='first_name',
208 last_name='last_name',
209 sex='M',
210 year_of_birth=2026,
211 )
212
213 reality = dir(an_instance_of_person)
214 my_expectation = [
215 '__class__',
216 '__delattr__',
217 '__dict__',
218 '__dir__',
219 '__doc__',
220 '__eq__',
221 '__firstlineno__',
222 '__format__',
223 '__ge__',
224 '__getattribute__',
225 '__getstate__',
226 '__gt__',
227 '__hash__',
228 '__init__',
229 '__init_subclass__',
230 '__le__',
231 '__lt__',
232 '__module__',
233 '__ne__',
234 '__new__',
235 '__reduce__',
236 '__reduce_ex__',
237 '__repr__',
238 '__setattr__',
239 '__sizeof__',
240 '__static_attributes__',
241 '__str__',
242 '__subclasshook__',
243 '__weakref__',
244 'say_hello'
245 ]
246 assert reality == my_expectation
247
248
249 # Exceptions seen
the terminal is my friend, and shows AssertionError
AssertionError:
assert [
'__class__', '__delattr__', '__dict__', '__dir__',
'__doc__', '__eq__', '__firstlineno__', '__format__',
'__ge__', '__getattribute__', '__getstate__', '__gt__',
'__hash__', '__init__', '__init_subclass__', '__le__',
'__lt__', '__module__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__static_attributes__', '__str__', '__subclasshook__',
'__weakref__',
'first_name', 'last_name', 'say_hello', 'year_of_birth'
]
== [
'__class__', '__delattr__', '__dict__', '__dir__',
'__doc__', '__eq__', '__firstlineno__', '__format__',
'__ge__', '__getattribute__', '__getstate__', '__gt__',
'__hash__', '__init__', '__init_subclass__', '__le__',
'__lt__', '__module__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__static_attributes__', '__str__', '__subclasshook__',
'__weakref__', 'say_hello'
]
because first_name, last_name and year_of_birth are missing. Why is there no sex?
GREEN: make it pass
I add the missing attributes to my_expectation
205def test_dir_person_instance():
206 an_instance_of_person = src.person.Person(
207 first_name='first_name',
208 last_name='last_name',
209 sex='M',
210 year_of_birth=2026,
211 )
212
213 reality = dir(an_instance_of_person)
214 my_expectation = [
215 '__class__',
216 '__delattr__',
217 '__dict__',
218 '__dir__',
219 '__doc__',
220 '__eq__',
221 '__firstlineno__',
222 '__format__',
223 '__ge__',
224 '__getattribute__',
225 '__getstate__',
226 '__gt__',
227 '__hash__',
228 '__init__',
229 '__init_subclass__',
230 '__le__',
231 '__lt__',
232 '__module__',
233 '__ne__',
234 '__new__',
235 '__reduce__',
236 '__reduce_ex__',
237 '__repr__',
238 '__setattr__',
239 '__sizeof__',
240 '__static_attributes__',
241 '__str__',
242 '__subclasshook__',
243 '__weakref__',
244 'first_name',
245 'last_name',
246 'say_hello',
247 'year_of_birth',
248 ]
249 assert reality == my_expectation
250
251
252# Exceptions seen
the test passes.
REFACTOR: make it better
I add
sexto the list244 'first_name', 245 'last_name', 246 'say_hello', 247 'sex', 248 'year_of_birth', 249 ] 250 assert reality == my_expectation 251 252 253# Exceptions seen 254# AssertionError 255# NameError 256# TypeError 257# AttributeError 258# SyntaxErrorthe terminal is my friend, and shows AssertionError
AssertionError: assert [ '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', 'first_name', 'last_name', 'say_hello', 'year_of_birth' ] == [ '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', 'first_name', 'last_name', 'say_hello', 'sex', 'year_of_birth' ]the
sexattribute is not defined anywhere in the Person class.I add
self.sexto the __init__ method of the Person class inperson.py1# Person 2# Person = None 3# def Person(): 4# def Person(first_name): 5# def Person(first_name, last_name): 6# def Person(first_name, last_name, sex): 7# def Person( 8# class Person( 9# first_name, last_name, 10# sex, year_of_birth, 11# ): 12class Person: 13 14 # say_hello 15 # say_hello = None 16 17 def __init__( 18 # first_name, last_name, 19 self, first_name, last_name, 20 sex, year_of_birth, 21 ): 22 # return None 23 # pass 24 self.first_name = first_name 25 self.last_name = last_name 26 self.year_of_birth = year_of_birth 27 self.sex = sex 28 29 # def say_hello(): 30 # def say_hello(argument): 31 def say_hello(self): 32 # return None 33 # return 'Hello, my name is mary public and I am 26.' 34 # return argument 35 return ( 36 # f'Hello, my name is {argument.first_name}' 37 # f' {argument.last_name} and I am' 38 # f' {2026-argument.year_of_birth}.' 39 f'Hello, my name is {self.first_name}' 40 f' {self.last_name} and I am' 41 f' {2026-self.year_of_birth}.' 42 ) 43 44 45def say_hello( 46 first_name, last_name, year_of_birth, 47):the test passes.
I remove the commented lines
1class Person: 2 3 def __init__( 4 self, first_name, last_name, 5 sex, year_of_birth, 6 ): 7 self.first_name = first_name 8 self.last_name = last_name 9 self.year_of_birth = year_of_birth 10 self.sex = sex 11 12 def say_hello(self): 13 return ( 14 f'Hello, my name is {self.first_name}' 15 f' {self.last_name} and I am' 16 f' {2026-self.year_of_birth}.' 17 ) 18 19 20def say_hello( 21 first_name, last_name, year_of_birth, 22): 23 return ( 24 f'Hello, my name is {first_name}' 25 f' {last_name} and I am' 26 f' {2026-year_of_birth}.' 27 ) 28 29 30def factory( 31 first_name, last_name, 32 sex, year_of_birth, 33 ): 34 return ( 35 f'{first_name}, {last_name},' 36 f' {sex}, {year_of_birth}' 37 )I add a git commit message in the other terminal
git commit -am \ 'add test_dir_person_instance'
close the project
I close
person.pyandtest_person.pyI click in the terminal where the tests are running
I use q on the keyboard to leave the tests. The terminal goes back to the command line.
I change directory to the parent of
personcd ..the terminal shows
...\pumping_pythonI am back in the
pumping_pythondirectory.
review
I ran tests to write a class that makes a person when given
first_name,last_name,sexandyear_of_birthand has a method so I do not have to pass the same values every time I want to do something with a person.I saw the following Exceptions
My tests have a problem, each test is now the same three tests. There has to be a way that I can use one test for all the people.
code from the chapter
what is next?
Would you like to know where the extra attributes and methods of the Person class came from?
rate pumping python
If this has been a 7 star experience for you, please CLICK HERE to leave a 5 star review of pumping python. It helps other people get into the book too.