functions that take input

To review, a function_ is code that is callable, which means I can write code to do something one time, and call the name for it to do that thing at a different time from when I write it.

functions_ can make code simpler, easier to read, test, reuse, maintain and improve - all the good things.

Part of Computer Programming is sending input data to a process and getting output data back

input_data -> process -> output_data

where process is the function_. I think of it like mapping a function f in Mathematics with inputs x and output y

\[f(x) -> y\]

in other words

                f(x) -> y
function(input_data) -> output_data

the function does something (the process) with input_data and returns output_data as the result.


how to make a function that takes input

functions_ are made with

  • the def keyword

  • a name

  • parentheses and a colon at the end

  • the code that makes up the function (its body) comes after the colon

def name_of_function(input_data):
    the body of the function
    return output_data

preview

I have these tests by the end of the chapter

  1import src.functions
  2import unittest
  3
  4
  5def add_x(number):
  6    # return 2 + number
  7    return 3 + number
  8
  9
 10class TestFunctions(unittest.TestCase):
 11
 12    first = 'first'
 13    last = 'last'
 14    a_tuple = (1, 2, 3, 'n')
 15    a_list = [1, 2, 3, 'n']
 16    first_number = 0
 17    second_number = 1
 18
 19    def test_why_use_a_function(self):
 20        # reality = 1 + 0
 21        # reality = 2 + 0
 22        reality = add_x(0)
 23        # my_expectation = 0
 24        # my_expectation = 1
 25        # my_expectation = 2
 26        my_expectation = 3
 27        self.assertEqual(reality, my_expectation)
 28
 29        # reality = 1 + 1
 30        # reality = 2 + 1
 31        reality = add_x(1)
 32        # my_expectation = 1
 33        # my_expectation = 2
 34        # my_expectation = 3
 35        my_expectation = 4
 36        self.assertEqual(reality, my_expectation)
 37
 38        # reality = 1 + 2
 39        # reality = 2 + 2
 40        reality = add_x(2)
 41        # my_expectation = 2
 42        # my_expectation = 3
 43        # my_expectation = 4
 44        my_expectation = 5
 45        self.assertEqual(reality, my_expectation)
 46
 47        # reality = 1 + 3
 48        # reality = 2 + 3
 49        reality = add_x(3)
 50        # my_expectation = 3
 51        # my_expectation = 4
 52        # my_expectation = 5
 53        my_expectation = 6
 54        self.assertEqual(reality, my_expectation)
 55
 56        # reality = 1 + 4
 57        # reality = 2 + 4
 58        reality = add_x(4)
 59        # my_expectation = 4
 60        # my_expectation = 5
 61        # my_expectation = 6
 62        my_expectation = 7
 63        self.assertEqual(reality, my_expectation)
 64
 65        # reality = 1 + 5
 66        # reality = 2 + 5
 67        reality = add_x(5)
 68        # my_expectation = 5
 69        # my_expectation = 6
 70        # my_expectation = 7
 71        my_expectation = 8
 72        self.assertEqual(reality, my_expectation)
 73
 74        # reality = 1 + 6
 75        # reality = 2 + 6
 76        reality = add_x(6)
 77        # my_expectation = 6
 78        # my_expectation = 7
 79        # my_expectation = 8
 80        my_expectation = 9
 81        self.assertEqual(reality, my_expectation)
 82
 83        # reality = 1 + 7
 84        # reality = 2 + 7
 85        reality = add_x(7)
 86        # my_expectation = 7
 87        # my_expectation = 8
 88        # my_expectation = 9
 89        my_expectation = 10
 90        self.assertEqual(reality, my_expectation)
 91
 92        # reality = 1 + 8
 93        # reality = 2 + 8
 94        reality = add_x(8)
 95        # my_expectation = 8
 96        # my_expectation = 9
 97        # my_expectation = 10
 98        my_expectation = 11
 99        self.assertEqual(reality, my_expectation)
100
101        # reality = 1 + 9
102        # reality = 2 + 9
103        reality = add_x(9)
104        # my_expectation = 9
105        # my_expectation = 10
106        # my_expectation = 11
107        my_expectation = 12
108        self.assertEqual(reality, my_expectation)
109
110    def test_making_a_function_w_pass(self):
111        self.assertIs(src.functions.w_pass(), None)
112
113    def test_making_a_function_w_return(self):
114        self.assertIs(src.functions.w_return(), None)
115
116    def test_making_a_function_w_return_none(self):
117        self.assertIs(
118            src.functions.w_return_none(), None
119        )
120
121    def test_what_happens_after_a_function_returns(self):
122        self.assertIs(
123            src.functions.return_is_last(), None
124        )
125
126    def test_constant_function(self):
127        reality = src.functions.constant()
128        my_expectation = 'the same thing'
129        self.assertEqual(reality, my_expectation)
130
131    def test_identity_function(self):
132        reality = src.functions.identity(None)
133        my_expectation = None
134        self.assertEqual(reality, my_expectation)
135
136        reality = src.functions.identity(object)
137        my_expectation = object
138        self.assertEqual(reality, my_expectation)
139
140    def test_w_positional_arguments(self):
141        reality = src.functions.w_positional_arguments(
142            self.first, self.last,
143        )
144        my_expectation = (self.first, self.last)
145        self.assertEqual(reality, my_expectation)
146
147        reality = src.functions.w_positional_arguments(
148            self.last, self.first,
149        )
150        my_expectation = (self.last, self.first)
151        self.assertEqual(reality, my_expectation)
152
153        reality = src.functions.w_positional_arguments(
154            self.first_number, self.second_number,
155        )
156        my_expectation = (
157            self.first_number, self.second_number
158        )
159        self.assertEqual(reality, my_expectation)
160
161        reality = src.functions.w_positional_arguments(
162            self.a_tuple, self.a_list,
163        )
164        my_expectation = (self.a_tuple, self.a_list)
165        self.assertEqual(reality, my_expectation)
166
167    def test_w_keyword_arguments(self):
168        reality = src.functions.w_keyword_arguments(
169            first_input=self.first,
170            last_input=self.last,
171        )
172        my_expectation = (self.first, self.last)
173        self.assertEqual(reality, my_expectation)
174
175        reality = src.functions.w_keyword_arguments(
176            last_input=self.last,
177            first_input=self.first,
178        )
179        my_expectation = (self.first, self.last)
180        self.assertEqual(reality, my_expectation)
181
182        reality = src.functions.w_keyword_arguments(
183            self.last, self.first,
184        )
185        my_expectation = (self.last, self.first)
186        self.assertEqual(reality, my_expectation)
187
188        reality = src.functions.w_keyword_arguments(
189            last_input=self.first_number,
190            first_input=self.second_number,
191        )
192        my_expectation = (
193            self.second_number, self.first_number
194        )
195        self.assertEqual(reality, my_expectation)
196
197        a_set = {1, 2, 3, 'n'}
198        a_dictionary = {'key': 'value'}
199        reality = src.functions.w_keyword_arguments(
200            first_input=a_set,
201            last_input=a_dictionary,
202        )
203        my_expectation = (a_set, a_dictionary)
204        self.assertEqual(reality, my_expectation)
205
206        reality = src.functions.w_positional_arguments(
207            first_input=self.a_list,
208            last_input=self.a_tuple,
209        )
210        my_expectation = (self.a_list, self.a_tuple)
211        self.assertEqual(reality, my_expectation)
212
213    def test_w_args_and_kwargs(self):
214        reality = (
215            src.functions.w_args_and_kwargs(
216                self.first, last_input=self.last,
217            )
218        )
219        my_expectation = (self.first, self.last)
220        self.assertEqual(reality, my_expectation)
221
222    def test_w_optional_arguments(self):
223        first_name, last_name = 'jane', 'doe'
224        reality = src.functions.w_optional_arguments(
225            first_name,
226        )
227        my_expectation = (first_name, last_name)
228        self.assertEqual(reality, my_expectation)
229
230        first_name, blow = 'joe', 'blow'
231        reality = src.functions.w_optional_arguments(
232            first_name, blow,
233        )
234        my_expectation = (first_name, blow)
235        self.assertEqual(reality, my_expectation)
236
237        first_name = 'john'
238        reality = src.functions.w_optional_arguments(
239            first_input=first_name,
240        )
241        my_expectation = (first_name, last_name)
242        self.assertEqual(reality, my_expectation)
243
244        last_name = 'smith'
245        reality = src.functions.w_optional_arguments(
246            last_input=last_name,
247            first_input=first_name,
248        )
249        my_expectation = (first_name, last_name)
250        self.assertEqual(reality, my_expectation)
251
252    def test_w_unknown_arguments(self):
253        a_tuple = (0, 1)
254        a_dictionary = {'a': 2, 'b': 3}
255        reality = src.functions.w_unknown_arguments(
256            *a_tuple, **a_dictionary
257        )
258        my_expectation = (a_tuple, a_dictionary)
259        self.assertEqual(reality, my_expectation)
260
261        a_tuple = (0, 1)
262        a_dictionary = {'a': 2, 'b': 3, 'c': 4}
263        reality = src.functions.w_unknown_arguments(
264            *a_tuple, **a_dictionary
265        )
266        my_expectation = (
267            a_tuple, a_dictionary
268        )
269        self.assertEqual(reality, my_expectation)
270
271        a_tuple = (0, 1, 2)
272        a_dictionary = {'a': 3, 'b': 4, 'c': 5}
273        reality = src.functions.w_unknown_arguments(
274            *a_tuple, **a_dictionary,
275        )
276        my_expectation = (
277            a_tuple, a_dictionary
278        )
279        self.assertEqual(reality, my_expectation)
280
281        a_tuple = (0, 1, 2, 3)
282        reality = src.functions.w_unknown_arguments(
283            *a_tuple
284        )
285        my_expectation = (a_tuple, {})
286        self.assertEqual(reality, my_expectation)
287
288        a_dictionary = {'a': 4, 'b': 5, 'c': 6, 'd': 7}
289        reality = src.functions.w_unknown_arguments(
290            **a_dictionary
291        )
292        my_expectation = ((), a_dictionary)
293        self.assertEqual(reality, my_expectation)
294
295        reality = src.functions.w_unknown_arguments()
296        my_expectation = ((), {})
297        self.assertEqual(reality, my_expectation)
298
299
300# Exceptions seen
301# AssertionError
302# NameError
303# AttributeError
304# TypeError
305# SyntaxError

questions about functions that take input

Questions to think about as I go through the chapter


open the project

  • I open a terminal

  • I change directory to the project

    cd functions
    

    the terminal shows I am in the functions folder

    .../pumping_python/functions
    
  • I open test_functions.py

  • I use pytest-watcher to run the tests automatically

    uv run pytest-watcher . --now
    

    the terminal shows

    test_functions.py .....                             [100%]
    
    =================== 5 passed in X.YZs ====================
    

test_identity_function

A function can take input, and the simplest thing it can do is return the input as output, it is called the Identity or Passthrough function and is also in the Truth Table chapter in test_logical_identity.


RED: make it fail


I add a test to test_functions.py

30def test_constant_function():
31    def constant():
32        return 'the same thing'
33
34    assert constant() is 'the same thing'
35
36
37def test_identity_function():
38    assert identity() == None
39
40
41# Exceptions seen

the terminal is my friend, and shows NameError

NameError: name 'identity' is not defined

is it because test_functions.py has no identity?


GREEN: make it pass


I add a function for identity

37def test_identity_function():
38    def identity():
39        return None
40
41    assert identity() == None
42
43
44# Exceptions seen

the test passes.


how to call a function with input

I can call a function with input by placing an object in parentheses when I call it.


RED: make it fail


I add input to the function call

37def test_identity_function():
38    def identity():
39        return None
40
41    # assert identity() == None
42    assert identity(None) == None
43
44
45# Exceptions seen

the terminal is my friend, and shows TypeError

TypeError:
    test_identity_function.<locals>.identity()
    takes 0 positional arguments but 1 was given

because

  • I called identity which belongs to test_identity_function with one input (None).

  • The function definition (signature) of identity does not allow any inputs when it is called, since the parentheses are empty.

  • I am violating the function signature when I call it in a way that it was not designed to be called which raises TypeError.


GREEN: make it pass


I add a name in parentheses for the identity function to take input

37def test_identity_function():
38    # def identity():
39    def identity(the_input):
40        return None
41
42    # assert identity() == None
43    assert identity(None) == None
44
45
46# Exceptions seen

the test passes. I am genius.


REFACTOR: make it better


The description for the identity function is that it returns the same thing it is given, this test passes when None is given as input.

Does it pass when another value is given or does it always return None? There is one way to find out

  • I add an assertion to test_identity_function in

    37def test_identity_function():
    38    # def identity():
    39    def identity(the_input):
    40        return None
    41
    42    # assert identity() == None
    43    assert identity(None) == None
    44    assert identity(object) == object
    45
    46
    47# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert None == object
    
  • I make the identity function return what it gets

    37def test_identity_function():
    38    # def identity():
    39    def identity(the_input):
    40        # return None
    41        return the_input
    42
    43    # assert identity() == None
    44    assert identity(None) == None
    45    assert identity(object) == object
    46
    47
    48# Exceptions seen
    

    the test passes.

  • I remove the commented lines

    37def test_identity_function():
    38    def identity(the_input):
    39        return the_input
    40
    41    assert identity(None) == None
    42    assert identity(object) == object
    43
    44
    45# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit --all --message \
    'add test_identity_function'
    

    the terminal shows a summary of the changes then goes back to the command line.

The Identity Function returns its input as output.

I sometimes use the Identity Function when I am testing, to check connections. If I can send something (input) and get it back, I can start making changes to see how it affects the output.


test_why_use_a_function

Why would I use a function when I can just write code to do the thing I want? Let us assume I am writing a program to add up numbers.


RED: make it fail


  • I add a test

    37def test_identity_function():
    38    def identity(the_input):
    39        return the_input
    40
    41    assert identity(None) == None
    42    assert identity(object) == object
    43
    44
    45def test_why_use_a_function():
    46    assert 1 + 0 == 0
    47
    48
    49# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 0) == 0
    

    because 1 + 0 is NOT equal to 0.


GREEN: make it pass


I change the assertion to make it True

45def test_why_use_a_function():
46    # assert 1 + 0 == 0
47    assert 1 + 0 == 1
48
49
50# Exceptions seen

the test passes.


REFACTOR: make it better


  • I add an assertion for 1 + 1

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    assert 1 + 0 == 1
    48    assert 1 + 1 == 1
    49
    50
    51# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 1) == 1
    

    because 1 + 1 is NOT equal to 1.

  • I change the assertion to make it True

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    assert 1 + 0 == 1
    48    # assert 1 + 1 == 1
    49    assert 1 + 1 == 2
    50
    51
    52# Exceptions seen
    

    the test passes.

  • I add an assertion for 1 + 2

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    assert 1 + 0 == 1
    48    # assert 1 + 1 == 1
    49    assert 1 + 1 == 2
    50    assert 1 + 2 == 2
    51
    52
    53# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 2) == 2
    

    because 1 + 2 is NOT equal to 2.

  • I change the assertion to make it True

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    assert 1 + 0 == 1
    48    # assert 1 + 1 == 1
    49    assert 1 + 1 == 2
    50    # assert 1 + 2 == 2
    51    assert 1 + 2 == 3
    52
    53
    54# Exceptions seen
    

    the test passes.

  • I add an assertion for 1 + 3

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    assert 1 + 0 == 1
    48    # assert 1 + 1 == 1
    49    assert 1 + 1 == 2
    50    # assert 1 + 2 == 2
    51    assert 1 + 2 == 3
    52    assert 1 + 3 == 3
    53
    54
    55# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 3) == 3
    

    because 1 + 3 is NOT equal to 3.

  • I change the assertion to make it True

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    assert 1 + 0 == 1
    48    # assert 1 + 1 == 1
    49    assert 1 + 1 == 2
    50    # assert 1 + 2 == 2
    51    assert 1 + 2 == 3
    52    # assert 1 + 3 == 3
    53    assert 1 + 3 == 4
    54
    55
    56# Exceptions seen
    

    the test passes.

  • I add an assertion for 1 + 3

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    assert 1 + 0 == 1
    48    # assert 1 + 1 == 1
    49    assert 1 + 1 == 2
    50    # assert 1 + 2 == 2
    51    assert 1 + 2 == 3
    52    assert 1 + 3 == 3
    53
    54
    55# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 3) == 3
    

    because 1 + 3 is NOT equal to 3.

  • I change the assertion to make it True

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    assert 1 + 0 == 1
    48    # assert 1 + 1 == 1
    49    assert 1 + 1 == 2
    50    # assert 1 + 4 == 2
    51    assert 1 + 4 == 3
    52    # assert 1 + 3 == 3
    53    assert 1 + 3 == 4
    54
    55
    56# Exceptions seen
    

    the test passes.

  • I add an assertion for 1 + 4

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    assert 1 + 0 == 1
    48    # assert 1 + 1 == 1
    49    assert 1 + 1 == 2
    50    # assert 1 + 2 == 2
    51    assert 1 + 2 == 3
    52    # assert 1 + 3 == 3
    53    assert 1 + 3 == 4
    54    assert 1 + 4 == 4
    55
    56
    57# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 4) == 4
    

    because 1 + 4 is NOT equal to 4.

  • I change the assertion to make it True

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    assert 1 + 0 == 1
    48    # assert 1 + 1 == 1
    49    assert 1 + 1 == 2
    50    # assert 1 + 2 == 2
    51    assert 1 + 2 == 3
    52    # assert 1 + 3 == 3
    53    assert 1 + 3 == 4
    54    # assert 1 + 4 == 4
    55    assert 1 + 4 == 5
    56
    57
    58# Exceptions seen
    

    the test passes.

  • I add an assertion for 1 + 5

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    assert 1 + 0 == 1
    48    # assert 1 + 1 == 1
    49    assert 1 + 1 == 2
    50    # assert 1 + 2 == 2
    51    assert 1 + 2 == 3
    52    # assert 1 + 3 == 3
    53    assert 1 + 3 == 4
    54    # assert 1 + 4 == 4
    55    assert 1 + 4 == 5
    56    assert 1 + 5 == 5
    57
    58
    59# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 5) == 5
    

    because 1 + 5 is NOT equal to 5.

  • I change the assertion to make it True

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    assert 1 + 0 == 1
    48    # assert 1 + 1 == 1
    49    assert 1 + 1 == 2
    50    # assert 1 + 2 == 2
    51    assert 1 + 2 == 3
    52    # assert 1 + 3 == 3
    53    assert 1 + 3 == 4
    54    # assert 1 + 4 == 4
    55    assert 1 + 4 == 5
    56    # assert 1 + 5 == 5
    57    assert 1 + 5 == 6
    58
    59
    60# Exceptions seen
    

    the test passes.

  • I add an assertion for 1 + 6

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    assert 1 + 0 == 1
    48    # assert 1 + 1 == 1
    49    assert 1 + 1 == 2
    50    # assert 1 + 2 == 2
    51    assert 1 + 2 == 3
    52    # assert 1 + 3 == 3
    53    assert 1 + 3 == 4
    54    # assert 1 + 4 == 4
    55    assert 1 + 4 == 5
    56    # assert 1 + 5 == 5
    57    assert 1 + 5 == 6
    58    assert 1 + 6 == 6
    59
    60
    61# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 6) == 6
    

    because 1 + 6 is NOT equal to 6.

  • I change the assertion to make it True

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    assert 1 + 0 == 1
    48    # assert 1 + 1 == 1
    49    assert 1 + 1 == 2
    50    # assert 1 + 2 == 2
    51    assert 1 + 2 == 3
    52    # assert 1 + 3 == 3
    53    assert 1 + 3 == 4
    54    # assert 1 + 4 == 4
    55    assert 1 + 4 == 5
    56    # assert 1 + 5 == 5
    57    assert 1 + 5 == 6
    58    # assert 1 + 6 == 6
    59    assert 1 + 6 == 7
    60
    61
    62# Exceptions seen
    

    the test passes.

  • I add an assertion for 1 + 7

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    assert 1 + 0 == 1
    48    # assert 1 + 1 == 1
    49    assert 1 + 1 == 2
    50    # assert 1 + 2 == 2
    51    assert 1 + 2 == 3
    52    # assert 1 + 3 == 3
    53    assert 1 + 3 == 4
    54    # assert 1 + 4 == 4
    55    assert 1 + 4 == 5
    56    # assert 1 + 5 == 5
    57    assert 1 + 5 == 6
    58    # assert 1 + 6 == 6
    59    assert 1 + 6 == 7
    60    assert 1 + 7 == 7
    61
    62
    63# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 7) == 7
    

    because 1 + 7 is NOT equal to 7.

  • I change the assertion to make it True

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    assert 1 + 0 == 1
    48    # assert 1 + 1 == 1
    49    assert 1 + 1 == 2
    50    # assert 1 + 2 == 2
    51    assert 1 + 2 == 3
    52    # assert 1 + 3 == 3
    53    assert 1 + 3 == 4
    54    # assert 1 + 4 == 4
    55    assert 1 + 4 == 5
    56    # assert 1 + 5 == 5
    57    assert 1 + 5 == 6
    58    # assert 1 + 6 == 6
    59    assert 1 + 6 == 7
    60    # assert 1 + 7 == 7
    61    assert 1 + 7 == 8
    62
    63
    64# Exceptions seen
    

    the test passes.

  • I add an assertion for 1 + 8

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    assert 1 + 0 == 1
    48    # assert 1 + 1 == 1
    49    assert 1 + 1 == 2
    50    # assert 1 + 2 == 2
    51    assert 1 + 2 == 3
    52    # assert 1 + 3 == 3
    53    assert 1 + 3 == 4
    54    # assert 1 + 4 == 4
    55    assert 1 + 4 == 5
    56    # assert 1 + 5 == 5
    57    assert 1 + 5 == 6
    58    # assert 1 + 6 == 6
    59    assert 1 + 6 == 7
    60    # assert 1 + 7 == 7
    61    assert 1 + 7 == 8
    62    assert 1 + 8 == 8
    63
    64
    65# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 8) == 8
    

    because 1 + 8 is NOT equal to 8.

  • I change the assertion to make it True

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    assert 1 + 0 == 1
    48    # assert 1 + 1 == 1
    49    assert 1 + 1 == 2
    50    # assert 1 + 2 == 2
    51    assert 1 + 2 == 3
    52    # assert 1 + 3 == 3
    53    assert 1 + 3 == 4
    54    # assert 1 + 4 == 4
    55    assert 1 + 4 == 5
    56    # assert 1 + 5 == 5
    57    assert 1 + 5 == 6
    58    # assert 1 + 6 == 6
    59    assert 1 + 6 == 7
    60    # assert 1 + 7 == 7
    61    assert 1 + 7 == 8
    62    # assert 1 + 8 == 8
    63    assert 1 + 8 == 9
    64
    65
    66# Exceptions seen
    

    the test passes.

  • I add an assertion for 1 + 9

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    assert 1 + 0 == 1
    48    # assert 1 + 1 == 1
    49    assert 1 + 1 == 2
    50    # assert 1 + 2 == 2
    51    assert 1 + 2 == 3
    52    # assert 1 + 3 == 3
    53    assert 1 + 3 == 4
    54    # assert 1 + 4 == 4
    55    assert 1 + 4 == 5
    56    # assert 1 + 5 == 5
    57    assert 1 + 5 == 6
    58    # assert 1 + 6 == 6
    59    assert 1 + 6 == 7
    60    # assert 1 + 7 == 7
    61    assert 1 + 7 == 8
    62    # assert 1 + 8 == 8
    63    assert 1 + 8 == 9
    64    assert 1 + 9 == 9
    65
    66
    67# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 9) == 9
    

    because 1 + 9 is NOT equal to 9.

  • I change the assertion to make it True

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    assert 1 + 0 == 1
    48    # assert 1 + 1 == 1
    49    assert 1 + 1 == 2
    50    # assert 1 + 2 == 2
    51    assert 1 + 2 == 3
    52    # assert 1 + 3 == 3
    53    assert 1 + 3 == 4
    54    # assert 1 + 4 == 4
    55    assert 1 + 4 == 5
    56    # assert 1 + 5 == 5
    57    assert 1 + 5 == 6
    58    # assert 1 + 6 == 6
    59    assert 1 + 6 == 7
    60    # assert 1 + 7 == 7
    61    assert 1 + 7 == 8
    62    # assert 1 + 8 == 8
    63    assert 1 + 8 == 9
    64    # assert 1 + 9 == 9
    65    assert 1 + 9 == 10
    66
    67
    68# Exceptions seen
    

    the test passes.





  • all those assertions test what happens when I add a number to 1. If I want to test what happens when I add a number to 2, I would have to change 1 in 10 places. I change 1 to 2 for the calculation part of the assertions

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    # assert 1 + 0 == 1
    48    assert 2 + 0 == 1
    49    # assert 1 + 1 == 1
    50    # assert 1 + 1 == 2
    51    assert 2 + 1 == 2
    52    # assert 1 + 2 == 2
    53    # assert 1 + 2 == 3
    54    assert 2 + 2 == 3
    55    # assert 1 + 3 == 3
    56    # assert 1 + 3 == 4
    57    assert 2 + 3 == 4
    58    # assert 1 + 4 == 4
    59    # assert 1 + 4 == 5
    60    assert 2 + 4 == 5
    61    # assert 1 + 5 == 5
    62    # assert 1 + 5 == 6
    63    assert 2 + 5 == 6
    64    # assert 1 + 6 == 6
    65    # assert 1 + 6 == 7
    66    assert 2 + 6 == 7
    67    # assert 1 + 7 == 7
    68    # assert 1 + 7 == 8
    69    assert 2 + 7 == 8
    70    # assert 1 + 8 == 8
    71    # assert 1 + 8 == 9
    72    assert 2 + 8 == 9
    73    # assert 1 + 9 == 9
    74    # assert 1 + 9 == 10
    75    assert 2 + 9 == 10
    76
    77
    78# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert (2 + 0) == 1
    
  • I change the result side of each assertion to make them True

    45def test_why_use_a_function():
    46    # assert 1 + 0 == 0
    47    # assert 1 + 0 == 1
    48    # assert 2 + 0 == 1
    49    assert 2 + 0 == 2
    50    # assert 1 + 1 == 1
    51    # assert 1 + 1 == 2
    52    # assert 2 + 1 == 2
    53    assert 2 + 1 == 3
    54    # assert 1 + 2 == 2
    55    # assert 1 + 2 == 3
    56    # assert 2 + 2 == 3
    57    assert 2 + 2 == 4
    58    # assert 1 + 3 == 3
    59    # assert 1 + 3 == 4
    60    # assert 2 + 3 == 4
    61    assert 2 + 3 == 5
    62    # assert 1 + 4 == 4
    63    # assert 1 + 4 == 5
    64    # assert 2 + 4 == 5
    65    assert 2 + 4 == 6
    66    # assert 1 + 5 == 5
    67    # assert 1 + 5 == 6
    68    # assert 2 + 5 == 6
    69    assert 2 + 5 == 7
    70    # assert 1 + 6 == 6
    71    # assert 1 + 6 == 7
    72    # assert 2 + 6 == 7
    73    assert 2 + 6 == 8
    74    # assert 1 + 7 == 7
    75    # assert 1 + 7 == 8
    76    # assert 2 + 7 == 8
    77    assert 2 + 7 == 9
    78    # assert 1 + 8 == 8
    79    # assert 1 + 8 == 9
    80    # assert 2 + 8 == 9
    81    assert 2 + 8 == 10
    82    # assert 1 + 9 == 9
    83    # assert 1 + 9 == 10
    84    # assert 2 + 9 == 10
    85    assert 2 + 9 == 11
    86
    87
    88# Exceptions seen
    

    the test passes.

  • I add a git commit message

    git commit --all --message \
    'add test_why_use_a_function'
    

    the terminal shows a summary of the changes then goes back to the command line.

  • I go back to the terminal where the tests are running


  • What if I want to test what happens when I add 3 to a number? Wait! No more, please! I do not want to have to make a change for each new number, there has to be a better way. I can use a function for the parts that repeat. I add one

    45def test_why_use_a_function():
    46    def add_x(number):
    47        return 2 + number
    48
    49    # assert 1 + 0 == 0
    50    # assert 1 + 0 == 1
    51    # assert 2 + 0 == 1
    52    assert 2 + 0 == 2
    
  • I use the new function for the calculation in the first assertion

    45def test_why_use_a_function():
    46    def add_x(number):
    47        return 2 + number
    48
    49    # assert 1 + 0 == 0
    50    # assert 1 + 0 == 1
    51    # assert 2 + 0 == 1
    52    # assert 2 + 0 == 2
    53    assert add_x(0) == 2
    54    # assert 1 + 1 == 1
    55    # assert 1 + 1 == 2
    56    # assert 2 + 1 == 2
    57    assert 2 + 1 == 3
    

    the test is still green because when I call add_x with a number as input, it returns 2 plus the number as output. Using substitution since I can treat a call to a function as the object it returns

    assert add_x(0) == 2
           # inside add_x
               add_x(number)
               add_x(0)
           # add_x returns 2 + number
             2 + 0  == 2
    

    2 + 0 is equal to 2.

  • I use the add_x function for the other assertions

     45def test_why_use_a_function():
     46    def add_x(number):
     47        return 2 + number
     48
     49    # assert 1 + 0 == 0
     50    # assert 1 + 0 == 1
     51    # assert 2 + 0 == 1
     52    # assert 2 + 0 == 2
     53    assert add_x(0) == 2
     54    # assert 1 + 1 == 1
     55    # assert 1 + 1 == 2
     56    # assert 2 + 1 == 2
     57    # assert 2 + 1 == 3
     58    assert add_x(1) == 3
     59    # assert 1 + 2 == 2
     60    # assert 1 + 2 == 3
     61    # assert 2 + 2 == 3
     62    # assert 2 + 2 == 4
     63    assert add_x(2) == 4
     64    # assert 1 + 3 == 3
     65    # assert 1 + 3 == 4
     66    # assert 2 + 3 == 4
     67    # assert 2 + 3 == 5
     68    assert add_x(3) == 5
     69    # assert 1 + 4 == 4
     70    # assert 1 + 4 == 5
     71    # assert 2 + 4 == 5
     72    # assert 2 + 4 == 6
     73    assert add_x(4) == 6
     74    # assert 1 + 5 == 5
     75    # assert 1 + 5 == 6
     76    # assert 2 + 5 == 6
     77    # assert 2 + 5 == 7
     78    assert add_x(5) == 7
     79    # assert 1 + 6 == 6
     80    # assert 1 + 6 == 7
     81    # assert 2 + 6 == 7
     82    # assert 2 + 6 == 8
     83    assert add_x(6) == 8
     84    # assert 1 + 7 == 7
     85    # assert 1 + 7 == 8
     86    # assert 2 + 7 == 8
     87    # assert 2 + 7 == 9
     88    assert add_x(7) == 9
     89    # assert 1 + 8 == 8
     90    # assert 1 + 8 == 9
     91    # assert 2 + 8 == 9
     92    # assert 2 + 8 == 10
     93    assert add_x(8) == 10
     94    # assert 1 + 9 == 9
     95    # assert 1 + 9 == 10
     96    # assert 2 + 9 == 10
     97    # assert 2 + 9 == 11
     98    assert add_x(9) == 11
     99
    100
    101# Exceptions seen
    

    still green.

  • Now I only have to make a change in one place if I want to test what happens if I add 3 to a number

    45def test_why_use_a_function():
    46    def add_x(number):
    47        # return 2 + number
    48        return 3 + number
    49
    50    # assert 1 + 0 == 0
    51    # assert 1 + 0 == 1
    52    # assert 2 + 0 == 1
    53    # assert 2 + 0 == 2
    54    assert add_x(0) == 2
    

    the terminal is my friend, and shows AssertionError

    E       assert 3 == 2
    
  • I change the results part of the assertions one at a time

     45def test_why_use_a_function():
     46    def add_x(number):
     47        # return 2 + number
     48        return 3 + number
     49
     50    # assert 1 + 0 == 0
     51    # assert 1 + 0 == 1
     52    # assert 2 + 0 == 1
     53    # assert 2 + 0 == 2
     54    # assert add_x(0) == 2
     55    assert add_x(0) == 3
     56    # assert 1 + 1 == 1
     57    # assert 1 + 1 == 2
     58    # assert 2 + 1 == 2
     59    # assert 2 + 1 == 3
     60    # assert add_x(1) == 3
     61    assert add_x(1) == 4
     62    # assert 1 + 2 == 2
     63    # assert 1 + 2 == 3
     64    # assert 2 + 2 == 3
     65    # assert 2 + 2 == 4
     66    # assert add_x(2) == 4
     67    assert add_x(2) == 5
     68    # assert 1 + 3 == 3
     69    # assert 1 + 3 == 4
     70    # assert 2 + 3 == 4
     71    # assert 2 + 3 == 5
     72    # assert add_x(3) == 5
     73    assert add_x(3) == 6
     74    # assert 1 + 4 == 4
     75    # assert 1 + 4 == 5
     76    # assert 2 + 4 == 5
     77    # assert 2 + 4 == 6
     78    # assert add_x(4) == 6
     79    assert add_x(4) == 7
     80    # assert 1 + 5 == 5
     81    # assert 1 + 5 == 6
     82    # assert 2 + 5 == 6
     83    # assert 2 + 5 == 7
     84    # assert add_x(5) == 7
     85    assert add_x(5) == 8
     86    # assert 1 + 6 == 6
     87    # assert 1 + 6 == 7
     88    # assert 2 + 6 == 7
     89    # assert 2 + 6 == 8
     90    # assert add_x(6) == 8
     91    assert add_x(6) == 9
     92    # assert 1 + 7 == 7
     93    # assert 1 + 7 == 8
     94    # assert 2 + 7 == 8
     95    # assert 2 + 7 == 9
     96    # assert add_x(7) == 9
     97    assert add_x(7) == 10
     98    # assert 1 + 8 == 8
     99    # assert 1 + 8 == 9
    100    # assert 2 + 8 == 9
    101    # assert 2 + 8 == 10
    102    # assert add_x(8) == 10
    103    assert add_x(8) == 11
    104    # assert 1 + 9 == 9
    105    # assert 1 + 9 == 10
    106    # assert 2 + 9 == 10
    107    # assert 2 + 9 == 11
    108    # assert add_x(9) == 11
    109    assert add_x(9) == 12
    110
    111
    112# Exceptions seen
    

    the test passes.

  • I remove the commented lines

    45def test_why_use_a_function():
    46    def add_x(number):
    47        return 3 + number
    48
    49    assert add_x(0) == 3
    50    assert add_x(1) == 4
    51    assert add_x(2) == 5
    52    assert add_x(3) == 6
    53    assert add_x(4) == 7
    54    assert add_x(5) == 8
    55    assert add_x(6) == 9
    56    assert add_x(7) == 10
    57    assert add_x(8) == 11
    58    assert add_x(9) == 12
    59
    60
    61# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit --all --message \
    'extract add_x function'
    

    the terminal shows a summary of the changes then goes back to the command line.

  • I can use a function to remove repetition. Is there a better way to handle the changing results?

  • I can use a function to organize tests


test_w_positional_arguments

test_identity_function used one input, these next tests use functions that take more than one input.


RED: make it fail


  • I go back to the terminal where the tests are running

  • I add a test

    58    assert add_x(9) == 12
    59
    60
    61def test_w_positional_arguments():
    62    assert w_positional_arguments() == None
    63
    64
    65# Exceptions seen
    

    the terminal is my friend, and shows NameError

    NameError: name 'w_positional_arguments' is not defined
    

    because …


GREEN: make it pass


I add the function

61def test_w_positional_arguments():
62    def w_positional_arguments():
63        return None
64
65    assert w_positional_arguments() == None
66
67
68# Exceptions seen

the test passes.


REFACTOR: make it better


  • I add input to the function call

    61def test_w_positional_arguments():
    62    def w_positional_arguments():
    63        return None
    64
    65    # assert w_positional_arguments() == None
    66    assert w_positional_arguments('first') == None
    67
    68
    69# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError:
        test_w_positional_arguments.<locals>.w_positional_arguments()
        takes 0 positional arguments but 1 was given
    

    because

    • I called w_positional_arguments which belongs to test_w_positional_arguments with one input ('first').

    • The function definition (signature) of w_positional_arguments does not allow any inputs when it is called, since the parentheses are empty.

    • I am violating the function signature when I call it in a way that it was not designed to be called which raises TypeError.

  • I make the function take input by adding a name in parentheses

    61def test_w_positional_arguments():
    62    # def w_positional_arguments():
    63    def w_positional_arguments(first_input):
    64        return None
    65
    66    # assert w_positional_arguments() == None
    67    assert w_positional_arguments('first') == None
    68
    69
    70# Exceptions seen
    

    the test passes.

  • I add another input to the function call

    61def test_w_positional_arguments():
    62    # def w_positional_arguments():
    63    def w_positional_arguments(first_input):
    64        return None
    65
    66    # assert w_positional_arguments() == None
    67    # assert w_positional_arguments('first') == None
    68    assert w_positional_arguments('first', 'last') == None
    69
    70
    71# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError:
        test_w_positional_arguments.<locals>.w_positional_arguments()
        takes 1 positional arguments but 2 was given
    

    because

    • I called w_positional_arguments which belongs to test_w_positional_arguments with two inputs('first' and 'last').

    • The function definition (signature) of w_positional_arguments only allows one input.

    • I am violating the function signature when I call it in a way that it was not designed to be called which raises TypeError.

  • I make the function take another input by adding a name in parentheses

    61def test_w_positional_arguments():
    62    # def w_positional_arguments():
    63    # def w_positional_arguments(first_input):
    64    def w_positional_arguments(first_input, last_input):
    65        return None
    66
    67    # assert w_positional_arguments() == None
    68    # assert w_positional_arguments('first') == None
    69    assert w_positional_arguments('first', 'last') == None
    70
    71
    72# Exceptions seen
    

    the test passes.

  • I change the expectation of the assertion

    65def test_w_positional_arguments():
    66    # def w_positional_arguments():
    67    # def w_positional_arguments(first_input):
    68    def w_positional_arguments(first_input, last_input):
    69        return None
    70
    71    # assert w_positional_arguments() == None
    72    # assert w_positional_arguments('first') == None
    73    # assert w_positional_arguments('first', 'last') == None
    74    assert (
    75        w_positional_arguments('first', 'last')
    76     == ('first', 'last')
    77    )
    78
    79
    80# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert None == ('first', 'last')
    

    because when I call w_positional_arguments with 'first' and 'last' as inputs, it returns None. Using substitution since I can treat a call to a function as the object it returns

    w_positional_arguments('first'    , 'last'    ) == ('first', 'last')
    w_positional_arguments(first_input, last_input)
        return None
    
    assert w_positional_arguments('first', 'last') == ('first', 'last')
    assert None                                    == ('first', 'last')
    

    which raises AssertionError since None is not a tuple.

  • I change the return statement to make the function return its inputs as output

    61def test_w_positional_arguments():
    62    # def w_positional_arguments():
    63    # def w_positional_arguments(first_input):
    64    def w_positional_arguments(first_input, last_input):
    65        # return None
    66        return first_input, last_input
    67
    68    # assert w_positional_arguments() == None
    69    # assert w_positional_arguments('first') == None
    70    # assert w_positional_arguments('first', 'last') == None
    71    assert (
    72        w_positional_arguments('first', 'last')
    73     == ('first', 'last')
    74    )
    75
    76
    77# Exceptions seen
    

    the test passes, because the function always returns first_input, last_input and the call in the test sends 'first' as first_input and 'last' as last_input.

  • The problem with giving arguments this way is that they always have to be in the order the function expects or I get something different. I add an assertion to show this

    61def test_w_positional_arguments():
    62    # def w_positional_arguments():
    63    # def w_positional_arguments(first_input):
    64    def w_positional_arguments(first_input, last_input):
    65        # return None
    66        return first_input, last_input
    67
    68    # assert w_positional_arguments() == None
    69    # assert w_positional_arguments('first') == None
    70    # assert w_positional_arguments('first', 'last') == None
    71    assert (
    72        w_positional_arguments('first', 'last')
    73     == ('first', 'last')
    74    )
    75    assert (
    76        w_positional_arguments('last', 'first')
    77     == ('first', 'last')
    78    )
    79
    80
    81# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert ('last', 'first') == ('first', 'last')
    

    because the function always returns first_input, last_input and the call in this test sends 'last' as first_input and 'first' as last_input. Using substitution since I can treat a call to a function as the object it returns

    w_positional_arguments('last'     , 'first'   )
    w_positional_arguments(first_input, last_input)
        return first_input, last_input
        return 'last'     , 'first'
    
    assert w_positional_arguments('last', 'first') == ('first', 'last')
    assert ('last', 'first')                       == ('first', 'last')
    
  • I change my expectation to match reality

    61def test_w_positional_arguments():
    62    # def w_positional_arguments():
    63    # def w_positional_arguments(first_input):
    64    def w_positional_arguments(first_input, last_input):
    65        # return None
    66        return first_input, last_input
    67
    68    # assert w_positional_arguments() == None
    69    # assert w_positional_arguments('first') == None
    70    # assert w_positional_arguments('first', 'last') == None
    71    assert (
    72        w_positional_arguments('first', 'last')
    73     == ('first', 'last')
    74    )
    75    assert (
    76        w_positional_arguments('last', 'first')
    77    #  == ('first', 'last')
    78     == ('last', 'first')
    79    )
    80
    81
    82# Exceptions seen
    

    the test passes.

  • I add variables for 'first' and 'last'

    61def test_w_positional_arguments():
    62    # def w_positional_arguments():
    63    # def w_positional_arguments(first_input):
    64    def w_positional_arguments(first_input, last_input):
    65        # return None
    66        return first_input, last_input
    67
    68    # assert w_positional_arguments() == None
    69    # assert w_positional_arguments('first') == None
    70    # assert w_positional_arguments('first', 'last') == None
    71
    72    first, last = 'first', 'last'
    73
    74    assert (
    75        w_positional_arguments('first', 'last')
    76     == ('first', 'last')
    77    )
    78    assert (
    79        w_positional_arguments('last', 'first')
    80    #  == ('first', 'last')
    81     == ('last', 'first')
    82    )
    83
    84
    85# Exceptions seen
    
  • I use the variables to remove repetition of 'first' and 'last'

    61def test_w_positional_arguments():
    62    # def w_positional_arguments():
    63    # def w_positional_arguments(first_input):
    64    def w_positional_arguments(first_input, last_input):
    65        # return None
    66        return first_input, last_input
    67
    68    # assert w_positional_arguments() == None
    69    # assert w_positional_arguments('first') == None
    70    # assert w_positional_arguments('first', 'last') == None
    71
    72    first, last = 'first', 'last'
    73
    74    assert (
    75    #     w_positional_arguments('first', 'last')
    76    #  == ('first', 'last')
    77        w_positional_arguments(first, last)
    78     == (first, last)
    79    )
    80    assert (
    81    #     w_positional_arguments('last', 'first')
    82    #  == ('first', 'last')
    83    #  == ('last', 'first')
    84        w_positional_arguments(last, first)
    85     == (last, first)
    86    )
    87
    88
    89# Exceptions seen
    

    the test is still green.

  • I add another assertion

    61def test_w_positional_arguments():
    62    # def w_positional_arguments():
    63    # def w_positional_arguments(first_input):
    64    def w_positional_arguments(first_input, last_input):
    65        # return None
    66        return first_input, last_input
    67
    68    # assert w_positional_arguments() == None
    69    # assert w_positional_arguments('first') == None
    70    # assert w_positional_arguments('first', 'last') == None
    71
    72    first, last = 'first', 'last'
    73
    74    assert (
    75    #     w_positional_arguments('first', 'last')
    76    #  == ('first', 'last')
    77        w_positional_arguments(first, last)
    78     == (first, last)
    79    )
    80    assert (
    81    #     w_positional_arguments('last', 'first')
    82    #  == ('first', 'last')
    83    #  == ('last', 'first')
    84        w_positional_arguments(last, first)
    85     == (last, first)
    86    )
    87    assert (
    88        w_positional_arguments(0, 1)
    89     == (1, 0)
    90    )
    91
    92
    93# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert (0, 1) == (1, 0)
    

    because the function always returns first_input, last_input and the call in this assertion sends 0 as first_input and 1 as last_input. Using substitution

    w_positional_arguments(0          , 1         )
    w_positional_arguments(first_input, last_input)
        return first_input, last_input
        return 0          , 1
    
    assert w_positional_arguments(0, 1) == (1, 0)
    assert (0, 1)                       == (1, 0)
    
  • I change my expectation to match reality

    74    assert (
    75    #     w_positional_arguments('first', 'last')
    76    #  == ('first', 'last')
    77        w_positional_arguments(first, last)
    78     == (first, last)
    79    )
    80    assert (
    81    #     w_positional_arguments('last', 'first')
    82    #  == ('first', 'last')
    83    #  == ('last', 'first')
    84        w_positional_arguments(last, first)
    85     == (last, first)
    86    )
    87    assert (
    88        w_positional_arguments(0, 1)
    89    #  == (1, 0)
    90     == (0, 1)
    91    )
    92
    93
    94# Exceptions seen
    

    the test passes.

  • I add an assertion with a tuple (anything in parentheses ( ) separated by a comma) and a list(anything in square brackets ‘[ ]’)

     74    assert (
     75    #     w_positional_arguments('first', 'last')
     76    #  == ('first', 'last')
     77        w_positional_arguments(first, last)
     78     == (first, last)
     79    )
     80    assert (
     81    #     w_positional_arguments('last', 'first')
     82    #  == ('first', 'last')
     83    #  == ('last', 'first')
     84        w_positional_arguments(last, first)
     85     == (last, first)
     86    )
     87    assert (
     88        w_positional_arguments(0, 1)
     89    #  == (1, 0)
     90     == (0, 1)
     91    )
     92
     93    a_tuple = (1, 2, 3, 'n')
     94    a_list = [1, 2, 3, 'n']
     95    assert (
     96        w_positional_arguments(a_list, a_tuple)
     97     == (a_tuple, a_list)
     98    )
     99
    100
    101# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        assert ([1, 2, 3, 'n...1, 2, 3, 'n'))
            == ((1, 2, 3, 'n...1, 2, 3, 'n'])
    

    because the function always returns first_input, last_input and the call in this test sends (1, 2, 3, 'n') as first_input and [1, 2, 3, 'n'] as last_input. Using substitution

    a_tuple = (1, 2, 3, 'n')
    a_list = [1, 2, 3, 'n']
    
    w_positional_arguments(a_list        , a_tuple       )
    w_positional_arguments([1, 2, 3, 'n'], (1, 2, 3, 'n'))
        return first_input   , last_input
        return [1, 2, 3, 'n'], (1, 2, 3, 'n')
    
    assert w_positional_arguments(a_list, a_tuple)
                              == (a_tuple, a_list)
    assert ([1, 2, 3, 'n'], (1, 2, 3, 'n'))
        == ((1, 2, 3, 'n'), [1, 2, 3, 'n'])
    
  • I change reality to match my expectation

     93    a_tuple = (1, 2, 3, 'n')
     94    a_list = [1, 2, 3, 'n']
     95    assert (
     96        # w_positional_arguments(a_list, a_tuple)
     97        w_positional_arguments(a_tuple, a_list)
     98     == (a_tuple, a_list)
     99    )
    100
    101
    102# Exceptions seen
    

    the test passes.

  • I remove the commented lines

    61def test_w_positional_arguments():
    62    def w_positional_arguments(first_input, last_input):
    63        return first_input, last_input
    64
    65    first, last = 'first', 'last'
    66
    67    assert (
    68        w_positional_arguments(first, last)
    69     == (first, last)
    70    )
    71    assert (
    72        w_positional_arguments(last, first)
    73     == (last, first)
    74    )
    75    assert (
    76        w_positional_arguments(0, 1)
    77     == (0, 1)
    78    )
    79
    80    a_tuple = (1, 2, 3, 'n')
    81    a_list = [1, 2, 3, 'n']
    82    assert (
    83        w_positional_arguments(a_tuple, a_list)
    84     == (a_tuple, a_list)
    85    )
    86
    87
    88# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit --all --message \
    'add test_w_positional_arguments'
    

    the terminal shows a summary of the changes then goes back to the command line.

I can call functions with positional arguments


test_w_keyword_arguments

The tests show that positional arguments must always be given in the right order. What if I forget the order? What if there are many inputs?

I can use `Keyword Arguments`_ to make sure the function always gets the values for the inputs it expects, that way it does what I want even when I send inputs out of order.


RED: make it fail


  • I go back to the terminal where the tests are running

  • I add a new test to test_functions.py

    155        a_tuple = (1, 2, 3, 'n')
    156        a_list = [1, 2, 3, 'n']
    157        reality = src.functions.w_positional_arguments(
    158            a_tuple, a_list,
    159        )
    160        my_expectation = (a_tuple, a_list)
    161        self.assertEqual(reality, my_expectation)
    162
    163    def test_w_keyword_arguments(self):
    164        reality = src.functions.w_keyword_arguments(
    165            first_input='first', last_input='last',
    166        )
    167        my_expectation = ('first', 'last')
    168        self.assertEqual(reality, my_expectation)
    169
    170
    171# Exceptions seen
    

    the terminal is my friend, and shows AttributeError

    AttributeError: module 'src.functions'
                    has no attribute 'w_keyword_arguments'
    

    because functions.py in the src folder does not have a definition for w_keyword_arguments


GREEN: make it pass


  • I add a function definition to functions.py

    26def w_positional_arguments(first_input, last_input):
    27    return first_input, last_input
    28
    29
    30def w_keyword_arguments():
    31    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: w_keyword_arguments() got
               an unexpected keyword argument 'first_input'
    

    because the definition for w_keyword_arguments does not allow inputs and the test uses two in the call (first_input and last_input)

  • I add the name of the unexpected argument_ in parentheses

    30# def w_keyword_arguments():
    31def w_keyword_arguments(first_input):
    32    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: w_keyword_arguments() got
               an unexpected keyword argument 'last_input'.
               Did you mean 'first_input'?
    

    because the definition for w_keyword_arguments allows one input (first_input) and the test uses two in the call (first_input and last_input)

  • I add a name for the second argument_ in parentheses

    30# def w_keyword_arguments():
    31# def w_keyword_arguments(first_input):
    32def w_keyword_arguments(first_input, second_input):
    33    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: w_keyword_arguments() got
               an unexpected keyword argument 'last_input'.
               Did you mean 'first_input'?
    

    because the definition for w_keyword_arguments allows two inputs with the names first_input and second_input, and the test calls the function with first_input and last_input, the names must match when I am using keyword arguments

  • I change the name of the second argument to match the name used in the call

    30# def w_keyword_arguments():
    31# def w_keyword_arguments(first_input):
    32# def w_keyword_arguments(first_input, second_input):
    33def w_keyword_arguments(first_input, last_input):
    34    return None
    

    the terminal is my friend, and shows AssertionError

    AssertionError: None != ('first', 'last')
    

    because the function returns None and the assertion expects ('first', 'last')

  • I change the `return statement`_ to make the function return its inputs

    30# def w_keyword_arguments():
    31# def w_keyword_arguments(first_input):
    32# def w_keyword_arguments(first_input, second_input):
    33def w_keyword_arguments(first_input, last_input):
    34    # return None
    35    return first_input, last_input
    

    the test passes.


REFACTOR: make it better


  • I remove the commented lines

    26def w_positional_arguments(first_input, last_input):
    27    return first_input, last_input
    28
    29
    30def w_keyword_arguments(first_input, last_input):
    31    return first_input, last_input
    
  • I add another assertion with the `keyword arguments`_ given out of order in test_w_keyword_arguments in test_functions.py

    163    def test_w_keyword_arguments(self):
    164        reality = src.functions.w_keyword_arguments(
    165            first_input='first', last_input='last',
    166        )
    167        my_expectation = ('first', 'last')
    168        self.assertEqual(reality, my_expectation)
    169
    170        reality = src.functions.w_keyword_arguments(
    171            last_input='last', first_input='first',
    172        )
    173        my_expectation = ('last', 'first')
    174        self.assertEqual(reality, my_expectation)
    175
    176
    177# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: Tuples differ: ('first', 'last')
                                != ('last', 'first')
    

    the order the function returns the values stayed the same this time, because it always returns first_input, last_input.

    Compare this call that uses positional arguments and its result

    src.functions.w_positional_arguments('last', 'first')
    ('last', 'first')
    

    with this call that uses keyword arguments and its result

    src.functions.w_keyword_arguments(
        last_input='last', first_input='first',
    )
    ('first', 'last')
    

    in both cases the function returns first_input, last_input

  • I change my_expectation to match reality

    170        reality = src.functions.w_keyword_arguments(
    171            last_input='last', first_input='first',
    172        )
    173        # my_expectation = ('last', 'first')
    174        my_expectation = ('first', 'last')
    175        self.assertEqual(reality, my_expectation)
    176
    177
    178# Exceptions seen
    

    the test passes. The order does not matter when I use `keyword arguments`_.

  • I add variables to use them to remove repetition of 'first' and 'last' from the test

    163    def test_w_keyword_arguments(self):
    164        first, last = 'first', 'last'
    165
    166        reality = src.functions.w_keyword_arguments(
    167            first_input='first', last_input='last',
    168        )
    169        my_expectation = ('first', 'last')
    170        self.assertEqual(reality, my_expectation)
    
  • I use the new variables to remove repetition of 'first' and 'last' from the test

    163    def test_w_keyword_arguments(self):
    164        first, last = 'first', 'last'
    165
    166        reality = src.functions.w_keyword_arguments(
    167            # first_input='first', last_input='last',
    168            first_input=first, last_input=last,
    169        )
    170        # my_expectation = ('first', 'last')
    171        my_expectation = (first, last)
    172        self.assertEqual(reality, my_expectation)
    173
    174        reality = src.functions.w_keyword_arguments(
    175            # last_input='last', first_input='first',
    176            last_input=last, first_input=first,
    177        )
    178        # my_expectation = ('last', 'first')
    179        # my_expectation = ('first', 'last')
    180        my_expectation = (first, last)
    181        self.assertEqual(reality, my_expectation)
    182
    183
    184# Exceptions seen
    

    the test is still green.

  • I can still call the function the same way I did in test_w_positional_arguments (without using the names). I add an assertion to show this

    174        reality = src.functions.w_keyword_arguments(
    175            # last_input='last', first_input='first',
    176            last_input=last, first_input=first,
    177        )
    178        # my_expectation = ('last', 'first')
    179        # my_expectation = ('first', 'last')
    180        my_expectation = (first, last)
    181        self.assertEqual(reality, my_expectation)
    182
    183        reality = src.functions.w_keyword_arguments(
    184            last, first,
    185        )
    186        my_expectation = (first, last)
    187        self.assertEqual(reality, my_expectation)
    188
    189
    190# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: Tuples differ: ('last', 'first')
                                != ('first', 'last')
    

    because the function uses the order (positions) when I do not use the names

  • I change my_expectation to match reality

    183        reality = src.functions.w_keyword_arguments(
    184            last, first,
    185        )
    186        # my_expectation = (first, last)
    187        my_expectation = (last, first)
    188        self.assertEqual(reality, my_expectation)
    189
    190
    191# Exceptions seen
    

    the test passes.

  • I add another assertion

    183        reality = src.functions.w_keyword_arguments(
    184            last, first,
    185        )
    186        # my_expectation = (first, last)
    187        my_expectation = (last, first)
    188        self.assertEqual(reality, my_expectation)
    189
    190        zero, one = 0, 1
    191        reality = src.functions.w_keyword_arguments(
    192            last_input=zero, first_input=one,
    193        )
    194        my_expectation = (zero, one)
    195        self.assertEqual(reality, my_expectation)
    196
    197
    198# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: Tuples differ: (1, 0) != (0, 1)
    
  • I change my_expectation to match reality

    190        zero, one = 0, 1
    191        reality = src.functions.w_keyword_arguments(
    192            last_input=zero, first_input=one,
    193        )
    194        # my_expectation = (zero, one)
    195        my_expectation = (one, zero)
    196        self.assertEqual(reality, my_expectation)
    197
    198
    199# Exceptions seen
    

    the test passes.

  • I add an assertion

    190        zero, one = 0, 1
    191        reality = src.functions.w_keyword_arguments(
    192            last_input=zero, first_input=one,
    193        )
    194        # my_expectation = (zero, one)
    195        my_expectation = (one, zero)
    196        self.assertEqual(reality, my_expectation)
    197
    198        a_set = {1, 2, 3, 'n'}
    199        a_dictionary = {'key': 'value'}
    200        reality = src.functions.w_keyword_arguments(
    201            first_input=a_dictionary,
    202            last_input=a_set,
    203        )
    204        my_expectation = (a_set, a_dictionary)
    205        self.assertEqual(reality, my_expectation)
    206
    207
    208# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: Tuples differ:
            ({'key': 'value'}, {1, 2, 3, 'n'})
         != ({1, 2, 3, 'n'}, {'key': 'value'})
    
  • I change reality to match my_expectation

    198        a_set = {1, 2, 3, 'n'}
    199        a_dictionary = {'key': 'value'}
    200        reality = src.functions.w_keyword_arguments(
    201            # first_input=a_dictionary,
    202            # last_input=a_set,
    203            first_input=a_set,
    204            last_input=a_dictionary,
    205        )
    206        my_expectation = (a_set, a_dictionary)
    207        self.assertEqual(reality, my_expectation)
    208
    209
    210# Exceptions seen
    

    the test passes.

  • w_keyword_arguments and w_positional_arguments are the same functions_, they always

    return first_input, last_input
    

    Their names are different

    def w_positional_arguments(first_input, last_input):
    def w_keyword_arguments(first_input, last_input):
    

    The difference that matters in the tests is how I call the functions_

    • I have to give the input in order when I use positional arguments

      w_positional_arguments('first', 'last')
                     return ('first', 'last')
      
      w_positional_arguments('last', 'first')
                     return ('last', 'first')
      
      w_positional_arguments(0, 1)
                     return (0, 1)
      
      w_positional_arguments((1, 2, 3, 'n'), [1, 2, 3, 'n'])
                     return ((1, 2, 3, 'n'), [1, 2, 3, 'n'])
      
      w_keyword_arguments('last', 'first')
                  return ('last', 'first')
      
    • I can give the input in any order when I use `keyword arguments`_ because I use the names from the function definition when I call it

      w_keyword_arguments(
          first_input='first', last_input='last',
      )
          return ('first', 'last')
      
      w_keyword_arguments(
          last_input='last', first_input='first',
      )
          return ('first', 'last')
      
      w_keyword_arguments(last_input=0, first_input=1,)
                  return (1, 0)
      
      w_keyword_arguments(
          first_input={'key': 'value'},
          last_input={1, 2, 3, 'n'},
      )
          return ({'key': 'value'}, {1, 2, 3, 'n'})
      

    I add another assertion to show that the two functions are the same, by calling the w_positional_arguments function with keyword arguments

    198        a_set = {1, 2, 3, 'n'}
    199        a_dictionary = {'key': 'value'}
    200        reality = src.functions.w_keyword_arguments(
    201            # first_input=a_dictionary,
    202            # last_input=a_set,
    203            first_input=a_set,
    204            last_input=a_dictionary,
    205        )
    206        my_expectation = (a_set, a_dictionary)
    207        self.assertEqual(reality, my_expectation)
    208
    209        a_tuple = (1, 2, 3, 'n')
    210        a_list = [1, 2, 3, 'n']
    211        reality = src.functions.w_positional_arguments(
    212            first_input=a_tuple,
    213            last_input=a_list,
    214        )
    215        my_expectation = (a_list, a_tuple)
    216        self.assertEqual(reality, my_expectation)
    217
    218
    219# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: Tuples differ:
            ((1, 2, 3, 'n'), [1, 2, 3, 'n'])
         != ([1, 2, 3, 'n'], (1, 2, 3, 'n'))
    

    because these two calls are the same

    src.functions.w_positional_arguments(
        first_input=a_tuple,
        last_input=a_list,
    )
    src.functions.w_positional_arguments(
        a_tuple,
        a_list,
    )
    
  • I change reality to match my expectation

    209        a_tuple = (1, 2, 3, 'n')
    210        a_list = [1, 2, 3, 'n']
    211        reality = src.functions.w_positional_arguments(
    212            # first_input=a_tuple,
    213            # last_input=a_list,
    214            first_input=a_list,
    215            last_input=a_tuple,
    216        )
    217        my_expectation = (a_list, a_tuple)
    218        self.assertEqual(reality, my_expectation)
    219
    220
    221# Exceptions seen
    

    the test passes.

  • I remove the commented lines

    163    def test_w_keyword_arguments(self):
    164        first, last = 'first', 'last'
    165
    166        reality = src.functions.w_keyword_arguments(
    167            first_input=first, last_input=last,
    168        )
    169        my_expectation = (first, last)
    170        self.assertEqual(reality, my_expectation)
    171
    172        reality = src.functions.w_keyword_arguments(
    173            last_input=last, first_input=first,
    174        )
    175        my_expectation = (first, last)
    176        self.assertEqual(reality, my_expectation)
    177
    178        reality = src.functions.w_keyword_arguments(
    179            last, first,
    180        )
    181        my_expectation = (last, first)
    182        self.assertEqual(reality, my_expectation)
    183
    184        zero, one = 0, 1
    185        reality = src.functions.w_keyword_arguments(
    186            last_input=zero, first_input=one,
    187        )
    188        my_expectation = (one, zero)
    189        self.assertEqual(reality, my_expectation)
    190
    191        a_set = {1, 2, 3, 'n'}
    192        a_dictionary = {'key': 'value'}
    193        reality = src.functions.w_keyword_arguments(
    194            first_input=a_set,
    195            last_input=a_dictionary,
    196        )
    197        my_expectation = (a_set, a_dictionary)
    198        self.assertEqual(reality, my_expectation)
    199
    200        a_tuple = (1, 2, 3, 'n')
    201        a_list = [1, 2, 3, 'n']
    202        reality = src.functions.w_positional_arguments(
    203            first_input=a_list,
    204            last_input=a_tuple,
    205        )
    206        my_expectation = (a_list, a_tuple)
    207        self.assertEqual(reality, my_expectation)
    208
    209
    210# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit --all --message \
    'add test_w_keyword_arguments'
    

    the terminal shows a summary of the changes then goes back to the command line.

I can call a function with keyword arguments


test_w_args_and_kwargs

I can write functions_ that take both positional and keyword arguments, which is useful when I want some arguments to be required and some to be optional.


RED: make it fail


  • I go back to the terminal where the tests are running

  • I add a failing test to test_functions.py

    200        a_tuple = (1, 2, 3, 'n')
    201        a_list = [1, 2, 3, 'n']
    202        reality = src.functions.w_positional_arguments(
    203            first_input=a_list,
    204            last_input=a_tuple,
    205        )
    206        my_expectation = (a_list, a_tuple)
    207        self.assertEqual(reality, my_expectation)
    208
    209    def test_w_args_and_kwargs(self):
    210        reality = (
    211            src.functions.w_args_and_kwargs(
    212                last_input='last', 'first',
    213            )
    214        )
    215        my_expectation = ('first', 'last')
    216        self.assertEqual(reality, my_expectation)
    217
    218
    219# Exceptions seen
    

    the terminal is my friend, and shows SyntaxError

    SyntaxError: positional argument follows keyword argument
    

    because I cannot put keyword arguments before positional arguments. The Integrated Development Environment (IDE) also shows the error with a red squiggly line under 'first'.


GREEN: make it pass


  • I add SyntaxError to the list of Exceptions seen, in test_functions.py

    217# Exceptions seen
    218# AssertionError
    219# NameError
    220# AttributeError
    221# TypeError
    222# SyntaxError
    
  • I change the order of the arguments to follow Python rules

    209    def test_w_args_and_kwargs(self):
    210        reality = (
    211            src.functions.w_args_and_kwargs(
    212                # last_input='last', 'first',
    213                'first', last_input='last',
    214            )
    215        )
    216        my_expectation = ('first', 'last')
    217        self.assertEqual(reality, my_expectation)
    218
    219
    220# Exceptions seen
    

    the terminal is my friend, and shows AttributeError

    AttributeError: module 'src.functions'
                    has no attribute 'w_args_and_kwargs'
    

    because functions.py does not have anything named w_args_and_kwargs

  • I add a function to functions.py

    30def w_keyword_arguments(first_input, last_input):
    31    return first_input, last_input
    32
    33
    34def w_args_and_kwargs():
    35    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: w_args_and_kwargs() got
               an unexpected keyword argument 'last_input'
    

    because the definition for w_args_and_kwargs does not allow inputs and the test called the function with a keyword argument (last_input)

  • I add the name to the function definition in parentheses, in functions.py

    34# def w_args_and_kwargs():
    35def w_args_and_kwargs(last_input):
    36    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: w_args_and_kwargs() got
               multiple values for argument 'last_input'
    

    because the definition for w_args_and_kwargs takes one argument, and the test calls the function with two arguments ('first', last_input='last'). How does Python know which value to use for last_input if I use the position and the keyword?

  • I add another name in parentheses to make it clearer

    34# def w_args_and_kwargs():
    35# def w_args_and_kwargs(last_input):
    36def w_args_and_kwargs(last_input, first_input):
    37    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: w_args_and_kwargs() got
               multiple values for argument 'last_input'
    

    because I have not fixed the problem, I gave confusing values in the call. Python still cannot tell the difference between the two values because I gave a positional value which according to the function definition is last_input and I gave a value with the name last_input.

  • How does it know what value to use for last_input? The call tells it that the values for last_input are both 'first' and 'last', it would be like defining the function with the same name twice

    34# def w_args_and_kwargs():
    35# def w_args_and_kwargs(last_input):
    36# def w_args_and_kwargs(last_input, first_input):
    37def w_args_and_kwargs(last_input, last_input):
    38    return None
    

    the terminal is my friend, and shows SyntaxError

    SyntaxError: duplicate argument 'last_input'
                 in function definition
    
  • I change the names back and put them in the right order

    34# def w_args_and_kwargs():
    35# def w_args_and_kwargs(last_input):
    36# def w_args_and_kwargs(last_input, first_input):
    37# def w_args_and_kwargs(last_input, last_input):
    38def w_args_and_kwargs(first_input, last_input):
    39    return None
    

    the terminal is my friend, and shows AssertionError

    AssertionError: None != ('first', 'last')
    

    because the function returns None and the assertion expects ('first', 'last')

  • I change the `return statement`_ to give the test what it wants

    34def w_args_and_kwargs(first_input, last_input):
    35    return first_input, last_input
    

    the test passes.

  • I remove the commented lines

    30def w_keyword_arguments(first_input, last_input):
    31    return first_input, last_input
    32
    33
    34def w_args_and_kwargs(first_input, last_input):
    35    return first_input, last_input
    
  • I add variables to use them to remove the repetition of 'first' and 'last' from test_w_args_and_kwargs in test_functions.py

    209    def test_w_args_and_kwargs(self):
    210        first, last = 'first', 'last'
    211        reality = (
    212            src.functions.w_args_and_kwargs(
    213                # last_input='last', 'first',
    214                'first', last_input='last',
    215            )
    216        )
    217        my_expectation = ('first', 'last')
    218        self.assertEqual(reality, my_expectation)
    219
    220
    221# Exceptions seen
    
  • I use the new variables to remove repetition of 'first' and 'last'

    209    def test_w_args_and_kwargs(self):
    210        first, last = 'first', 'last'
    211        reality = (
    212            src.functions.w_args_and_kwargs(
    213                # last_input='last', 'first',
    214                # 'first', last_input='last',
    215                first, last_input=last,
    216            )
    217        )
    218        # my_expectation = ('first', 'last')
    219        my_expectation = (first, last)
    220        self.assertEqual(reality, my_expectation)
    221
    222
    223# Exceptions seen
    

    the test is still green.

  • I remove the commented lines

    209    def test_w_args_and_kwargs(self):
    210        first, last = 'first', 'last'
    211        reality = (
    212            src.functions.w_args_and_kwargs(
    213                first, last_input=last,
    214            )
    215        )
    216        my_expectation = (first, last)
    217        self.assertEqual(reality, my_expectation)
    218
    219
    220# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit --all --message \
    'add test_w_args_and_kwargs'
    

    the terminal shows a summary of the changes then goes back to the command line.

I can call a function with positional and keyword arguments


test_w_optional_arguments

I can use positional and keyword arguments when I want some arguments to be required and some to be optional.


RED: make it fail


  • I go back to the terminal where the tests are running

  • I add a failing test to test_functions.py

    209    def test_w_args_and_kwargs(self):
    210        first, last = 'first', 'last'
    211        reality = (
    212            src.functions.w_args_and_kwargs(
    213                first, last_input=last,
    214            )
    215        )
    216        my_expectation = (first, last)
    217        self.assertEqual(reality, my_expectation)
    218
    219    def test_w_optional_arguments(self):
    220        first_name, last_name = 'jane', 'doe'
    221        reality = src.functions.w_optional_arguments(
    222            first_name, last_input=last_name,
    223        )
    224        my_expectation = (first_name, last_name)
    225        self.assertEqual(reality, my_expectation)
    226
    227
    228# Exceptions seen
    

    the terminal is my friend, and shows AttributeError

    AttributeError: module 'src.functions'
                    has no attribute 'w_optional_arguments'.
                    Did you mean: 'w_positional_arguments'?
    

    because functions.py does not have a definition for w_optional_arguments


GREEN: make it pass


I add a function named w_optional_arguments to functions.py

34  def w_args_and_kwargs(first_input, last_input):
35      return first_input, last_input
36
37
38  def w_optional_arguments(first_input, last_input):
39      return first_input, last_input

the test passes.


REFACTOR: make it better


  • I remove last_input=last_name from the call to w_optional_arguments in test_functions.py

    219    def test_w_optional_arguments(self):
    220        first_name, last_name = 'jane', 'doe'
    221        reality = src.functions.w_optional_arguments(
    222            # first_name, last_input=last_name,
    223            first_name,
    224        )
    225        my_expectation = (first_name, last_name)
    226        self.assertEqual(reality, my_expectation)
    227
    228
    229# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError: w_optional_arguments() missing 1
               required positional argument: 'last_input'
    

    because the last_input argument MUST be given when this function is called (it is required).

  • I make the argument optional by giving it a default value in functions.py

    38# def w_optional_arguments(first_input, last_input):
    39def w_optional_arguments(first_input, last_input='doe'):
    40    return first_input, last_input
    

    the test passes since I do not need to give a value for the last_input parameter in the call to src.functions.w_optional_arguments because the default value for the last_input parameter of the function is doe. This means that

    src.functions.w_optional_arguments('jane')
    

    is the same as

    src.functions.w_optional_arguments('jane', last_input='doe')
    

    is the same as

    return 'jane', 'doe'
    

    because w_optional_arguments will always

    return first_input, last_input
    

    A function uses the default value for a parameter when it is called without the parameter.

  • I remove the commented lines

  • I add another assertion to show that I can still call the function with different values, in test_w_optional_arguments in test_functions.py

    219    def test_w_optional_arguments(self):
    220        first_name, last_name = 'jane', 'doe'
    221        reality = src.functions.w_optional_arguments(
    222            # first_name, last_input=last_name,
    223            first_name,
    224        )
    225        my_expectation = (first_name, last_name)
    226        self.assertEqual(reality, my_expectation)
    227
    228        first_name, blow = 'joe', 'blow'
    229        reality = src.functions.w_optional_arguments(
    230            first_name, blow,
    231        )
    232        my_expectation = ()
    233        self.assertEqual(reality, my_expectation)
    234
    235
    236# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: Tuples differ: ('joe', 'blow') != ()
    
  • I change my_expectation to match reality

    228        first_name, blow = 'joe', 'blow'
    229        reality = src.functions.w_optional_arguments(
    230            first_name, blow,
    231        )
    232        # my_expectation = ()
    233        my_expectation = (first_name, blow)
    234        self.assertEqual(reality, my_expectation)
    235
    236
    237# Exceptions seen
    

    the test passes.

  • I add another assertion

    228        first_name, blow = 'joe', 'blow'
    229        reality = src.functions.w_optional_arguments(
    230            first_name, blow,
    231        )
    232        # my_expectation = ()
    233        my_expectation = (first_name, blow)
    234        self.assertEqual(reality, my_expectation)
    235
    236        first_name = 'john'
    237        reality = src.functions.w_optional_arguments(
    238            first_input=first_name,
    239        )
    240        my_expectation = ()
    241        self.assertEqual(reality, my_expectation)
    242
    243
    244# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: Tuples differ: ('john', 'doe') != ()
    
  • I change my_expectation to match reality

    236        first_name = 'john'
    237        reality = src.functions.w_optional_arguments(
    238            first_input=first_name,
    239        )
    240        # my_expectation = ()
    241        my_expectation = (first_name, last_name)
    242        self.assertEqual(reality, my_expectation)
    243
    244
    245# Exceptions seen
    

    the test passes since I do not need to give a value for the last_input parameter in the call to src.functions.w_optional_arguments because the default value for the last_input parameter of the w_optional_arguments function is doe. This means that

    src.functions.w_optional_arguments(first_input='john')
    

    is the same as

    src.functions.w_optional_arguments(
        first_input='john', last_input='doe',
    )
    

    is the same as

    return 'john', 'doe'
    

    because w_optional_arguments will always

    return first_input, last_input
    

    A function uses the default value for a parameter when it is called without the parameter.

  • I add one more assertion

    236        first_name = 'john'
    237        reality = src.functions.w_optional_arguments(
    238            first_input=first_name,
    239        )
    240        # my_expectation = ()
    241        my_expectation = (first_name, last_name)
    242        self.assertEqual(reality, my_expectation)
    243
    244        last_name = 'smith'
    245        reality = src.functions.w_optional_arguments(
    246            last_input=last_name,
    247            first_input=first_name,
    248        )
    249        my_expectation = (last_name, first_name)
    250        self.assertEqual(reality, my_expectation)
    251
    252
    253# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: Tuples differ: ('john', 'smith')
                                != ('smith', 'john')
    
  • I change my_expectation to match reality

    244        last_name = 'smith'
    245        reality = src.functions.w_optional_arguments(
    246            last_input=last_name,
    247            first_input=first_name,
    248        )
    249        # my_expectation = (last_name, first_name)
    250        my_expectation = (first_name, last_name)
    251        self.assertEqual(reality, my_expectation)
    252
    253
    254# Exceptions seen
    

    the test passes.

  • I remove the commented lines

    219    def test_w_optional_arguments(self):
    220        first_name, last_name = 'jane', 'doe'
    221        reality = src.functions.w_optional_arguments(
    222            first_name,
    223        )
    224        my_expectation = (first_name, last_name)
    225        self.assertEqual(reality, my_expectation)
    226
    227        first_name, blow = 'joe', 'blow'
    228        reality = src.functions.w_optional_arguments(
    229            first_name, blow,
    230        )
    231        my_expectation = (first_name, blow)
    232        self.assertEqual(reality, my_expectation)
    233
    234        first_name = 'john'
    235        reality = src.functions.w_optional_arguments(
    236            first_input=first_name,
    237        )
    238        my_expectation = (first_name, last_name)
    239        self.assertEqual(reality, my_expectation)
    240
    241        last_name = 'smith'
    242        reality = src.functions.w_optional_arguments(
    243            last_input=last_name,
    244            first_input=first_name,
    245        )
    246        my_expectation = (first_name, last_name)
    247        self.assertEqual(reality, my_expectation)
    248
    249
    250# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit --all --message \
    'add test_w_optional_arguments'
    

    the terminal shows a summary of the changes then goes back to the command line.

Note

these four functions

  • w_keyword_arguments

  • w_positional_arguments

  • w_args_and_kwargs

  • w_optional_arguments

are the same, they always return first_input, last_input, their names are different.

def w_positional_arguments(first_input, last_input):
def w_keyword_arguments(first_input, last_input):
def w_args_and_kwargs(first_input, last_input):
def w_optional_arguments(first_input, last_input='doe'):

first_input and last_input are also names (variables), they can be any names. The difference that matters in the tests is how I call the functions_

w_positional_arguments('first', 'last')
                return 'first', 'last'
w_positional_arguments('last', 'first')
                return 'last', 'first'
w_positional_arguments(
    first_input=[1, 2, 3, 'n'], last_input=(1, 2, 3, 'n')
)
    return [1, 2, 3, 'n'], (1, 2, 3, 'n')
w_keyword_arguments(first_input='first', last_input='last')
            return 'first', 'last'
w_keyword_arguments(last_input='last', first_input='first')
            return 'first', 'last'
w_keyword_arguments('last', 'first')
             return 'last', 'first'
w_args_and_kwargs('first', last_input='last')
                 return 'first', 'last'
w_optional_arguments('jane', last_input='doe')
              return 'jane', 'doe'
w_optional_arguments('jane')
              return 'jane', 'doe'
w_optional_arguments('joe', 'blow')
              return 'joe', 'blow'
w_optional_arguments(
    first_input='john', last_input='smith'
)
    return 'john', 'smith'

Tip

as a rule of thumb I use keyword arguments when I have 2 or more inputs so I do not have to remember the order.


test_w_unknown_arguments

I can make functions_ that take any number of positional and keyword arguments. This means I do not need to know how many inputs the function should take when it is called.


RED: make it fail


  • I go back to the terminal where the tests are running

  • I add a new test to test_functions.py

    241        last_name = 'smith'
    242        reality = src.functions.w_optional_arguments(
    243            last_input=last_name,
    244            first_input=first_name,
    245        )
    246        my_expectation = (first_name, last_name)
    247        self.assertEqual(reality, my_expectation)
    248
    249    def test_w_unknown_arguments(self):
    250        reality = src.functions.w_unknown_arguments(
    251            0, 1, a=2, b=3,
    252        )
    253        my_expectation = None
    254        self.assertEqual(reality, my_expectation)
    255
    256
    257# Exceptions seen
    

    the terminal is my friend, and shows AttributeError

    AttributeError: module 'src.functions'
                    has no attribute 'w_unknown_arguments'.
                    Did you mean: 'w_keyword_arguments'?
    

    because functions.py does not have w_unknown_arguments


GREEN: make it pass


  • I add the function to functions.py

    38def w_optional_arguments(first_input, last_input='doe'):
    39    return first_input, last_input
    40
    41
    42def w_unknown_arguments():
    43    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: w_unknown_arguments() got
               an unexpected keyword argument 'a'
    
  • I add the name to the function definition

    42# def w_unknown_arguments():
    43def w_unknown_arguments(a):
    44    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: w_unknown_arguments() got
               multiple values for argument 'a'
    

    I had this same problem in test_w_args_and_kwargs. Python cannot tell if a is a positional or keyword argument based on my function definition.


double starred expressions


  • Python has a way for a function to take any number of keyword arguments without knowing how many they are. I use it to replace a in the parentheses

    42# def w_unknown_arguments():
    43# def w_unknown_arguments(a):
    44def w_unknown_arguments(**kwargs):
    45    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: w_unknown_arguments() takes
               0 positional arguments but 2 were given
    
  • I add a name for the first positional argument

    42# def w_unknown_arguments():
    43# def w_unknown_arguments(a):
    44# def w_unknown_arguments(**kwargs):
    45def w_unknown_arguments(**kwargs, x):
    46    return None
    

    the terminal is my friend, and shows SyntaxError

    SyntaxError: arguments cannot follow var-keyword argument
    

    a reminder that I cannot put positional arguments after keyword arguments

  • I change the order of the inputs in w_unknown_arguments in functions.py

    42# def w_unknown_arguments():
    43# def w_unknown_arguments(a):
    44# def w_unknown_arguments(**kwargs):
    45# def w_unknown_arguments(**kwargs, x):
    46def w_unknown_arguments(x, **kwargs):
    47    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: w_unknown_arguments() takes
               1 positional argument but 4 were given
    
  • I add a name for the other positional argument

    42# def w_unknown_arguments():
    43# def w_unknown_arguments(a):
    44# def w_unknown_arguments(**kwargs):
    45# def w_unknown_arguments(**kwargs, x):
    46# def w_unknown_arguments(x, **kwargs):
    47def w_unknown_arguments(x, y, **kwargs):
    48    return None
    

    the test passes.


REFACTOR: make it better


  • I add an assertion to see what happens if I call the function with 3 keyword arguments

    249    def test_w_unknown_arguments(self):
    250        reality = src.functions.w_unknown_arguments(
    251            0, 1, a=2, b=3,
    252        )
    253        my_expectation = None
    254        self.assertEqual(reality, my_expectation)
    255
    256        reality = src.functions.w_unknown_arguments(
    257            0, 1, a=2, b=3, c=4,
    258        )
    259        my_expectation = ()
    260        self.assertEqual(reality, my_expectation)
    261
    262
    263# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: None != ()
    
  • I change my_expectation to match reality

    256        reality = src.functions.w_unknown_arguments(
    257            0, 1, a=2, b=3, c=4,
    258        )
    259        # my_expectation = ()
    260        my_expectation = None
    261        self.assertEqual(reality, my_expectation)
    262
    263
    264# Exceptions seen
    

    the test passes because the function can take any number of keyword arguments without knowing how many are in the call.

  • I add an assertion to see what happens when I call the function with 3 positional arguments

    206        reality = src.functions.w_unknown_arguments(
    207            0, 1, a=2, b=3, c=4,
    208        )
    209        my_expectation = None
    210        self.assertEqual(reality, my_expectation)
    211
    212        reality = src.functions.w_unknown_arguments(
    213            0, 1, 2, a=3, b=4, c=5,
    214        )
    215        my_expectation = None
    216        self.assertEqual(reality, my_expectation)
    217
    218
    219# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError: w_unknown_arguments() takes
               2 positional arguments but 3 were given
    

    the function definition only allows two positional arguments

  • I change the definition of the w_unknown_arguments function to make it take three positional arguments

    42# def w_unknown_arguments():
    43# def w_unknown_arguments(a):
    44# def w_unknown_arguments(**kwargs):
    45# def w_unknown_arguments(**kwargs, x):
    46# def w_unknown_arguments(x, **kwargs):
    47# def w_unknown_arguments(x, y, **kwargs):
    48def w_unknown_arguments(x, y, z, **kwargs):
    49    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: w_unknown_arguments() missing 1
               required positional argument: 'z'
    

    because the previous assertion calls the function with two positional arguments and it now requires three.


starred expressions


  • Python also has a way to handle any number of positional arguments. I use it

    42# def w_unknown_arguments():
    43# def w_unknown_arguments(a):
    44# def w_unknown_arguments(**kwargs):
    45# def w_unknown_arguments(**kwargs, x):
    46# def w_unknown_arguments(x, **kwargs):
    47# def w_unknown_arguments(x, y, **kwargs):
    48# def w_unknown_arguments(x, y, z, **kwargs):
    49def w_unknown_arguments(*args, **kwargs):
    50    return None
    

    the test passes.

  • *args, **kwargs is Python convention. I change the names to make it clearer

    42# def w_unknown_arguments():
    43# def w_unknown_arguments(a):
    44# def w_unknown_arguments(**kwargs):
    45# def w_unknown_arguments(**kwargs, x):
    46# def w_unknown_arguments(x, **kwargs):
    47# def w_unknown_arguments(x, y, **kwargs):
    48# def w_unknown_arguments(x, y, z, **kwargs):
    49# def w_unknown_arguments(*args, **kwargs):
    50def w_unknown_arguments(
    51        *positional_arguments, **keyword_arguments
    52    ):
    53    return None
    
  • I change the `return statement`_ because I want the function to return its input (remember the identity function?)

    50def w_unknown_arguments(
    51        *positional_arguments, **keyword_arguments
    52    ):
    53    # return None
    54    return positional_arguments, keyword_arguments
    

    the terminal is my friend, and shows

    AssertionError: ((0, 1), {'a': 2, 'b': 3}) != None
    

    I get a tuple that has a tuple and a dictionary

  • I change my_expectation to match reality in the first assertion

    249    def test_w_unknown_arguments(self):
    250        reality = src.functions.w_unknown_arguments(
    251            0, 1, a=2, b=3,
    252        )
    253        # my_expectation = None
    254        my_expectation = ((0, 1), {'a': 2, 'b': 3})
    255        self.assertEqual(reality, my_expectation)
    

    the terminal is my friend, and shows AssertionError

    AssertionError: ((0, 1), {'a': 2, 'b': 3, 'c': 4}) != None
    
  • I change my_expectation to match reality in the second assertion

    249    def test_w_unknown_arguments(self):
    250        reality = src.functions.w_unknown_arguments(
    251            0, 1, a=2, b=3,
    252        )
    253        # my_expectation = None
    254        my_expectation = ((0, 1), {'a': 2, 'b': 3})
    255        self.assertEqual(reality, my_expectation)
    256
    257        reality = src.functions.w_unknown_arguments(
    258            0, 1, a=2, b=3, c=4,
    259        )
    260        # my_expectation = ()
    261        # my_expectation = None
    262        my_expectation = (
    263            (0, 1), {'a': 2, 'b': 3, 'c': 4}
    264        )
    265        self.assertEqual(reality, my_expectation)
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
            ((0, 1, 2), {'a': 3, 'b': 4, 'c': 5})
         != None
    
  • I change my_expectation to match reality in the last assertion

    249    def test_w_unknown_arguments(self):
    250        reality = src.functions.w_unknown_arguments(
    251            0, 1, a=2, b=3,
    252        )
    253        # my_expectation = None
    254        my_expectation = ((0, 1), {'a': 2, 'b': 3})
    255        self.assertEqual(reality, my_expectation)
    256
    257        reality = src.functions.w_unknown_arguments(
    258            0, 1, a=2, b=3, c=4,
    259        )
    260        # my_expectation = ()
    261        # my_expectation = None
    262        my_expectation = (
    263            (0, 1), {'a': 2, 'b': 3, 'c': 4}
    264        )
    265        self.assertEqual(reality, my_expectation)
    266
    267        reality = src.functions.w_unknown_arguments(
    268            0, 1, 2, a=3, b=4, c=5,
    269        )
    270        # my_expectation = None
    271        my_expectation = (
    272            (0, 1, 2), {'a': 3, 'b': 4, 'c': 5}
    273        )
    274        self.assertEqual(reality, my_expectation)
    275
    276
    277# Exceptions seen
    

    the test passes.

  • I add variables to use them to remove repetition of the tuple and dictionary from the first assertion

    249    def test_w_unknown_arguments(self):
    250        a_tuple = (0, 1)
    251        a_dictionary = {'a': 2, 'b': 3}
    252        reality = src.functions.w_unknown_arguments(
    253            0, 1, a=2, b=3,
    254        )
    255        # my_expectation = None
    256        my_expectation = ((0, 1), {'a': 2, 'b': 3})
    257        self.assertEqual(reality, my_expectation)
    
  • I use the variables to remove repetition of the values

    249    def test_w_unknown_arguments(self):
    250        a_tuple = (0, 1)
    251        a_dictionary = {'a': 2, 'b': 3}
    252        reality = src.functions.w_unknown_arguments(
    253            # 0, 1, a=2, b=3,
    254            a_tuple, a_dictionary
    255        )
    256        # my_expectation = None
    257        # my_expectation = ((0, 1), {'a': 2, 'b': 3})
    258        my_expectation = (a_tuple, a_dictionary)
    259        self.assertEqual(reality, my_expectation)
    

    the terminal is my friend, and shows AssertionError

    AssertionError: Tuples differ:
        (((0, 1), {'a': 2, 'b': 3}), {})
     != ((0, 1), {'a': 2, 'b': 3})
    

    because passing in the values this way means I am sending in two positional arguments - (a_tuple, a_dictionary) so I get a tuple with a tuple of the arguments and an empty dictionary instead of a tuple with the arguments in the tuple I sent and a dictionary with the dictionary I sent

  • I change the inputs with * and ** so that Python breaks up the contents, allowing them to be used as separate arguments

    249    def test_w_unknown_arguments(self):
    250        a_tuple = (0, 1)
    251        a_dictionary = {'a': 2, 'b': 3}
    252        reality = src.functions.w_unknown_arguments(
    253            # 0, 1, a=2, b=3,
    254            # a_tuple, a_dictionary
    255            *a_tuple, **a_dictionary
    256        )
    257        # my_expectation = None
    258        # my_expectation = ((0, 1), {'a': 2, 'b': 3})
    259        my_expectation = (a_tuple, a_dictionary)
    260        self.assertEqual(reality, my_expectation)
    

    the test passes because src.functions.w_unknown_arguments(*a_tuple, **a_dictionary) is src.functions.w_unknown_arguments(0, 1, a=2, b=3).

  • I add variables to the next assertion to use them to remove repetition of the tuple and dictionary from the second assertion

    249    def test_w_unknown_arguments(self):
    250        a_tuple = (0, 1)
    251        a_dictionary = {'a': 2, 'b': 3}
    252        reality = src.functions.w_unknown_arguments(
    253            # 0, 1, a=2, b=3,
    254            # a_tuple, a_dictionary
    255            *a_tuple, **a_dictionary
    256        )
    257        # my_expectation = None
    258        # my_expectation = ((0, 1), {'a': 2, 'b': 3})
    259        my_expectation = (a_tuple, a_dictionary)
    260        self.assertEqual(reality, my_expectation)
    261
    262        a_tuple = (0, 1)
    263        a_dictionary = {'a': 2, 'b': 3, 'c': 4}
    264        reality = src.functions.w_unknown_arguments(
    265            0, 1, a=2, b=3, c=4,
    266        )
    267        # my_expectation = ()
    268        # my_expectation = None
    269        my_expectation = (
    270            (0, 1), {'a': 2, 'b': 3, 'c': 4}
    271        )
    272        self.assertEqual(reality, my_expectation)
    
  • I use the variables to remove repetition of the tuple and dictionary

    262        a_tuple = (0, 1)
    263        a_dictionary = {'a': 2, 'b': 3, 'c': 4}
    264        reality = src.functions.w_unknown_arguments(
    265            # 0, 1, a=2, b=3, c=4,
    266            a_tuple, a_dictionary
    267        )
    268        # my_expectation = ()
    269        # my_expectation = None
    270        my_expectation = (
    271            # (0, 1), {'a': 2, 'b': 3, 'c': 4}
    272            a_tuple, a_dictionary
    273        )
    274        self.assertEqual(reality, my_expectation)
    

    the terminal is my friend, and shows AssertionError

    AssertionError: Tuples differ:
            (((0, 1), {'a': 2, 'b': 3, 'c': 4}), {})
         != ((0, 1), {'a': 2, 'b': 3, 'c': 4})
    

    because passing in the values this way means I am sending in two positional arguments - (a_tuple, a_dictionary) so I get a tuple with a tuple of the arguments and an empty dictionary instead of a tuple with the arguments in the tuple I sent and a dictionary with the dictionary I sent

  • I change the inputs with * and ** so that Python breaks up the contents, allowing them to be used as separate arguments

    262        a_tuple = (0, 1)
    263        a_dictionary = {'a': 2, 'b': 3, 'c': 4}
    264        reality = src.functions.w_unknown_arguments(
    265            # 0, 1, a=2, b=3, c=4,
    266            # a_tuple, a_dictionary
    267            *a_tuple, **a_dictionary
    268        )
    269        # my_expectation = ()
    270        # my_expectation = None
    271        my_expectation = (
    272            # (0, 1), {'a': 2, 'b': 3, 'c': 4}
    273            a_tuple, a_dictionary
    274        )
    275        self.assertEqual(reality, my_expectation)
    

    the test passes because src.functions.w_unknown_arguments(*a_tuple, **a_dictionary) is src.functions.w_unknown_arguments(0, 1, a=2, b=3, c=4).

  • I add variables to the third assertion to use them to remove repetition of the tuple and dictionary

    249    def test_w_unknown_arguments(self):
    250        a_tuple = (0, 1)
    251        a_dictionary = {'a': 2, 'b': 3}
    252        reality = src.functions.w_unknown_arguments(
    253            # 0, 1, a=2, b=3,
    254            # a_tuple, a_dictionary
    255            *a_tuple, **a_dictionary
    256        )
    257        # my_expectation = None
    258        # my_expectation = ((0, 1), {'a': 2, 'b': 3})
    259        my_expectation = (a_tuple, a_dictionary)
    260        self.assertEqual(reality, my_expectation)
    261
    262        a_tuple = (0, 1)
    263        a_dictionary = {'a': 2, 'b': 3, 'c': 4}
    264        reality = src.functions.w_unknown_arguments(
    265            # 0, 1, a=2, b=3, c=4,
    266            # a_tuple, a_dictionary
    267            *a_tuple, **a_dictionary
    268        )
    269        # my_expectation = ()
    270        # my_expectation = None
    271        my_expectation = (
    272            # (0, 1), {'a': 2, 'b': 3, 'c': 4}
    273            a_tuple, a_dictionary
    274        )
    275        self.assertEqual(reality, my_expectation)
    276
    277        a_tuple = (0, 1, 2)
    278        a_dictionary = {'a': 3, 'b': 4, 'c': 5}
    279        reality = src.functions.w_unknown_arguments(
    280            0, 1, 2, a=3, b=4, c=5,
    281        )
    282        # my_expectation = None
    283        my_expectation = (
    284            (0, 1, 2), {'a': 3, 'b': 4, 'c': 5}
    285        )
    286        self.assertEqual(reality, my_expectation)
    287
    288
    289# Exceptions seen
    
  • I use the variables to remove repetition of the tuple and dictionary

    277        a_tuple = (0, 1, 2)
    278        a_dictionary = {'a': 3, 'b': 4, 'c': 5}
    279        reality = src.functions.w_unknown_arguments(
    280            # 0, 1, 2, a=3, b=4, c=5,
    281            a_tuple, a_dictionary,
    282        )
    283        # my_expectation = None
    284        my_expectation = (
    285            # (0, 1, 2), {'a': 3, 'b': 4, 'c': 5}
    286            a_tuple, a_dictionary
    287        )
    288        self.assertEqual(reality, my_expectation)
    289
    290
    291# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: Tuples differ:
            (((0, 1, 2), {'a': 3, 'b': 4, 'c': 5}), {})
         != ((0, 1, 2), {'a': 3, 'b': 4, 'c': 5})
    

    because passing in the values this way means I am sending in two positional arguments - (a_tuple, a_dictionary) so I get a tuple with a tuple of the arguments and an empty dictionary instead of a tuple with the arguments in the tuple I sent and a dictionary with the dictionary I sent

  • I change the inputs with * and ** so that Python breaks up the contents, allowing them to be used as separate arguments

    277        a_tuple = (0, 1, 2)
    278        a_dictionary = {'a': 3, 'b': 4, 'c': 5}
    279        reality = src.functions.w_unknown_arguments(
    280            # 0, 1, 2, a=3, b=4, c=5,
    281            # a_tuple, a_dictionary,
    282            *a_tuple, **a_dictionary,
    283        )
    284        # my_expectation = None
    285        my_expectation = (
    286            # (0, 1, 2), {'a': 3, 'b': 4, 'c': 5}
    287            a_tuple, a_dictionary
    288        )
    289        self.assertEqual(reality, my_expectation)
    290
    291
    292# Exceptions seen
    

    the test passes because src.functions.w_unknown_arguments(*a_tuple, **a_dictionary) is src.functions.w_unknown_arguments(0, 1, 2, a=3, b=4, c=5).


how Python reads positional arguments

I add an assertion to see what happens when I call w_unknown_arguments with ONLY positional arguments.

277          a_tuple = (0, 1, 2)
278          a_dictionary = {'a': 3, 'b': 4, 'c': 5}
279          reality = src.functions.w_unknown_arguments(
280              # 0, 1, 2, a=3, b=4, c=5,
281              # a_tuple, a_dictionary,
282              *a_tuple, **a_dictionary,
283          )
284          # my_expectation = None
285          my_expectation = (
286              # (0, 1, 2), {'a': 3, 'b': 4, 'c': 5}
287              a_tuple, a_dictionary
288          )
289          self.assertEqual(reality, my_expectation)
290
291          a_tuple = (0, 1, 2, 3)
292          reality = src.functions.w_unknown_arguments(
293              *a_tuple
294          )
295          my_expectation = ()
296          self.assertEqual(reality, my_expectation)
297
298
299  # Exceptions seen

the terminal is my friend, and shows AssertionError

AssertionError: Tuples differ: ((0, 1, 2, 3), {}) != ()

I change my_expectation to match reality

291          a_tuple = (0, 1, 2, 3)
292          reality = src.functions.w_unknown_arguments(
293              *a_tuple
294          )
295          # my_expectation = ()
296          my_expectation = (a_tuple, {})
297          self.assertEqual(reality, my_expectation)
298
299
300  # Exceptions seen

the test passes. The function gives me back the positional arguments in a tuple (things in parentheses (()) separated by commas) and gives me an empty dictionary (any key-value pairs in curly braces { } separated by a comma) for the keyword arguments because I did not give any in the call.


how Python reads keyword arguments

I add another assertion to see what happens when I call the function with ONLY keyword arguments

291          a_tuple = (0, 1, 2, 3)
292          reality = src.functions.w_unknown_arguments(
293              *a_tuple
294          )
295          # my_expectation = ()
296          my_expectation = (a_tuple, {})
297          self.assertEqual(reality, my_expectation)
298
299          a_dictionary = {'a': 4, 'b': 5, 'c': 6, 'd': 7}
300          reality = src.functions.w_unknown_arguments(
301              **a_dictionary
302          )
303          my_expectation = ()
304          self.assertEqual(reality, my_expectation)
305
306
307  # Exceptions seen

the terminal is my friend, and shows

AssertionError: Tuples differ:
                ((), {'a': 4, 'b': 5, 'c': 6, 'd': 7}) != ()

I change my_expectation to match reality

299          a_dictionary = {'a': 4, 'b': 5, 'c': 6, 'd': 7}
300          reality = src.functions.w_unknown_arguments(
301              **a_dictionary
302          )
303          # my_expectation = ()
304          my_expectation = ((), a_dictionary)
305          self.assertEqual(reality, my_expectation)
306
307
308  # Exceptions seen

the test passes. The function gives me back the positional arguments as an empty tuple (things in parentheses (()) separated by commas) because I did not give any in the call, it gives me a dictionary (any key-value pairs in curly braces { } separated by a comma) of the keyword arguments.


how Python reads positional and keyword arguments

  • I add one more assertion to see what happens when I call the function with no inputs

    299        a_dictionary = {'a': 4, 'b': 5, 'c': 6, 'd': 7}
    300        reality = src.functions.w_unknown_arguments(
    301            **a_dictionary
    302        )
    303        # my_expectation = ()
    304        my_expectation = ((), a_dictionary)
    305        self.assertEqual(reality, my_expectation)
    306
    307        reality = src.functions.w_unknown_arguments()
    308        my_expectation = TypeError
    309        self.assertEqual(reality, my_expectation)
    310
    311
    312# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: ((), {}) != <class 'TypeError'>
    
  • I change the my_expectation to match reality

    307        reality = src.functions.w_unknown_arguments()
    308        # my_expectation = TypeError
    309        my_expectation = ((), {})
    310        self.assertEqual(reality, my_expectation)
    311
    312
    313# Exceptions seen
    

    the test passes.

  • I remove the commented lines

    249    def test_w_unknown_arguments(self):
    250        a_tuple = (0, 1)
    251        a_dictionary = {'a': 2, 'b': 3}
    252        reality = src.functions.w_unknown_arguments(
    253            *a_tuple, **a_dictionary
    254        )
    255        my_expectation = (a_tuple, a_dictionary)
    256        self.assertEqual(reality, my_expectation)
    257
    258        a_tuple = (0, 1)
    259        a_dictionary = {'a': 2, 'b': 3, 'c': 4}
    260        reality = src.functions.w_unknown_arguments(
    261            *a_tuple, **a_dictionary
    262        )
    263        my_expectation = (
    264            a_tuple, a_dictionary
    265        )
    266        self.assertEqual(reality, my_expectation)
    267
    268        a_tuple = (0, 1, 2)
    269        a_dictionary = {'a': 3, 'b': 4, 'c': 5}
    270        reality = src.functions.w_unknown_arguments(
    271            *a_tuple, **a_dictionary,
    272        )
    273        my_expectation = (
    274            a_tuple, a_dictionary
    275        )
    276        self.assertEqual(reality, my_expectation)
    277
    278        a_tuple = (0, 1, 2, 3)
    279        reality = src.functions.w_unknown_arguments(
    280            *a_tuple
    281        )
    282        my_expectation = (a_tuple, {})
    283        self.assertEqual(reality, my_expectation)
    284
    285        a_dictionary = {'a': 4, 'b': 5, 'c': 6, 'd': 7}
    286        reality = src.functions.w_unknown_arguments(
    287            **a_dictionary
    288        )
    289        my_expectation = ((), a_dictionary)
    290        self.assertEqual(reality, my_expectation)
    291
    292        reality = src.functions.w_unknown_arguments()
    293        my_expectation = ((), {})
    294        self.assertEqual(reality, my_expectation)
    295
    296
    297# Exceptions seen
    
  • I remove the commented lines from the w_unknown_arguments function in functions.py

    38def w_optional_arguments(first_input, last_input='doe'):
    39    return first_input, last_input
    40
    41
    42def w_unknown_arguments(
    43        *positional_arguments, **keyword_arguments
    44    ):
    45    return positional_arguments, keyword_arguments
    
  • I add a git commit message in the other terminal

    git commit --all --message \
    'add test_w_unknown_arguments'
    

    the terminal shows a summary of the changes then goes back to the command line.

Note

these statements are the same

w_unknown_arguments(0, 1, 2, 3, a=4, b=5, c=6, d=7)
w_unknown_arguments(
    *(0, 1, 2, 3), **{'a': 4, 'b': 5, 'c': 6, 'd': 7}
)

because w_unknown_arguments in functions.py in the src folder will always

return positional_arguments, keyword_arguments

in this case

0, 1, 2, 3
*(0, 1, 2, 3)

are positional arguments which are taken as a tuple and

a=4, b=5, c=6, d=7
**{'a': 4, 'b': 5, 'c': 6, 'd': 7}

are keyword arguments which are taken as a dictionary.

The function reads positional arguments as tuples, and keyword arguments as dictionaries.

Is this why the update method of dictionaries can take a dictionary as input?


close the project

  • I close test_functions.py and functions.py

  • I 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 functions

    cd ..
    

    the terminal shows

    .../pumping_python
    

    I am back in the pumping_python directory


review

I ran tests to show that I can make functions_ with

  • the def, pass and return keywords

How many questions can you answer about functions?


code from the chapter

Do you want to see all the CODE I typed in this chapter?


what is next?

you have covered a bit so far and know

how to make a Python test driven development environment manually * what causes AssertionError? and * how to make functions

Would you like to use variables and functions to make a person?


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.