functions that take input

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_object -> process -> output_object

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

A function does something (the process) with input_object and returns output_object as the result. For example

                  f(x) -> y
function(input_object) -> output_object
 solar_panel(sunlight) -> electricity
    factory(materials) -> product
     chef(ingredients) -> food
         stomach(food) -> poop

how to make a function that takes input

functions that take input are made with

  • the def keyword

  • a name

  • parentheses with the inputs allowed

  • a colon after the parentheses

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

  • a return statement

def name_of_function(input_object):
    the body of the function
    return output_object

preview

I have these tests by the end of the chapter

functions/tests/test_functions.py
 1def assert_equal(input_1, input_2):
 2    assert input_1 == input_2
 3
 4
 5def assert_is_none(something):
 6    assert something is None
 7
 8
 9def test_making_a_function_w_pass():
10    def w_pass():
11        pass
12
13    assert_is_none(w_pass())
14
15
16def test_making_a_function_w_return():
17    def w_return():
18        return
19
20    assert_is_none(w_return())
functions/tests/test_functions.py
23def test_making_a_function_w_return_none():
24    def w_return_none():
25        return None
26
27    assert_is_none(w_return_none())
28
29
30def test_what_happens_after_functions_return():
31    def return_leaves_the_function():
32        return None
33        return 'only one way for this line to run'
34
35    assert_is_none(return_leaves_the_function())
36
37
38def test_constant_function():
39    def constant():
40        return 'the same thing'
41
42    assert_equal(constant(), 'the same thing')
functions/tests/test_functions.py
45def test_identity_function():
46    def identity(the_input):
47        return the_input
48
49    assert_is_none(identity(None))
50    assert_equal(identity(object), object)
51
52
53def test_why_use_a_function():
54    def add_x(number):
55        return 3 + number
56
57    assert_equal(add_x(0), 3)
58    assert_equal(add_x(1), 4)
59    assert_equal(add_x(2), 5)
60    assert_equal(add_x(3), 6)
61    assert_equal(add_x(4), 7)
62    assert_equal(add_x(5), 8)
63    assert_equal(add_x(6), 9)
64    assert_equal(add_x(7), 10)
65    assert_equal(add_x(8), 11)
66    assert_equal(add_x(9), 12)
functions/tests/test_functions.py
 69def positional_arguments(first_input, last_input):
 70    return first_input, last_input
 71
 72
 73def test_positional_arguments():
 74    first, last = 'first', 'last'
 75
 76    assert_equal(
 77        positional_arguments(first, last),
 78        (first, last)
 79    )
 80    assert_equal(
 81        positional_arguments(last, first),
 82        (last, first)
 83    )
 84
 85    assert_equal(
 86        positional_arguments(0, 1), (0, 1)
 87    )
 88
 89    a_tuple = (0, 1, 2, 'n')
 90    a_list = [0, 1, 2, 'n']
 91    assert_equal(
 92        positional_arguments(a_tuple, a_list),
 93        (a_tuple, a_list)
 94    )
 95
 96    a_set = {0, 1, 2, 'n'}
 97    a_dictionary = {'key': 'value'}
 98    assert_equal(
 99        keyword_arguments(
100            a_set, a_dictionary,
101        ),
102        (a_set, a_dictionary)
103    )
functions/tests/test_functions.py
106def keyword_arguments(first_input, last_input):
107    return first_input, last_input
108
109
110def test_keyword_arguments():
111    first, last = 'first', 'last'
112
113    assert_equal(
114        keyword_arguments(
115            first_input=first, last_input=last,
116        ),
117        (first, last)
118    )
119    assert_equal(
120        keyword_arguments(
121            last_input=last, first_input=first,
122        ),
123        (first, last)
124    )
125
126    assert_equal(
127        keyword_arguments(
128            last_input=0, first_input=1,
129        ),
130        (1, 0)
131    )
132
133    a_tuple = (0, 1, 2, 'n')
134    a_list = [0, 1, 2, 'n']
135    assert_equal(
136        keyword_arguments(
137            first_input=a_tuple,
138            last_input=a_list,
139        ),
140        (a_tuple, a_list)
141    )
142
143    a_set = {0, 1, 2, 'n'}
144    a_dictionary = {'key': 'value'}
145    assert_equal(
146        positional_arguments(
147            last_input=a_dictionary,
148            first_input=a_set,
149        ),
150        (a_set, a_dictionary)
151    )
functions/tests/test_functions.py
154def test_args_and_kwargs():
155    def args_and_kwargs(first_input, last_input):
156        return first_input, last_input
157
158    first, last = 'first', 'last'
159
160    assert_equal(
161        args_and_kwargs(
162            first, last_input=last
163        ),
164        (first, last)
165    )
functions/tests/test_functions.py
168def test_optional_arguments():
169    def optional_arguments(
170        first_input, last_input='doe'
171    ):
172        return first_input, last_input
173
174    first_name, last_name = 'jane', 'doe'
175    assert_equal(
176        optional_arguments(
177            first_name,
178        ),
179        (first_name, last_name)
180    )
181
182    first_name, blow = 'joe', 'blow'
183    assert_equal(
184        optional_arguments(
185            first_name, blow
186        ),
187        (first_name, blow)
188    )
189
190    first_name = 'john'
191    assert_equal(
192        optional_arguments(
193            first_input=first_name,
194        ),
195        (first_name, last_name)
196    )
197
198    last_name = 'smith'
199    assert_equal(
200        optional_arguments(
201            last_input=last_name,
202            first_input=first_name,
203        ),
204        (first_name, last_name)
205    )
functions/tests/test_functions.py
208def test_unknown_number_of_arguments():
209    def unknown_number_of_arguments(
210        *positional_arguments, **keyword_arguments
211    ):
212        return positional_arguments, keyword_arguments
213
214    a_tuple = (0, 1)
215    a_dictionary = {'a': 2, 'b': 3}
216    assert_equal(
217        unknown_number_of_arguments(
218            *a_tuple, **a_dictionary
219        ),
220        (a_tuple, a_dictionary)
221    )
222
223    a_dictionary = {'a': 2, 'b': 3, 'c': 4}
224    assert_equal(
225        unknown_number_of_arguments(
226            *a_tuple, **a_dictionary,
227        ),
228        (a_tuple, a_dictionary)
229    )
230
231    a_tuple = (0, 1, 2)
232    a_dictionary = {'a': 3, 'b': 4, 'c': 5}
233    assert_equal(
234        unknown_number_of_arguments(
235            *a_tuple, **a_dictionary
236        ),
237        (a_tuple, a_dictionary)
238    )
239
240    a_tuple = (0, 1, 2, 'n')
241    assert_equal(
242        unknown_number_of_arguments(*a_tuple),
243        (a_tuple, {})
244    )
245
246    a_dictionary = {'a': 1, 'b': 2, 'c': 3, 'd': 'n'}
247    assert_equal(
248        unknown_number_of_arguments(**a_dictionary),
249        ((), a_dictionary)
250    )
251
252    assert_equal(
253        unknown_number_of_arguments(), ((), {})
254    )
255
256
257# Exceptions seen
258# AssertionError
259# NameError
260# TypeError
261# SyntaxError

questions about functions that take input


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 from the tests folder

  • 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 it returns None by default. The Identity or Passthrough function returns the input it gets as output.


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() == '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 because I get None when I call identity

identity() -> None
└── def identity():
    └── return None

how to call a function with input

I can call a function with input by placing an object in parentheses (()) when I use the name after it is defined.

name_of_function(input_object)

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


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.

    identity(None  ) -> None
    └── def identity(the_input):
        ├── the_input = None
        └── return the_input
    
    identity(object) -> object
    └── def identity(the_input):
        ├── the_input = object
        └── return the_input
    
  • 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.

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.

The Identity Function returns its input as 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 + 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 these 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 a function to test_why_use_a_function

    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.

    add_x(number) -> 2 + number
    └── def add_x(number):
        └── return 2 + number
    

    When add_x(0) runs

    add_x(0) -> 2
    └── def add_x(number):
        ├── number = 0
        └── return 2 + number
            return 2 + 0
            return 2
    

    Using substitution since I can treat a call to a function as the object it returns

    assert add_x(0) == 2
    assert 2        == 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
    

    because

    add_x(number) -> 3 + number
    └── def add_x(number):
        └── return 3 + number
    
  • 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 organize tests

  • I can use a function to remove repetition.

  • Is there a better way to handle the changing results?

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


test_positional_arguments

I can call functions with positional arguments.


RED: make it fail


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

  • I add test_positional_arguments

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

    the terminal is my friend, and shows NameError

    NameError: name 'positional_arguments' is not defined
    

    because …


GREEN: make it pass


I add the function

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

the test passes.

positional_arguments() -> None
└── def positional_arguments():
    └── return None

REFACTOR: make it better


  • I add input to the function call

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

    the terminal is my friend, and shows TypeError

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

    because

  • I add a name in parentheses to make the function take input

    61def test_positional_arguments():
    62    # def positional_arguments():
    63    def positional_arguments(the_input):
    64        return None
    65
    66    # assert positional_arguments() == None
    67    assert positional_arguments('first') == None
    68
    69
    70# Exceptions seen
    

    the test passes because

    positional_arguments(the_input) -> None
    └── def positional_arguments(the_input):
        └── return None
    
  • I add another input to the function call

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

    the terminal is my friend, and shows TypeError

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

    because

  • I make the function take two inputs by changing the name of the first input to be clearer, then I add a another name to the parentheses

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

    the test passes.

  • I change the expectation of the assertion

    65def test_positional_arguments():
    66    # def positional_arguments():
    67    # def positional_arguments(the_input):
    68    def positional_arguments(first_input, last_input):
    69        return None
    70
    71    # assert positional_arguments() == None
    72    # assert positional_arguments('first') == None
    73    # assert positional_arguments('first', 'last') == None
    74    assert (
    75        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 positional_arguments with 'first' and 'last' as inputs, it returns None

    positional_arguments(first_input, last_input) -> None
    └── def positional_arguments(first_input, last_input):
        └── return None
    

    Using substitution since I can treat a call to a function as the object it returns

    assert positional_arguments('first', 'last') == ('first', 'last')
    assert None                                  == ('first', 'last')
    

    I get AssertionError since None is NOT equal to a tuple.

  • I change the return statement to make the function return its inputs as output (like the identity function)

    61def test_positional_arguments():
    62    # def positional_arguments():
    63    # def positional_arguments(the_input):
    64    def positional_arguments(first_input, last_input):
    65        # return None
    66        return first_input, last_input
    67
    68    # assert positional_arguments() == None
    69    # assert positional_arguments('first') == None
    70    # assert positional_arguments('first', 'last') == None
    71    assert (
    72        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

    When positional_arguments('first', 'last') runs

    positional_arguments('first', 'last') -> ('first', 'last')
    └── def positional_arguments(first_input, last_input)
        ├── first_input = 'first'
        ├── last_input  = 'last'
        └── return first_input, last_input
            return 'first'    , 'last'
    
  • The bad thing about giving arguments this way, is I must use the exact same order in the function definition when I make a call a function or I get something different. The good thing about giving arguments this way is I do not need to know the names of the arguments. I add an assertion to show this

    61def test_positional_arguments():
    62    # def positional_arguments():
    63    # def positional_arguments(the_input):
    64    def positional_arguments(first_input, last_input):
    65        # return None
    66        return first_input, last_input
    67
    68    # assert positional_arguments() == None
    69    # assert positional_arguments('first') == None
    70    # assert positional_arguments('first', 'last') == None
    71    assert (
    72        positional_arguments('first', 'last')
    73     == ('first', 'last')
    74    )
    75    assert (
    76        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 this test calls the function with 'last' as first_input and 'first' as last_input.

    When positional_arguments('last', 'first') runs

    positional_arguments('last', 'first') -> ('last', 'first')
    └── def positional_arguments(first_input, last_input)
        ├── first_input = 'last'
        ├── last_input  = 'first'
        └── return first_input, last_input
            return 'last'     , 'first'
    

    Using substitution since I can treat a call to a function as the object it returns

    assert positional_arguments('last', 'first') == ('first', 'last')
    assert ('last', 'first')                     == ('first', 'last')
    
  • I change my expectation to match reality

    61def test_positional_arguments():
    62    # def positional_arguments():
    63    # def positional_arguments(the_input):
    64    def positional_arguments(first_input, last_input):
    65        # return None
    66        return first_input, last_input
    67
    68    # assert positional_arguments() == None
    69    # assert positional_arguments('first') == None
    70    # assert positional_arguments('first', 'last') == None
    71    assert (
    72        positional_arguments('first', 'last')
    73     == ('first', 'last')
    74    )
    75    assert (
    76        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' in test_positional_arguments

    61def test_positional_arguments():
    62    # def positional_arguments():
    63    # def positional_arguments(the_input):
    64    def positional_arguments(first_input, last_input):
    65        # return None
    66        return first_input, last_input
    67
    68    # assert positional_arguments() == None
    69    # assert positional_arguments('first') == None
    70    # assert positional_arguments('first', 'last') == None
    71
    72    first, last = 'first', 'last'
    73
    74    assert (
    75        positional_arguments('first', 'last')
    76     == ('first', 'last')
    77    )
    78    assert (
    79        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' from test_positional_arguments

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

    the test is still green.

  • I add another assertion to test_positional_arguments

    80    assert (
    81        # positional_arguments('last', 'first')
    82    #  == ('first', 'last')
    83    #  == ('last', 'first')
    84        positional_arguments(last, first)
    85     == (last, first)
    86    )
    87
    88    assert (
    89        positional_arguments(0, 1)
    90     == (1, 0)
    91    )
    92
    93
    94# 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 this test calls the function with 0 as first_input and 1 as last_input.

    Using substitution since I can treat a call to a function as the object it returns

    assert positional_arguments(0, 1) == (1, 0)
    assert (0, 1)                     == (1, 0)
    
  • I change my expectation to match reality

    88    assert (
    89        positional_arguments(0, 1)
    90    #  == (1, 0)
    91     == (0, 1)
    92    )
    93
    94
    95# Exceptions seen
    

    the test passes.

    positional_arguments(0, 1) -> (0, 1)
    └── def positional_arguments(first_input, last_input)
        ├── first_input = 0
        ├── last_input  = 1
        └── return first_input, last_input
            return 0          , 1
    
  • I add an assertion to test_positional_arguments with a tuple (anything in parentheses ( ) separated by a comma) and a list (anything in square brackets [ ])

     88    assert (
     89        positional_arguments(0, 1)
     90    #  == (1, 0)
     91     == (0, 1)
     92    )
     93
     94    a_tuple = (0, 1, 2, 'n')
     95    a_list = [0, 1, 2, 'n']
     96    assert (
     97        positional_arguments(a_list, a_tuple)
     98     == (a_tuple, a_list)
     99    )
    100
    101
    102# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

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

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

    Using substitution

    assert positional_arguments(a_list, a_tuple)
                            == (a_tuple, a_list)
    assert ([0, 1, 2, 'n'], (0, 1, 2, 'n'))
        == ((0, 1, 2, 'n'), [0, 1, 2, 'n'])
    
  • I change reality to match my expectation

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

    the test passes.

    ├── a_tuple = (0, 1, 2, 'n')
    ├── a_list = [0, 1, 2, 'n']
    └── positional_arguments(a_tuple, a_list) -> (a_tuple, a_list)
        └── def positional_arguments(first_input, last_input)
            ├── first_input = a_tuple
            ├── last_input  = a_list
            └── return first_input, last_input
                return a_tuple    , a_list
    
  • I remove the commented lines

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

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

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

I can call functions with positional arguments.


extract assert_equal function

The assertions in test_positional_arguments, test_why_use_a_function, test_identity_function and test_constant_function are the same, they check if the result of a function call is equal to something.

assert function() == something

I can use a function to assert if two things are equal.


RED: make it fail


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

  • I add a function named assert_equal that takes two inputs and asserts that they are equal

    1def assert_equal(input_1, input_2):
    2    assert input_1 == input_2
    3
    4
    5def test_making_a_function_w_pass():
    
  • I use the new function for the first assertion in test_positional_arguments

    65def test_positional_arguments():
    66    def positional_arguments(first_input, last_input):
    67        return first_input, last_input
    68
    69    first, last = 'first', 'last'
    70
    71    # assert (
    72    #     positional_arguments(first, last)
    73    #  == (first, last)
    74    # )
    75    assert_equal(
    76        positional_arguments(first, last),
    77        (last, first)
    78    )
    79    assert (
    80        positional_arguments(last, first)
    81     == (last, first)
    82    )
    

    the terminal is my friend, and shows AssertionError

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

GREEN: make it pass


I change the expectation to match reality

75    assert_equal(
76        positional_arguments(first, last),
77        # (last, first)
78        (first, last)
79    )
80    assert (
81        positional_arguments(last, first)
82     == (last, first)
83    )

the test passes.

├── first = 'first'
├── last  = 'last'
└── assert_equal(
        positional_arguments(first, last),
        (first, last)
    ) -> None
    └── def assert_equal(input_1, input_2):
        ├── input_1 = positional_arguments(first, last)
                     └── def positional_arguments(
                             first_input, last_input
                         ):
                         ├── first_input = first
                         ├── last_input  = last
                         └── return first_input, last_input
                             return first      , last
        ├── input_2 = (first, last)
        └── assert input_1       == input_2
            assert (first, last) == (first, last)

REFACTOR: make it better


  • I use the assert_equal function for the second assertion in test_positional_arguments

    80    # assert (
    81    #     positional_arguments(last, first)
    82    #  == (last, first)
    83    # )
    84    assert_equal(
    85        positional_arguments(last, first),
    86        (first, last)
    87    )
    88
    89    assert (
    90        positional_arguments(0, 1)
    91     == (0, 1)
    92    )
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert ('last', 'first') == ('first', 'last')
    
  • I change the expectation to match reality

    84    assert_equal(
    85        positional_arguments(last, first),
    86        # (first, last)
    87        (last, first)
    88    )
    89
    90    assert (
    91        positional_arguments(0, 1)
    92     == (0, 1)
    93    )
    

    the test passes.

    ├── first = 'first'
    ├── last  = 'last'
    └── assert_equal(
            positional_arguments(last, first),
            (last, first)
        ) -> None
        └── def assert_equal(input_1, input_2):
            ├── input_1 = positional_arguments(last, first)
                         └── def positional_arguments(
                                 first_input, last_input
                             ):
                             ├── first_input = last
                             ├── last_input  = first
                             └── return first_input, last_input
                                 return last       , first
            ├── input_2 = (last, first)
            └── assert input_1       == input_2
                assert (last, first) == (last, first)
    
  • I call the assert_equal function for the third assertion in test_positional_arguments

    90    # assert (
    91    #     positional_arguments(0, 1)
    92    #  == (0, 1)
    93    # )
    94    assert_equal(
    95        positional_arguments(0, 1), (1, 0)
    96    )
    97
    98    a_tuple = (0, 1, 2, 'n')
    

    the terminal is my friend, and shows AssertionError

    E       assert (0, 1) == (1, 0)
    
  • I change the expectation to match reality for the third assertion

    94    assert_equal(
    95        # positional_arguments(0, 1), (1, 0)
    96        positional_arguments(0, 1), (0, 1)
    97    )
    98
    99    a_tuple = (0, 1, 2, 'n')
    

    the test passes.

    assert_equal(
        positional_arguments(0, 1),
        (0, 1)
    ) -> None
    └── def assert_equal(input_1, input_2):
        ├── input_1 = positional_arguments(0, 1)
                     └── def positional_arguments(
                             first_input, last_input
                         ):
                         ├── first_input = 0
                         ├── last_input  = 1
                         └── return first_input, last_input
                             return 0          , 1
        ├── input_2 = (0, 1)
        └── assert input_1 == input_2
            assert (0, 1)  == (0, 1)
    
  • I use the assert_equal function for the fourth assertion in test_positional_arguments

     99    a_tuple = (0, 1, 2, 'n')
    100    a_list = [0, 1, 2, 'n']
    101    # assert (
    102    #     positional_arguments(a_tuple, a_list)
    103    #  == (a_tuple, a_list)
    104    # )
    105    assert_equal(
    106        positional_arguments(a_list, a_tuple),
    107        (a_tuple, a_list)
    108    )
    109
    110
    111# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert ([0, 1, 2, 'n...0, 1, 2, 'n'))
                        == ((0, 1, 2, 'n...0, 1, 2, 'n']
    
  • I change the call to positional_arguments to match the expectation of the fourth assertion in test_positional_arguments

     99    a_tuple = (0, 1, 2, 'n')
    100    a_list = [0, 1, 2, 'n']
    101    # assert (
    102    #     positional_arguments(a_tuple, a_list)
    103    #  == (a_tuple, a_list)
    104    # )
    105    assert_equal(
    106        # positional_arguments(a_list, a_tuple),
    107        positional_arguments(a_tuple, a_list),
    108        (a_tuple, a_list)
    109    )
    110
    111
    112# Exceptions seen
    

    the test passes.

    ├── a_tuple = (0, 1, 2, 'n')
    ├── a_list = [0, 1, 2, 'n']
    └── assert_equal(
            positional_arguments(a_tuple, a_list),
            (a_tuple, a_list)
        ) -> None
        └── def assert_equal(input_1, input_2):
            ├── input_1 = positional_arguments(a_tuple, a_list)
                         └── def positional_arguments(
                                 first_input, last_input
                             ):
                             ├── first_input = a_tuple
                             ├── last_input  = a_list
                             └── return first_input, last_input
                                 return a_tuple    , a_list
            ├── input_2 = (a_tuple, a_list)
            └── assert input_1            == input_2
                assert (a_tuple, a_list)  == (a_tuple, a_list)
    
  • I remove the commented lines from test_positional_arguments

    65def test_positional_arguments():
    66    def positional_arguments(first_input, last_input):
    67        return first_input, last_input
    68
    69    first, last = 'first', 'last'
    70
    71    assert_equal(
    72        positional_arguments(first, last),
    73        (first, last)
    74    )
    75    assert_equal(
    76        positional_arguments(last, first),
    77        (last, first)
    78    )
    
    80    assert_equal(
    81        positional_arguments(0, 1), (0, 1)
    82    )
    83
    84    a_tuple = (0, 1, 2, 'n')
    85    a_list = [0, 1, 2, 'n']
    86    assert_equal(
    87        positional_arguments(a_tuple, a_list),
    88        (a_tuple, a_list)
    89    )
    90
    91
    92# Exceptions
    
  • I use the assert_equal function for the assertions in test_why_use_a_function

    49def test_why_use_a_function():
    50    def add_x(number):
    51        return 3 + number
    52
    53    # assert add_x(0) == 3
    54    assert_equal(add_x(0), 2)
    55    # assert add_x(1) == 4
    56    assert_equal(add_x(1), 3)
    57    # assert add_x(2) == 5
    58    assert_equal(add_x(2), 4)
    59    # assert add_x(3) == 6
    60    assert_equal(add_x(3), 5)
    61    # assert add_x(4) == 7
    62    assert_equal(add_x(4), 6)
    63    # assert add_x(5) == 8
    64    assert_equal(add_x(5), 7)
    65    # assert add_x(6) == 9
    66    assert_equal(add_x(6), 8)
    67    # assert add_x(7) == 10
    68    assert_equal(add_x(7), 9)
    69    # assert add_x(8) == 11
    70    assert_equal(add_x(8), 10)
    71    # assert add_x(9) == 12
    72    assert_equal(add_x(9), 11)
    73
    74
    75def test_positional_arguments():
    

    the terminal is my friend, and shows AssertionError

    E       assert 3 == 2
    
  • I change the expectations of the assertions of test_why_use_a_function

    49def test_why_use_a_function():
    50    def add_x(number):
    51        return 3 + number
    52
    53    # assert add_x(0) == 3
    54    # assert_equal(add_x(0), 2)
    55    assert_equal(add_x(0), 3)
    56    # assert add_x(1) == 4
    57    # assert_equal(add_x(1), 3)
    58    assert_equal(add_x(1), 4)
    59    # assert add_x(2) == 5
    60    # assert_equal(add_x(2), 4)
    61    assert_equal(add_x(2), 5)
    62    # assert add_x(3) == 6
    63    # assert_equal(add_x(3), 5)
    64    assert_equal(add_x(3), 6)
    65    # assert add_x(4) == 7
    66    # assert_equal(add_x(4), 6)
    67    assert_equal(add_x(4), 7)
    68    # assert add_x(5) == 8
    69    # assert_equal(add_x(5), 7)
    70    assert_equal(add_x(5), 8)
    71    # assert add_x(6) == 9
    72    # assert_equal(add_x(6), 8)
    73    assert_equal(add_x(6), 9)
    74    # assert add_x(7) == 10
    75    # assert_equal(add_x(7), 9)
    76    assert_equal(add_x(7), 10)
    77    # assert add_x(8) == 11
    78    # assert_equal(add_x(8), 10)
    79    assert_equal(add_x(8), 11)
    80    # assert add_x(9) == 12
    81    # assert_equal(add_x(9), 11)
    82    assert_equal(add_x(9), 12)
    83
    84
    85def test_positional_arguments():
    

    the test passes.

    assert_equal(add_x(0), 3) -> None
    └── def assert_equal(input_1, input_2):
        ├── input_1 = add_x(0)
                     └── def add_x(number):
                         ├── number = 0
                         └── return 3 + number
                             return 3 + 0
                             return 3
        ├── input_2 = 3
        └── assert input_1 == input_2
            assert 3       == 3
    
  • I remove the commented lines from test_why_use_a_function

    49def test_why_use_a_function():
    50    def add_x(number):
    51        return 3 + number
    52
    53    assert_equal(add_x(0), 3)
    54    assert_equal(add_x(1), 4)
    55    assert_equal(add_x(2), 5)
    56    assert_equal(add_x(3), 6)
    57    assert_equal(add_x(4), 7)
    58    assert_equal(add_x(5), 8)
    59    assert_equal(add_x(6), 9)
    60    assert_equal(add_x(7), 10)
    61    assert_equal(add_x(8), 11)
    62    assert_equal(add_x(9), 12)
    63
    64
    65def test_positional_arguments():
    
  • I use the assert_equal function for the assertions in test_identity_function

    41def test_identity_function():
    42    def identity(the_input):
    43        return the_input
    44
    45    # assert identity(None) == None
    46    assert_equal(identity(None), object)
    47    # assert identity(object) == object
    48    assert_equal(identity(object), None)
    49
    50
    51def test_why_use_a_function():
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert None == <class 'object'>
    
  • I change the expectation of the first assertion in test_identity_function

    41def test_identity_function():
    42    def identity(the_input):
    43        return the_input
    44
    45    # assert identity(None) == None
    46    # assert_equal(identity(None), object)
    47    assert_equal(identity(None), None)
    48    # assert identity(object) == object
    49    assert_equal(identity(object), None)
    50
    51
    52def test_why_use_a_function():
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert <class 'object'> == None
    
  • I change the expectation of the second assertion in test_identity_function

    41def test_identity_function():
    42    def identity(the_input):
    43        return the_input
    44
    45    # assert identity(None) == None
    46    # assert_equal(identity(None), object)
    47    assert_equal(identity(None), None)
    48    # assert identity(object) == object
    49    # assert_equal(identity(object), None)
    50    assert_equal(identity(object), object)
    51
    52
    53def test_why_use_a_function():
    

    the test passes.

    assert_equal(identity(object), object) -> None
    └── def assert_equal(input_1, input_2):
        ├── input_1 = identity(object)
                     └── def identity(the_input):
                         ├── the_input = object
                         └── return the_input
                             return object
        ├── input_2 = object
        └── assert input_1 == input_2
            assert object  == object
    
  • I remove the commented lines from test_identity_function

    41def test_identity_function():
    42    def identity(the_input):
    43        return the_input
    44
    45    assert_equal(identity(None), None)
    46    assert_equal(identity(object), object)
    47
    48
    49def test_why_use_a_function():
    
  • I use the assert_equal function in test_constant_function

    34def test_constant_function():
    35    def constant():
    36        return 'the same thing'
    37
    38    # assert constant() == 'the same thing'
    39    assert_equal(constant(), 'not the same thing')
    40
    41
    42def test_identity_function():
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert 'the same thing'
                        == 'not the same thing'
    
  • I change the expectation to match reality in test_constant_function

    34def test_constant_function():
    35    def constant():
    36        return 'the same thing'
    37
    38    # assert constant() == 'the same thing'
    39    # assert_equal(constant(), 'not the same thing')
    40    assert_equal(constant(), 'the same thing')
    41
    42
    43def test_identity_function():
    

    the test passes.

    assert_equal(constant(), 'the same thing') -> None
    └── def assert_equal(input_1, input_2):
        ├── input_1 = constant()
                     └── def constant():
                         └── return 'the same thing'
        ├── input_2 = 'the same thing'
        └── assert input_1          == input_2
            assert 'the same thing' == 'the same thing'
    
  • I remove the commented lines from test_identity_function

    34def test_constant_function():
    35    def constant():
    36        return 'the same thing'
    37
    38    assert_equal(constant(), 'the same thing')
    39
    40
    41def test_identity_function():
    
  • I add a git commit message

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

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

I can use a function to assert if two things are equal.


extract assert_is_none function

The assertions in test_what_happens_after_functions_return, test_making_a_function_w_return_none, test_making_a_function_w_return and test_making_a_function_w_pass are the same, they check if the result of a function call is the same object as None.

assert function() is None

I can use a function to assert if something is None.


RED: make it fail


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

  • I add a function named assert_is_none that takes one input and asserts if the input is None

    1def assert_equal(input_1, input_2):
    2    assert input_1 == input_2
    3
    4
    5def assert_is_none(something):
    6    assert something is None
    7
    8
    9def test_making_a_function_w_pass():
    
  • I use the new function for the assertion in test_making_a_function_w_pass

     9def test_making_a_function_w_pass():
    10    def w_pass():
    11        pass
    12
    13    # assert w_pass() is None
    14    assert_is_none(w_pass)
    15
    16
    17def test_making_a_function_w_return():
    

    the terminal is my friend, and shows AssertionError

    assert <function test_making_a_function_w_pass
                .<locals>.w_pass
            at 0xffffa76b5432>
        is None
    

    because I just passed the function, I did not call it.


GREEN: make it pass


I call the w_pass function in the assertion

 9  def test_making_a_function_w_pass():
10      def w_pass():
11          pass
12
13      # assert w_pass() is None
14      # assert_is_none(w_pass)
15      assert_is_none(w_pass())
16
17
18  def test_making_a_function_w_return():

the test passes.

assert_is_none(w_pass()) -> None
└── def assert_is_none(something):
        ├── something = w_pass()
                       └── def w_pass():
                           └── pass
                               return None
        └── assert something is None
            assert None      is None

REFACTOR: make it better


  • I remove the commented lines from test_making_a_function_w_pass

     9def test_making_a_function_w_pass():
    10    def w_pass():
    11        pass
    12
    13    assert_is_none(w_pass())
    14
    15
    16def test_making_a_function_w_return():
    
  • I use the assert_is_none function for the assertion in test_making_a_function_w_return

    16def test_making_a_function_w_return():
    17    def w_return():
    18        return
    19
    20    # assert w_return() is None
    21    assert_is_none(w_return)
    22
    23
    24def test_making_a_function_w_return_none():
    

    the terminal is my friend, and shows AssertionError

    assert <function test_making_a_function_w_return
                .<locals>.w_return
            at 0xffff7e654321>
        is None
    
  • I call the w_return function in the assertion

    16def test_making_a_function_w_return():
    17    def w_return():
    18        return
    19
    20    # assert w_return() is None
    21    # assert_is_none(w_return)
    22    assert_is_none(w_return())
    23
    24
    25def test_making_a_function_w_return_none():
    

    the test passes.

    assert_is_none(w_return()) -> None
    └── def assert_is_none(something):
            ├── something = w_return()
                           └── def w_return():
                               └── return
                                   return None
            └── assert something is None
                assert None      is None
    
  • I remove the commented lines from test_making_a_function_w_return

    16def test_making_a_function_w_return():
    17    def w_return():
    18        return
    19
    20    assert_is_none(w_return())
    21
    22
    23def test_making_a_function_w_return_none():
    
  • I use the assert_is_none function in test_making_a_function_w_return_none

    23def test_making_a_function_w_return_none():
    24    def w_return_none():
    25        return None
    26
    27    # assert w_return_none() is None
    28    assert_is_none(w_return_none)
    29
    30
    31def test_what_happens_after_functions_return():
    

    the terminal is my friend, and shows AssertionError

    assert <function test_making_a_function_w_return_none
                .<locals>.w_return_none
            at 0xffffa1234567>
          is None
    
  • I call the w_return_none function in the assertion

    23def test_making_a_function_w_return_none():
    24    def w_return_none():
    25        return None
    26
    27    # assert w_return_none() is None
    28    # assert_is_none(w_return_none)
    29    assert_is_none(w_return_none())
    30
    31
    32def test_what_happens_after_functions_return():
    

    the test passes.

    assert_is_none(w_return_none()) -> None
    └── def assert_is_none(something):
            ├── something = w_return_none()
                           └── def w_return_none():
                               └── return None
            └── assert something is None
                assert None      is None
    
  • I remove the commented lines from test_making_a_function_w_return_none

    23def test_making_a_function_w_return_none():
    24    def w_return_none():
    25        return None
    26
    27    assert_is_none(w_return_none())
    28
    29
    30def test_what_happens_after_functions_return():
    
  • I use the assert_is_none function in test_what_happens_after_functions_return

    30def test_what_happens_after_functions_return():
    31    def return_leaves_the_function():
    32        return None
    33        return 'only one way for this line to run'
    34
    35    # assert return_leaves_the_function() is None
    36    assert_is_none(return_leaves_the_function)
    37
    38
    39def test_constant_function():
    

    the terminal is my friend, and shows AssertionError

    assert <function test_what_happens_after_functions_return
                .<locals>.return_leaves_the_function
            at 0xffffa01b2345>
        is None
    
  • I call the return_leaves_the_function function in the assertion

    30def test_what_happens_after_functions_return():
    31    def return_leaves_the_function():
    32        return None
    33        return 'only one way for this line to run'
    34
    35    # assert return_leaves_the_function() is None
    36    # assert_is_none(return_leaves_the_function)
    37    assert_is_none(return_leaves_the_function())
    38
    39
    40def test_constant_function():
    

    the test passes.

    assert_is_none(return_leaves_the_function()) -> None
    └── def assert_is_none(something):
            ├── something = return_leaves_the_function()
                           └── def return_leaves_the_function():
                               └── return None
            └── assert something is None
                assert None      is None
    
  • I remove the commented lines from test_what_happens_after_functions_return

    30def test_what_happens_after_functions_return():
    31    def return_leaves_the_function():
    32        return None
    33        return 'only one way for this line to run'
    34
    35    assert_is_none(return_leaves_the_function())
    36
    37
    38def test_constant_function():
    
  • I use the assert_is_none function in test_identity_function for the assertion that has None as its expectation

    45def test_identity_function():
    46    def identity(the_input):
    47        return the_input
    48
    49    # assert_equal(identity(None), None)
    50    assert_is_none(identity(object))
    51    assert_equal(identity(object), object)
    52
    53
    54def test_why_use_a_function():
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert <class 'object'> is None
    
  • I change the input in call to the identity function from object to None

    45def test_identity_function():
    46    def identity(the_input):
    47        return the_input
    48
    49    # assert_equal(identity(None), None)
    50    # assert_is_none(identity(object))
    51    assert_is_none(identity(None))
    52    assert_equal(identity(object), object)
    53
    54
    55def test_why_use_a_function():
    

    the test passes.

    assert_is_none(identity(None)) -> None
    └── def assert_is_none(something):
            ├── something = identity(None)
                           └── def identity(the_input):
                               ├── the_input = None
                               └── return the_input
            └── assert something is None
                assert None      is None
    
  • I remove the commented lines from test_identity_function

    45def test_identity_function():
    46    def identity(the_input):
    47        return the_input
    48
    49    assert_is_none(identity(None))
    50    assert_equal(identity(object), object)
    51
    52
    53def test_why_use_a_function():
    
  • I add a git commit message

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

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

I can use a function to assert if something is None.


test_keyword_arguments

test_positional_arguments shows that positional arguments must always be given in the right order which is a problem if I forget the order, especially if there are many inputs.

Another way to call a function is to use Keyword Arguments to make sure the function always gets the values for the inputs it expects without worrying about the order.


RED: make it fail


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

  • I add a test for keyword arguments to test_functions.py

     88    a_tuple = (0, 1, 2, 'n')
     89    a_list = [0, 1, 2, 'n']
     90    assert_equal(
     91        positional_arguments(a_tuple, a_list),
     92        (a_tuple, a_list)
     93    )
     94
     95
     96def test_keyword_arguments():
     97    assert_equal(keyword_arguments(), None)
     98
     99
    100# Exceptions seen
    

    the terminal is my friend, and shows NameError

    NameError: name 'keyword_arguments' is not defined
    

    because there is no definition for keyword_arguments in this file.


GREEN: make it pass


I add a function definition

 96  def test_keyword_arguments():
 97      def keyword_arguments():
 98          return None
 99
100      assert_equal(keyword_arguments(), None)
101
102
103  # Exceptions seen

the test passes.

keyword_arguments() -> None
└── def keyword_arguments():
    └── return None

what is a keyword argument?

A keyword argument is a key-value pair that is used to pass input in a function call. Where key is a name, and the value is any object the function accepts.


REFACTOR: make it better


  • I add input to the function call with a name

     96def test_keyword_arguments():
     97    def keyword_arguments():
     98        return None
     99
    100    # assert_equal(keyword_arguments(), None)
    101    assert_equal(
    102        keyword_arguments(first_input='first'), None
    103    )
    104
    105
    106# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError:
        test_keyword_arguments.<locals>.keyword_arguments()
        got an unexpected keyword argument 'first_input'
    

    because

  • I add a name in parentheses to make the function take input

     96def test_keyword_arguments():
     97    # def keyword_arguments():
     98    def keyword_arguments(the_input):
     99        return None
    100
    101    # assert_equal(keyword_arguments(), None)
    102    assert_equal(
    103        keyword_arguments(first_input='first'), None
    104    )
    105
    106
    107# Exceptions seen
    

    the terminal still shows TypeError because the names in the function call and function definition are different.

  • I change the name of the input in the function definition (the_input) to match the name used in the function call (first_input)

     96def test_keyword_arguments():
     97    # def keyword_arguments():
     98    # def keyword_arguments(the_input):
     99    def keyword_arguments(first_input):
    100        return None
    101
    102    # assert_equal(keyword_arguments(), None)
    103    assert_equal(
    104        keyword_arguments(first_input='first'), None
    105    )
    106
    107
    108# Exceptions seen
    

    the test passes because the keyword I used to call the function matches the name in the function definition.

    keyword_arguments(first_input='first') -> None
    └── def keyword_arguments(first_input):
        └── return None
    
  • I add another keyword argument to the function call in test_keyword_arguments

     96def test_keyword_arguments():
     97    # def keyword_arguments():
     98    # def keyword_arguments(the_input):
     99    def keyword_arguments(first_input):
    100        return None
    101
    102    # assert_equal(keyword_arguments(), None)
    103    assert_equal(
    104        # keyword_arguments(first_input='first'), None
    105        keyword_arguments(
    106            first_input='first', last_input='last',
    107        ),
    108        None
    109    )
    110
    111
    112# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError:
        test_keyword_arguments.<locals>.keyword_arguments()
        got an unexpected keyword argument 'last_input'.
        Did you mean 'first_input'?
    

    because

  • I make the function take two inputs by adding another name in parentheses

     96def test_keyword_arguments():
     97    # def keyword_arguments():
     98    # def keyword_arguments(the_input):
     99    # def keyword_arguments(first_input):
    100    def keyword_arguments(first_input, second_input):
    101        return None
    102
    103    # assert_equal(keyword_arguments(), None)
    104    assert_equal(
    105        # keyword_arguments(first_input='first'), None
    106        keyword_arguments(
    107            first_input='first', last_input='last',
    108        ),
    109        None
    110    )
    111
    112
    113# Exceptions seen
    

    the terminal still shows TypeError because the names in the function call and function definition are different.

  • I change the name of the input in the function definition (second_input) to match the name used in the function call (last_input)

     96def test_keyword_arguments():
     97    # def keyword_arguments():
     98    # def keyword_arguments(the_input):
     99    # def keyword_arguments(first_input):
    100    # def keyword_arguments(first_input, second_input):
    101    def keyword_arguments(first_input, last_input):
    102        return None
    103
    104    # assert_equal(keyword_arguments(), None)
    105    assert_equal(
    106        # keyword_arguments(first_input='first'), None
    107        keyword_arguments(
    108            first_input='first', last_input='last',
    109        ),
    110        None
    111    )
    112
    113
    114# Exceptions seen
    

    the test passes because the keywords I used to call the function match the names in the function definition.

    keyword_arguments(
        first_input='first', last_input='last'
    ) -> None
    └── def keyword_arguments(first_input, last_input):
        └── return None
    
  • I change the expectation of the assertion

     96def test_keyword_arguments():
     97    # def keyword_arguments():
     98    # def keyword_arguments(the_input):
     99    # def keyword_arguments(first_input):
    100    # def keyword_arguments(first_input, second_input):
    101    def keyword_arguments(first_input, last_input):
    102        return None
    103
    104    # assert_equal(keyword_arguments(), None)
    105    assert_equal(
    106        # keyword_arguments(first_input='first'), None
    107        keyword_arguments(
    108            first_input='first', last_input='last',
    109        ),
    110        # None
    111        ('first', 'last')
    112    )
    113
    114
    115# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

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

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

    assert_equal(
        keyword_arguments(
            first_input='first', last_input='last',
        ),
        ('first', 'last')
    ) -> None
    └── def assert_equal(input_1, input_2):
        ├── input_1 = keyword_arguments(
                         first_input='first', last_input='last',
                     )
                     └── def keyword_arguments(
                             first_input, last_input
                         ):
                         └── return None
        ├── input_2 = ('first', 'last')
        └── assert input_1 == input_2
            assert None    == ('first', 'last')
    

    which raises AssertionError since None is NOT equal to a tuple.

  • I change the return statement to make the function return its inputs as output (like the identity function)

     96def test_keyword_arguments():
     97    # def keyword_arguments():
     98    # def keyword_arguments(the_input):
     99    # def keyword_arguments(first_input):
    100    # def keyword_arguments(first_input, second_input):
    101    def keyword_arguments(first_input, last_input):
    102        # return None
    103        return first_input, last_input
    104
    105    # assert_equal(keyword_arguments(), None)
    106    assert_equal(
    107        # keyword_arguments(first_input='first'), None
    108        keyword_arguments(
    109            first_input='first', last_input='last',
    110        ),
    111        # None
    112        ('first', 'last')
    113    )
    114
    115
    116# Exceptions seen
    

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

    keyword_arguments(
        first_input='first', last_input='last'
    ) -> ('first', 'last')
    └── def keyword_arguments(first_input, last_input):
        ├── first_input = 'first'
        ├── last_input  = 'last'
        └── return first_input, last_input
            return 'first'    , 'last'
    
  • The bad thing about giving arguments this way, is I must use the exact names in the function definition when I make a call to the function. The good thing about giving arguments this way is that the names do not have to match the order in the function definition. I add an assertion with the keyword arguments given out of order

    105    # assert_equal(keyword_arguments(), None)
    106    assert_equal(
    107        # keyword_arguments(first_input='first'), None
    108        keyword_arguments(
    109            first_input='first', last_input='last',
    110        ),
    111        # None
    112        ('first', 'last')
    113    )
    114    assert_equal(
    115        keyword_arguments(
    116            last_input='last', first_input='first',
    117        ),
    118        ('last', 'first')
    119    )
    120
    121
    122# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

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

    because the function always returns first_input, last_input and this test calls the function with 'last' as last_input and 'first' as first_input, the order does not matter because I used the names.

    assert_equal(
        keyword_arguments(
            last_input='last', first_input='first',
        ),
        ('last', 'first')
    ) -> None
    └── def assert_equal(input_1, input_2):
        ├── input_1 = keyword_arguments(
                         last_input='last', first_input='first',
                     )
                     └── def keyword_arguments(
                             first_input, last_input
                         ):
                         ├── first_input = 'first'
                         ├── last_input  = 'last'
                         └── return first_input, last_input
                             return 'first'    , 'last'
        ├── input_2 = ('last', 'first')
        └── assert input_1           == input_2
            assert ('first', 'last') == ('last', 'first')
    
  • I change my expectation to match reality

    105    # assert_equal(keyword_arguments(), None)
    106    assert_equal(
    107        # keyword_arguments(first_input='first'), None
    108        keyword_arguments(
    109            first_input='first', last_input='last',
    110        ),
    111        # None
    112        ('first', 'last')
    113    )
    114    assert_equal(
    115        keyword_arguments(
    116            last_input='last', first_input='first',
    117        ),
    118        # ('last', 'first')
    119        ('first', 'last')
    120    )
    121
    122
    123# Exceptions seen
    

    the test passes.

    keyword_arguments(
        last_input='last', first_input='first'
    ) -> ('first', 'last')
    └── def keyword_arguments(first_input, last_input):
        ├── first_input = 'first'
        ├── last_input  = 'last'
        └── return first_input, last_input
            return 'first'    , 'last'
    
  • I add variables for 'first' and 'last' in test_keyword_arguments

     96def test_keyword_arguments():
     97    # def keyword_arguments():
     98    # def keyword_arguments(the_input):
     99    # def keyword_arguments(first_input):
    100    # def keyword_arguments(first_input, second_input):
    101    def keyword_arguments(first_input, last_input):
    102        # return None
    103        return first_input, last_input
    104
    105    first, last = 'first', 'last'
    106
    107    # assert_equal(keyword_arguments(), None)
    
  • I use the variables to remove repetition of 'first' and 'last' from test_keyword_arguments

    107    # assert_equal(keyword_arguments(), None)
    108    assert_equal(
    109        # keyword_arguments(first_input='first'), None
    110        keyword_arguments(
    111            # first_input='first', last_input='last',
    112            first_input=first, last_input=last,
    113        ),
    114        # None
    115        # ('first', 'last')
    116        (first, last)
    117    )
    118    assert_equal(
    119        keyword_arguments(
    120            # last_input='last', first_input='first',
    121            last_input=last, first_input=first,
    122        ),
    123        # ('last', 'first')
    124        # ('first', 'last')
    125        (first, last)
    126    )
    127
    128
    129# Exceptions seen
    

    the test is still green.

  • I add another assertion to test_keyword_arguments

    118    assert_equal(
    119        keyword_arguments(
    120            # last_input='last', first_input='first',
    121            last_input=last, first_input=first,
    122        ),
    123        # ('last', 'first')
    124        # ('first', 'last')
    125        (first, last)
    126    )
    127
    128    assert_equal(
    129        keyword_arguments(
    130            last_input=0, first_input=1,
    131        ),
    132        (0, 1)
    133    )
    134
    135
    136# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

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

    because the function always returns first_input, last_input and this test calls the function with 0 as last_input and 1 as first_input, the order does not matter because I used the names.

    assert_equal(
        keyword_arguments(
            last_input=0, first_input=1,
        ),
        (0, 1)
    ) -> None
    └── def assert_equal(input_1, input_2):
        ├── input_1 = keyword_arguments(
                         last_input=0, first_input=1,
                     )
                     └── def keyword_arguments(
                             first_input, last_input
                         ):
                         ├── first_input = 1
                         ├── last_input  = 0
                         └── return first_input, last_input
                             return 1          , 0
        ├── input_2 = (0, 1)
        └── assert input_1 == input_2
            assert (1, 0)  == (0, 1)
    
  • I change my expectation to match reality

    118    assert_equal(
    119        keyword_arguments(
    120            # last_input='last', first_input='first',
    121            last_input=last, first_input=first,
    122        ),
    123        # ('last', 'first')
    124        # ('first', 'last')
    125        (first, last)
    126    )
    127
    128    assert_equal(
    129        keyword_arguments(
    130            last_input=0, first_input=1,
    131        ),
    132        # (0, 1)
    133        (1, 0)
    134    )
    135
    136
    137# Exceptions seen
    

    the test passes.

    keyword_arguments(
        last_input=0, first_input=1
    ) -> (1, 0)
    def keyword_arguments(first_input, last_input):
    ├── first_input = 1
    ├── last_input  = 0
    └── return first_input, last_input
        return 1          , 0
    
  • I add an assertion to test_keyword_arguments with a tuple and a list

    128    assert_equal(
    129        keyword_arguments(
    130            last_input=0, first_input=1,
    131        ),
    132        # (0, 1)
    133        (1, 0)
    134    )
    135
    136    a_tuple = (0, 1, 2, 'n')
    137    a_list = [0, 1, 2, 'n']
    138    assert_equal(
    139        keyword_arguments(
    140            first_input=a_list,
    141            last_input=a_tuple,
    142        ),
    143        (a_tuple, a_list)
    144    )
    145
    146
    147# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

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

    because the function always returns first_input, last_input and this test calls the function with [0, 1, 2, 'n'] as first_input and (0, 1, 2, 'n') as last_input, the order does not matter because I used the names

    ├── a_tuple = (0, 1, 2, 'n')
    ├── a_list = [0, 1, 2, 'n']
    └── assert_equal(
            keyword_arguments(
                first_input=a_list, last_input=a_tuple,
            ),
            (a_tuple, a_list)
        ) -> None
        └── def assert_equal(input_1, input_2):
            ├── input_1 = keyword_arguments(
                             first_input=a_list,
                             last_input=a_tuple,
                         )
                         └── def keyword_arguments(
                                 first_input, last_input
                             ):
                             ├── first_input = a_list
                             ├── last_input  = a_tuple
                             └── return first_input, last_input
                                 return a_list     , a_tuple
            ├── input_2 = (a_tuple, a_list)
            └── assert input_1            == input_2
                assert (a_list, a_tuple)  == (a_tuple, a_list)
    
  • I change reality to match my expectation

    128    assert_equal(
    129        keyword_arguments(
    130            last_input=0, first_input=1,
    131        ),
    132        # (0, 1)
    133        (1, 0)
    134    )
    135
    136    a_tuple = (0, 1, 2, 'n')
    137    a_list = [0, 1, 2, 'n']
    138    assert_equal(
    139        keyword_arguments(
    140            # first_input=a_list,
    141            # last_input=a_tuple,
    142            first_input=a_tuple,
    143            last_input=a_list,
    144        ),
    145        (a_tuple, a_list)
    146    )
    147
    148
    149# Exceptions seen
    

    the test passes.

    ├── a_tuple = (0, 1, 2, 'n')
    ├── a_list = [0, 1, 2, 'n']
    └── keyword_arguments(
            first_input=a_list,
            last_input=a_tuple,
        ) -> (a_tuple, a_list)
        └── def keyword_arguments(first_input, last_input):
            ├── first_input = a_list
            ├── last_input  = a_tuple
            └── return first_input, last_input
                return a_tuple    , a_list
    
  • keyword_arguments and positional_arguments are the same function in test_functions.py, they always

    return first_input, last_input
    

    Their names are different

    def positional_arguments(first_input, last_input):
    def keyword_arguments(first_input, last_input):
    

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

    • I have to give the input in order when I use positional arguments because I do NOT use the names from the function definition when I call it

      positional_arguments('first', 'last')
                -> return ('first', 'last')
      
      positional_arguments('last', 'first')
                -> return ('last', 'first')
      
      positional_arguments(0, 1)
                -> return (0, 1)
      
      positional_arguments((0, 1, 2, 'n'), [0, 1, 2, 'n'])
                -> return ((0, 1, 2, 'n'), [0, 1, 2, 'n'])
      
      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

      keyword_arguments(
          first_input='first', last_input='last',
      )
      -> return ('first', 'last')
      
      keyword_arguments(
          last_input='last', first_input='first',
      )
      -> return ('first', 'last')
      
      keyword_arguments(last_input=0, first_input=1)
      -> return (1, 0)
      
      keyword_arguments(
          first_input=(0, 1, 2, 'n'),
          last_input=[0, 1, 2, 'n'],
      )
      -> return ((0, 1, 2, 'n'), [0, 1, 2, 'n'])
      

    I call the positional_arguments function with keyword arguments to show that the two functions are the same

    136    a_tuple = (0, 1, 2, 'n')
    137    a_list = [0, 1, 2, 'n']
    138    assert_equal(
    139        keyword_arguments(
    140            # first_input=a_list,
    141            # last_input=a_tuple,
    142            first_input=a_tuple,
    143            last_input=a_list,
    144        ),
    145        (a_tuple, a_list)
    146    )
    147
    148    a_set = {0, 1, 2, 'n'}
    149    a_dictionary = {'key': 'value'}
    150    assert_equal(
    151        positional_arguments(
    152            last_input=a_dictionary,
    153            first_input=a_set,
    154        ),
    155        (a_set, a_dictionary)
    156    )
    157
    158
    159# Exceptions seen
    

    the terminal is my friend, and shows NameError

    NameError: name 'positional_arguments' is not defined
    

    because the positional_arguments function belongs to the test_positional_arguments function and I cannot reach it from outside test_positional_arguments.

  • I move the positional_arguments function out of test_positional_arguments so that it can be called from anywhere in the file

    66    assert_equal(add_x(9), 12)
    67
    68
    69def positional_arguments(first_input, last_input):
    70    return first_input, last_input
    71
    72
    73def test_positional_arguments():
    74    first, last = 'first', 'last'
    75
    76    assert_equal(
    77        positional_arguments(first, last),
    78        (first, last)
    79    )
    80    assert_equal(
    81        positional_arguments(last, first),
    82        (last, first)
    83    )
    84
    85    assert_equal(
    86        positional_arguments(0, 1), (0, 1)
    87    )
    88
    89    a_tuple = (0, 1, 2, 'n')
    

    the test passes because these two calls are the same

    positional_arguments(
        last_input=a_dictionary,
        first_input=a_set,
    )
    
    positional_arguments(
        a_set, a_dictionary,
    )
    
    • When positional_arguments(last_input=a_dictionary, first_input=a_set) runs

      positional_arguments(
          last_input=a_dictionary,
          first_input=a_set,
      ) -> (a_set, a_dictionary)
      └── def positional_arguments(first_input, last_input):
          ├── first_input = a_set
          ├── last_input = a_dictionary
          └── return first_input, last_input
              return a_set      , a_dictionary
      
    • When positional_arguments(a_set, a_dictionary) runs

      positional_arguments(
          a_set, a_dictionary
      ) -> (a_set, a_dictionary)
      └── def positional_arguments(first_input, last_input):
          ├── first_input = a_set
          ├── last_input = a_dictionary
          └── return first_input, last_input
              return a_set      , a_dictionary
      
  • I add an assertion to test_positional_arguments to show that I can call the keyword_arguments function with positional arguments

     89    a_tuple = (0, 1, 2, 'n')
     90    a_list = [0, 1, 2, 'n']
     91    assert_equal(
     92        positional_arguments(a_tuple, a_list),
     93        (a_tuple, a_list)
     94    )
     95
     96    a_set = {0, 1, 2, 'n'}
     97    a_dictionary = {'key': 'value'}
     98    assert_equal(
     99        keyword_arguments(
    100            a_set, a_dictionary,
    101        ),
    102        (a_set, a_dictionary)
    103    )
    104
    105
    106def test_keyword_arguments():
    

    the terminal is my friend, and shows NameError

    NameError: name 'keyword_arguments' is not defined
    

    because the keyword_arguments function belongs to the test_keyword_arguments function and I cannot reach it from outside test_keyword_arguments, yet.

  • I move the keyword_arguments function out of test_keyword_arguments so that it can be called from anywhere in the file

     96    a_set = {0, 1, 2, 'n'}
     97    a_dictionary = {'key': 'value'}
     98    assert_equal(
     99        keyword_arguments(
    100            a_set, a_dictionary,
    101        ),
    102        (a_set, a_dictionary)
    103    )
    104
    105
    106def keyword_arguments(first_input, last_input):
    107    return first_input, last_input
    108
    109
    110def test_keyword_arguments():
    111    # def keyword_arguments():
    112    # def keyword_arguments(the_input):
    113    # def keyword_arguments(first_input):
    114    # def keyword_arguments(first_input, second_input):
    115        # return None
    

    the test passes because these two calls are the same

    keyword_arguments(
        a_set, a_dictionary,
    )
    
    keyword_arguments(
        last_input=a_dictionary,
        first_input=a_set,
    )
    
    • When keyword_arguments(a_set, a_dictionary) runs

      keyword_arguments(
          a_set, a_dictionary
      ) -> (a_set, a_dictionary)
      └── def keyword_arguments(first_input, last_input):
          ├── first_input = a_set
          ├── last_input  = a_dictionary
          └── return first_input, last_input
              return a_set      , a_dictionary
      
    • When keyword_arguments(last_input=a_dictionary, first_input=a_set) runs

      keyword_arguments(
          last_input=a_dictionary,
          first_input=a_set,
      ) -> (a_set, a_dictionary)
      └── def keyword_arguments(first_input, last_input):
          ├── first_input = a_set
          ├── last_input  = a_dictionary
          └── return first_input, last_input
              return a_set      , a_dictionary
      
  • I remove the commented lines from test_keyword_arguments

    110def test_keyword_arguments():
    111    first, last = 'first', 'last'
    112
    113    assert_equal(
    114        keyword_arguments(
    115            first_input=first, last_input=last,
    116        ),
    117        (first, last)
    118    )
    119    assert_equal(
    120        keyword_arguments(
    121            last_input=last, first_input=first,
    122        ),
    123        (first, last)
    124    )
    
    126    assert_equal(
    127        keyword_arguments(
    128            last_input=0, first_input=1,
    129        ),
    130        (1, 0)
    131    )
    
    133    a_tuple = (0, 1, 2, 'n')
    134    a_list = [0, 1, 2, 'n']
    135    assert_equal(
    136        keyword_arguments(
    137            first_input=a_tuple,
    138            last_input=a_list,
    139        ),
    140        (a_tuple, a_list)
    141    )
    
    143    a_set = {0, 1, 2, 'n'}
    144    a_dictionary = {'key': 'value'}
    145    assert_equal(
    146        positional_arguments(
    147            last_input=a_dictionary,
    148            first_input=a_set,
    149        ),
    150        (a_set, a_dictionary)
    151    )
    152
    153
    154# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit --all --message \
    'add test_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_args_and_kwargs

Can I call a function with both positional and keyword arguments?


RED: make it fail


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

  • I add a test

    143    a_set = {0, 1, 2, 'n'}
    144    a_dictionary = {'key': 'value'}
    145    assert_equal(
    146        positional_arguments(
    147            last_input=a_dictionary,
    148            first_input=a_set,
    149        ),
    150        (a_set, a_dictionary)
    151    )
    152
    153
    154def test_args_and_kwargs():
    155    assert_equal(
    156        args_and_kwargs(
    157            last_input='last', 'first',
    158        ),
    159        ('first', 'last')
    160    )
    161
    162
    163# 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.


GREEN: make it pass


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

    163# Exceptions seen
    164# AssertionError
    165# NameError
    166# TypeError
    167# SyntaxError
    
  • I change the order of the arguments to follow Python rules

    154def test_args_and_kwargs():
    155    assert_equal(
    156        args_and_kwargs(
    157            # last_input='last', 'first',
    158            'first', last_input='last'
    159        ),
    160        ('first', 'last')
    161    )
    162
    163
    164# Exceptions seen
    

    the terminal is my friend, and shows NameError

    NameError: name 'args_and_kwargs' is not defined
    

    because I have not given a definition for the name yet.

  • I add a function definition for args_and_kwargs

    154def test_args_and_kwargs():
    155    def args_and_kwargs():
    156        return None
    157
    158    assert_equal(
    159        args_and_kwargs(
    160            # last_input='last', 'first',
    161            'first', last_input='last'
    162        ),
    163        ('first', 'last')
    164    )
    165
    166
    167# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError:
        test_args_and_kwargs.<locals>.args_and_kwargs()
        got an unexpected keyword argument 'last_input'
    

    because

  • I add last_input to the parentheses of args_and_kwargs

    154def test_args_and_kwargs():
    155    # def args_and_kwargs():
    156    def args_and_kwargs(last_input):
    157        return None
    158
    159    assert_equal(
    160        args_and_kwargs(
    161            # last_input='last', 'first',
    162            'first', last_input='last'
    163        ),
    164        ('first', 'last')
    165    )
    166
    167
    168# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError:
        test_args_and_kwargs.<locals>.args_and_kwargs()
        got multiple values for argument 'last_input'
    

    because

  • I add first_input to the parentheses of args_and_kwargs to make it clearer

    154def test_args_and_kwargs():
    155    # def args_and_kwargs():
    156    # def args_and_kwargs(last_input):
    157    def args_and_kwargs(last_input, first_input):
    158        return None
    159
    160    assert_equal(
    161        args_and_kwargs(
    162            # last_input='last', 'first',
    163            'first', last_input='last'
    164        ),
    165        ('first', 'last')
    166    )
    167
    168
    169# Exceptions seen
    

    the terminal still shows TypeError because I have not fixed the problem, the call has confusing values. Python cannot tell the difference between the two values because I gave a positional argument (first), the function definition has the last_input parameter in the first position, and I gave a value with the name last_input.

    args_and_kwargs_arguments('first', last_input='last',)
    └── def args_and_kwargs_arguments(last_input, first_input):
        ├── last_input = 'first' ?
        ├── last_input = 'last'  ?
        └── first_input = ?
    
  • The call gives the values for last_input as both 'first' and 'last', it would be like defining the function with the same name twice

    154def test_args_and_kwargs():
    155    # def args_and_kwargs():
    156    # def args_and_kwargs(last_input):
    157    # def args_and_kwargs(last_input, first_input):
    158    def args_and_kwargs(last_input, last_input):
    159        return None
    

    the terminal is my friend, and shows SyntaxError

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

    154def test_args_and_kwargs():
    155    # def args_and_kwargs():
    156    # def args_and_kwargs(last_input):
    157    # def args_and_kwargs(last_input, first_input):
    158    # def args_and_kwargs(last_input, last_input):
    159    def args_and_kwargs(first_input, last_input):
    160        return None
    

    the terminal is my friend, and shows AssertionError

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

    because when I call args_and_kwargs with 'first' and last_input='last' as inputs, it returns None which raises AssertionError since None is NOT equal to a tuple.

    ├── a_tuple = (0, 1, 2, 'n')
    ├── a_list = [0, 1, 2, 'n']
    └── assert_equal(
            args_and_kwargs(
                'first', last_input='last'
            ),
            ('first', 'last')
        ) -> None
        └── def assert_equal(input_1, input_2):
            ├── input_1 = args_and_kwargs(
                             'first', last_input='last'
                         )
                         └── def args_and_kwargs(
                                 first_input, last_input
                             ):
                             └── return None
            ├── input_2 = ('first', 'last')
            └── assert input_1 == input_2
                assert None    == ('first', 'last')
    
  • I change the return statement to give the test what it wants

    154def test_args_and_kwargs():
    155    # def args_and_kwargs():
    156    # def args_and_kwargs(last_input):
    157    # def args_and_kwargs(last_input, first_input):
    158    # def args_and_kwargs(last_input, last_input):
    159    def args_and_kwargs(first_input, last_input):
    160        # return None
    161        return first_input, last_input
    

    the test passes.

    args_and_kwargs(
        'first', last_input='last',
    ) -> ('first', 'last')
    └── def args_and_kwargs(first_input, last_input):
        └── return first_input, last_input
            return 'first'    , 'last'
    
  • I add variables for 'first' and 'last' in test_args_and_kwargs

    154def test_args_and_kwargs():
    155    # def args_and_kwargs():
    156    # def args_and_kwargs(last_input):
    157    # def args_and_kwargs(last_input, first_input):
    158    # def args_and_kwargs(last_input, last_input):
    159    def args_and_kwargs(first_input, last_input):
    160        # return None
    161        return first_input, last_input
    162
    163    first, last = 'first', 'last'
    164
    165    assert_equal(
    166        args_and_kwargs(
    167            # last_input='last', 'first',
    168            'first', last_input='last'
    169        ),
    170        ('first', 'last')
    171    )
    172
    173
    174# Exceptions seen
    
  • I use the new variables to remove repetition of 'first' and 'last' from test_args_and_kwargs

    163    first, last = 'first', 'last'
    164
    165    assert_equal(
    166        args_and_kwargs(
    167            # last_input='last', 'first',
    168            # 'first', last_input='last'
    169            first, last_input=last
    170        ),
    171        # ('first', 'last')
    172        (first, last)
    173    )
    174
    175
    176# Exceptions seen
    

    the test is still green.

  • I remove the commented lines from test_args_and_kwargs

    154def test_args_and_kwargs():
    155    def args_and_kwargs(first_input, last_input):
    156        return first_input, last_input
    157
    158    first, last = 'first', 'last'
    159
    160    assert_equal(
    161        args_and_kwargs(
    162            first, last_input=last
    163        ),
    164        (first, last)
    165    )
    166
    167
    168# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit --all --message \
    'add test_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_optional_arguments

I can make an argument of a function optional, which means a value does not need to be given for it when the function is called.


RED: make it fail


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

  • I add a test to test_functions.py

    160    assert_equal(
    161        args_and_kwargs(
    162            first, last_input=last
    163        ),
    164        (first, last)
    165    )
    166
    167
    168def test_optional_arguments():
    169    first_name, last_name = 'jane', 'doe'
    170    assert_equal(
    171        optional_arguments(
    172            first_name, last_input=last_name,
    173        ),
    174        (first_name, last_name)
    175    )
    176
    177
    178# Exceptions seen
    

    the terminal is my friend, and shows NameError

    NameError: name 'optional_arguments' is not defined
    

    because …


GREEN: make it pass


I add the function definition for optional_arguments

168def test_optional_arguments():
169    def optional_arguments(first_input, last_input):
170        return first_input, last_input
171
172    first_name, last_name = 'jane', 'doe'
173    assert_equal(
174        optional_arguments(
175            first_name, last_input=last_name,
176        ),
177        (first_name, last_name)
178    )
179
180
181# Exceptions seen

the test passes.


REFACTOR: make it better


  • I remove last_input=last_name from the call to optional_arguments to show that it is a required argument

    168def test_optional_arguments():
    169    def optional_arguments(first_input, last_input):
    170        return first_input, last_input
    171
    172    first_name, last_name = 'jane', 'doe'
    173    assert_equal(
    174        optional_arguments(
    175            # first_name, last_input=last_name,
    176            first_name,
    177        ),
    178        (first_name, last_name)
    179    )
    180
    181
    182# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError:
        test_optional_arguments.<locals>.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 give the argument a default value to make it optional

    168def test_optional_arguments():
    169    # def optional_arguments(first_input, last_input):
    170    def optional_arguments(
    171        first_input, last_input='doe'
    172    ):
    173        return first_input, last_input
    174
    175    first_name, last_name = 'jane', 'doe'
    176    assert_equal(
    177        optional_arguments(
    178            # first_name, last_input=last_name,
    179            first_name,
    180        ),
    181        (first_name, last_name)
    182    )
    183
    184
    185# Exceptions seen
    

    the test passes because I do not need to give a value for the last_input parameter when I call the function since there is a default value for the last_input parameter of the function (doe).

    These two calls are the same

    optional_arguments('jane')
    optional_arguments('jane', last_input='doe')
    
    • When optional_arguments('jane') runs

      optional_arguments('jane') -> ('jane', 'doe')
      └── def optional_arguments(first_input, last_input='doe'):
          ├── first_input = 'jane'
          ├── last_input  = 'doe' # use default value
          └── return first_input, last_input
              return 'jane'     , 'doe'
      
    • When optional_arguments(first_input, last_input='doe') runs

      optional_arguments(
          'jane', last_input='doe'
      ) -> ('jane', 'doe')
      └── def optional_arguments(first_input, last_input='doe'):
          ├── first_input = 'jane'
          ├── last_input  = 'doe' # use given value
          └── return first_input, last_input
              return 'jane'     , 'doe'
      

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

  • I add another assertion to show that I can still call the function with different values

    175    first_name, last_name = 'jane', 'doe'
    176    assert_equal(
    177        optional_arguments(
    178            # first_name, last_input=last_name,
    179            first_name,
    180        ),
    181        (first_name, last_name)
    182    )
    183
    184    first_name, blow = 'joe', 'blow'
    185    assert_equal(
    186        optional_arguments(
    187            first_name, blow
    188        ),
    189        ()
    190    )
    191
    192
    193# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert ('joe', 'blow') == ()
    
  • I change my expectation to match reality

    184    first_name, blow = 'joe', 'blow'
    185    assert_equal(
    186        optional_arguments(
    187            first_name, blow
    188        ),
    189        # ()
    190        (first_name, blow)
    191    )
    192
    193
    194# Exceptions seen
    

    the test passes.

    ├── first_name = 'joe'
    ├── blow       = 'blow'
    └── optional_arguments(
            first_name, blow
        ) -> ('joe', 'blow')
        └── def optional_arguments(first_input, last_input='doe'):
            ├── first_input = first_name
            ├── last_input  = blow # use given value
            └── return first_input, last_input
                return 'joe'     , 'blow'
    
  • I add another assertion to test_optional_arguments

    184    first_name, blow = 'joe', 'blow'
    185    assert_equal(
    186        optional_arguments(
    187            first_name, blow
    188        ),
    189        # ()
    190        (first_name, blow)
    191    )
    192
    193    first_name = 'john'
    194    assert_equal(
    195        optional_arguments(
    196            first_input=first_name,
    197        ),
    198        ()
    199    )
    200
    201
    202# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert ('john', 'doe') == ()
    
  • I change my expectation to match reality

    193    first_name = 'john'
    194    assert_equal(
    195        optional_arguments(
    196            first_input=first_name,
    197        ),
    198        # ()
    199        (first_name, last_name)
    200    )
    201
    202
    203# Exceptions seen
    

    the test passes because I do not need to give a value for the last_input parameter in the call to optional_arguments since there is a default value for the last_input parameter of the optional_arguments function (doe). This means that

    optional_arguments('john')
    

    is the same as

    optional_arguments('john', last_input='doe')
    
    ├── first_name = 'john'
    └── optional_arguments(
            first_input=first_name
        ) -> ('john', 'doe')
        └── def optional_arguments(first_input, last_input='doe'):
            ├── first_input = first_name
            ├── last_input  = 'doe' # use default value
            └── return first_input, last_input
                return 'john'     , 'doe'
    

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

  • I add one more assertion to test_optional_arguments

    193    first_name = 'john'
    194    assert_equal(
    195        optional_arguments(
    196            first_input=first_name,
    197        ),
    198        # ()
    199        (first_name, last_name)
    200    )
    201
    202    last_name = 'smith'
    203    assert_equal(
    204        optional_arguments(
    205            last_input=last_name,
    206            first_input=first_name,
    207        ),
    208        (last_name, first_name)
    209    )
    210
    211
    212# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert ('john', 'smith')
                        == ('smith', 'john')
    
  • I change my expectation to match reality

    202    last_name = 'smith'
    203    assert_equal(
    204        optional_arguments(
    205            last_input=last_name,
    206            first_input=first_name,
    207        ),
    208        # (last_name, first_name)
    209        (first_name, last_name)
    210    )
    211
    212
    213# Exceptions seen
    

    the test passes.

    ├── first_name = 'john'
    ├── last_name  = 'smith'
    └── optional_arguments(
            last_input=last_name,
            first_input=first_name,
        ) -> ('john', 'smith')
        └── def optional_arguments(first_input, last_input='doe'):
            ├── first_input = first_name
            ├── last_input  = smith # use given value
            └── return first_input, last_input
                return 'john'     , 'smith'
    
  • I remove the commented lines from test_optional_arguments

    168def test_optional_arguments():
    169    def optional_arguments(
    170        first_input, last_input='doe'
    171    ):
    172        return first_input, last_input
    173
    174    first_name, last_name = 'jane', 'doe'
    175    assert_equal(
    176        optional_arguments(
    177            first_name,
    178        ),
    179        (first_name, last_name)
    180    )
    
    174    first_name, blow = 'joe', 'blow'
    175    assert_equal(
    176        optional_arguments(
    177            first_name, blow
    178        ),
    179        (first_name, blow)
    180    )
    
    190    first_name = 'john'
    191    assert_equal(
    192        optional_arguments(
    193            first_input=first_name,
    194        ),
    195        (first_name, last_name)
    196    )
    
    198    last_name = 'smith'
    199    assert_equal(
    200        optional_arguments(
    201            last_input=last_name,
    202            first_input=first_name,
    203        ),
    204        (first_name, last_name)
    205    )
    206
    207
    208# Exceptions seen
    
  • I add a git commit message in the other terminal

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

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

I can make a function with optional and required arguments.


These four functions - keyword_arguments, positional_arguments, args_and_kwargs and optional_arguments are the same, they always return first_input, last_input, their names are different.

def positional_arguments(first_input, last_input):
def keyword_arguments(first_input, last_input):
def args_and_kwargs(first_input, last_input):
def 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

positional_arguments('first', 'last')
           -> return 'first', 'last'
positional_arguments('last', 'first')
           -> return 'last', 'first'
positional_arguments(
    first_input=[0, 1, 2, 'n'],
    last_input=(0, 1, 2, 'n')
) -> return [0, 1, 2, 'n'], (0, 1, 2, 'n')
keyword_arguments(
    first_input='first', last_input='last'
) -> return 'first', 'last'
keyword_arguments(
    last_input='last', first_input='first'
) -> return 'first', 'last'
keyword_arguments('last', 'first')
        -> return 'last', 'first'
args_and_kwargs('first', last_input='last')
      -> return 'first', 'last'
optional_arguments('jane', last_input='doe')
         -> return 'jane', 'doe'
optional_arguments('jane')
         -> return 'jane', 'doe'
optional_arguments('joe', 'blow')
         -> return 'joe', 'blow'
optional_arguments(
    first_input='john', last_input='smith'
) -> return 'john', 'smith'

Tip

As a rule of thumb I use keyword arguments when the function takes two or more inputs so I do not have to remember the order.


test_unknown_number_of_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, it can handle whatever I give it.


RED: make it fail


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

  • I add test_unknown_number_of_arguments to test_functions.py

    198    last_name = 'smith'
    199    assert_equal(
    200        optional_arguments(
    201            last_input=last_name,
    202            first_input=first_name,
    203        ),
    204        (first_name, last_name)
    205    )
    206
    207
    208def test_unknown_number_of_arguments():
    209    assert_equal(
    210        unknown_number_of_arguments(
    211            0, 1, a=2, b=3,
    212        ),
    213        None
    214    )
    215
    216
    217# Exceptions seen
    

    the terminal is my friend, and shows NameError

    NameError: name 'unknown_number_of_arguments' is not defined
    

    because test_functions.py does not have unknown_number_of_arguments.


GREEN: make it pass


  • I add the function

    208def test_unknown_number_of_arguments():
    209    def unknown_number_of_arguments():
    210        return None
    211
    212    assert_equal(
    213        unknown_number_of_arguments(
    214            0, 1, a=2, b=3,
    215        ),
    216        None
    217    )
    218
    219
    220# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError:
        test_unknown_number_of_arguments
              .<locals>.unknown_number_of_arguments()
        got an unexpected keyword argument 'a'
    

    because the assertion called unknown_number_of_arguments with a keyword argument named a and the function definition does not allow any inputs, the parentheses are empty.

  • I add a to the function definition

    208def test_unknown_number_of_arguments():
    209    # def unknown_number_of_arguments():
    210    def unknown_number_of_arguments(a):
    211        return None
    

    the terminal is my friend, and shows TypeError

    TypeError:
        test_unknown_number_of_arguments
            .<locals>.unknown_number_of_arguments()
        got multiple values for argument 'a'
    

    I had this same problem in test_args_and_kwargs. Python cannot tell if a is a positional or keyword argument based on my function definition. It cannot tell if 0 or 2 is the value for a.

    unknown_number_of_arguments(0, 1, a=2, b=3,)
    └── def unknown_number_of_arguments(a):
        ├── a = 0 ?
        └── a = 2 ?
    

double starred expressions

Python has a way for a function to take any number of keyword arguments without knowing how many they are. It is the double starred expression (**).

  • I use a double starred expression to replace a in the parentheses

    208def test_unknown_number_of_arguments():
    209    # def unknown_number_of_arguments():
    210    # def unknown_number_of_arguments(a):
    211    def unknown_number_of_arguments(**kwargs):
    212        return None
    

    the terminal is my friend, and shows TypeError

    TypeError:
        test_unknown_number_of_arguments
            .<locals>.unknown_number_of_arguments()
        takes 0 positional arguments but 2 were given
    
  • I add x as the name of the first positional argument

    208def test_unknown_number_of_arguments():
    209    # def unknown_number_of_arguments():
    210    # def unknown_number_of_arguments(a):
    211    # def unknown_number_of_arguments(**kwargs):
    212    def unknown_number_of_arguments(**kwargs, x):
    213        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 unknown_number_of_arguments

    208def test_unknown_number_of_arguments():
    209    # def unknown_number_of_arguments():
    210    # def unknown_number_of_arguments(a):
    211    # def unknown_number_of_arguments(**kwargs):
    212    # def unknown_number_of_arguments(**kwargs, x):
    213    def unknown_number_of_arguments(x, **kwargs):
    214        return None
    

    the terminal is my friend, and shows TypeError

    TypeError:
        test_unknown_number_of_arguments
            .<locals>.unknown_number_of_arguments()
        takes 1 positional argument but 2 were given
    
  • I add y as the name of the other positional argument

    208def test_unknown_number_of_arguments():
    209    # def unknown_number_of_arguments():
    210    # def unknown_number_of_arguments(a):
    211    # def unknown_number_of_arguments(**kwargs):
    212    # def unknown_number_of_arguments(**kwargs, x):
    213    # def unknown_number_of_arguments(x, **kwargs):
    214    def unknown_number_of_arguments(x, y, **kwargs):
    215        return None
    216
    217    assert_equal(
    218        unknown_number_of_arguments(
    219            0, 1, a=2, b=3,
    220        ),
    221        None
    222    )
    223
    224
    225# Exceptions seen
    

    the test passes.

    unknown_number_of_arguments(0, 1, a=2, b=3,) -> None
    └── def unknown_number_of_arguments(x, y, **kwargs):
        └── return None
    

REFACTOR: make it better


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

    217    assert_equal(
    218        unknown_number_of_arguments(
    219            0, 1, a=2, b=3,
    220        ),
    221        None
    222    )
    223
    224    assert_equal(
    225        unknown_number_of_arguments(
    226            0, 1, a=2, b=3, c=4,
    227        ),
    228        ()
    229    )
    230
    231
    232# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert None == ()
    
  • I change my expectation to match reality

    224    assert_equal(
    225        unknown_number_of_arguments(
    226            0, 1, a=2, b=3, c=4,
    227        ),
    228        # ()
    229        None
    230    )
    231
    232
    233# Exceptions seen
    

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

    unknown_number_of_arguments(0, 1, a=2, b=3, c=4,) -> None
    └── def unknown_number_of_arguments(x, y, **kwargs):
        └── return None
    
  • I add an assertion to see what happens when I call the function with three positional arguments

    224    assert_equal(
    225        unknown_number_of_arguments(
    226            0, 1, a=2, b=3, c=4,
    227        ),
    228        # ()
    229        None
    230    )
    231
    232    assert_equal(
    233        unknown_number_of_arguments(
    234            0, 1, 2, a=3, b=4, c=5,
    235        ),
    236        None
    237    )
    238
    239
    240# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError:
        test_unknown_number_of_arguments
            .<locals>.unknown_number_of_arguments()
        takes 2 positional arguments but 3 were given
    

    the function definition only allows two positional arguments not three.

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

    208def test_unknown_number_of_arguments():
    209    # def unknown_number_of_arguments():
    210    # def unknown_number_of_arguments(a):
    211    # def unknown_number_of_arguments(**kwargs):
    212    # def unknown_number_of_arguments(**kwargs, x):
    213    # def unknown_number_of_arguments(x, **kwargs):
    214    # def unknown_number_of_arguments(x, y, **kwargs):
    215    def unknown_number_of_arguments(x, y, z, **kwargs):
    216        return None
    

    the terminal is my friend, and shows TypeError

    TypeError:
        test_unknown_number_of_arguments
            .<locals>.unknown_number_of_arguments()
        missing 1 required positional argument: 'z'
    

    because the previous call to the function uses two positional arguments and the function now requires three.


single starred expressions

Python also has a way for a function to take any number of positional arguments without knowing how many they are. It is the single starred expression (*).

  • I use a single starred expression (*) to replace the positional arguments

    208def test_unknown_number_of_arguments():
    209    # def unknown_number_of_arguments():
    210    # def unknown_number_of_arguments(a):
    211    # def unknown_number_of_arguments(**kwargs):
    212    # def unknown_number_of_arguments(**kwargs, x):
    213    # def unknown_number_of_arguments(x, **kwargs):
    214    # def unknown_number_of_arguments(x, y, **kwargs):
    215    # def unknown_number_of_arguments(x, y, z, **kwargs):
    216    def unknown_number_of_arguments(*args, **kwargs):
    217        return None
    

    the test passes.

    unknown_number_of_arguments(0, 1, 2, a=3, b=4, c=5,) -> None
    └── def unknown_number_of_arguments(*args, **kwargs):
        └── return None
    
  • *args, **kwargs is Python convention. I change the names to make it clearer

    208def test_unknown_number_of_arguments():
    209    # def unknown_number_of_arguments():
    210    # def unknown_number_of_arguments(a):
    211    # def unknown_number_of_arguments(**kwargs):
    212    # def unknown_number_of_arguments(**kwargs, x):
    213    # def unknown_number_of_arguments(x, **kwargs):
    214    # def unknown_number_of_arguments(x, y, **kwargs):
    215    # def unknown_number_of_arguments(x, y, z, **kwargs):
    216    # def unknown_number_of_arguments(*args, **kwargs):
    217    def unknown_number_of_arguments(
    218        *positional_arguments, **keyword_arguments
    219    ):
    220        return None
    

    the test is still green.


how Python treats starred and double starred expressions

  • I change the return statement because I want the function to return its input (remember the identity function?)

    208def test_unknown_number_of_arguments():
    209    # def unknown_number_of_arguments():
    210    # def unknown_number_of_arguments(a):
    211    # def unknown_number_of_arguments(**kwargs):
    212    # def unknown_number_of_arguments(**kwargs, x):
    213    # def unknown_number_of_arguments(x, **kwargs):
    214    # def unknown_number_of_arguments(x, y, **kwargs):
    215    # def unknown_number_of_arguments(x, y, z, **kwargs):
    216    # def unknown_number_of_arguments(*args, **kwargs):
    217    def unknown_number_of_arguments(
    218        *positional_arguments, **keyword_arguments
    219    ):
    220        return positional_arguments, keyword_arguments
    221        # return None
    

    the terminal is my friend, and shows AssertionError

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

    I get a tuple that has

    When unknown_number_of_arguments(0, 1, a=2, b=3) runs

    unknown_number_of_arguments(
        0, 1, a=2, b=3
    ) -> ((0, 1), {'a': 2, 'b': 3})
    └── def unknown_number_of_arguments(
            *positional_arguments, **keyword_arguments
        ):
        ├── positional_arguments = (0, 1)
        ├── keyword_arguments    = {'a': 2, 'b': 3}
        └── return  positional_arguments, keyword_arguments
            return ((0, 1              ), {'a': 2, 'b': 3  })
    
  • I change my expectation to match reality in the first assertion of test_unknown_number_of_arguments

    217    def unknown_number_of_arguments(
    218        *positional_arguments, **keyword_arguments
    219    ):
    220        return positional_arguments, keyword_arguments
    221        # return None
    222
    223    assert_equal(
    224        unknown_number_of_arguments(
    225            0, 1, a=2, b=3,
    226        ),
    227        # None
    228        ((0, 1), {'a': 2, 'b': 3})
    229    )
    

    the terminal is my friend, and shows AssertionError

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

    I get a tuple that has

    When unknown_number_of_arguments(0, 1, a=2, b=3, c=4) runs

    unknown_number_of_arguments(
        0, 1, a=2, b=3, c=4
    ) -> ((0, 1), {'a': 2, 'b': 3, 'c': 4})
    └── def unknown_number_of_arguments(
            *positional_arguments, **keyword_arguments
        ):
        ├── positional_arguments = (0, 1)
        ├── keyword_arguments    = {'a': 2, 'b': 3, 'c': 4}
        └── return  positional_arguments, keyword_arguments
            return ((0, 1              ), {'a': 2, 'b': 3, 'c':4})
    
  • I change my expectation to match reality in the second assertion of test_unknown_number_of_arguments

    223    assert_equal(
    224        unknown_number_of_arguments(
    225            0, 1, a=2, b=3,
    226        ),
    227        # None
    228        ((0, 1), {'a': 2, 'b': 3})
    229    )
    230
    231    assert_equal(
    232        unknown_number_of_arguments(
    233            0, 1, a=2, b=3, c=4,
    234        ),
    235        # ()
    236        # None
    237        ((0, 1), {'a': 2, 'b': 3, 'c': 4})
    238    )
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        assert ((0, 1, 2), {'a': 3, 'b': 4, 'c': 5})
            == None
    

    I get a tuple that has

    unknown_number_of_arguments(
        0, 1, 2, a=3, b=4, c=5
    ) -> ((0, 1, 2), {'a': 3, 'b': 4, 'c': 5})
    └── def unknown_number_of_arguments(
            *positional_arguments, **keyword_arguments
        ):
        ├── positional_arguments = (0, 1, 2)
        ├── keyword_arguments    = {'a': 3, 'b': 4, 'c':5}
        └── return  positional_arguments, keyword_arguments
            return ((0, 1, 2           ), {'a': 3, 'b': 4, 'c':5})
    
  • I change my expectation to match reality in the last assertion

    231    assert_equal(
    232        unknown_number_of_arguments(
    233            0, 1, a=2, b=3, c=4,
    234        ),
    235        # ()
    236        # None
    237        ((0, 1), {'a': 2, 'b': 3, 'c': 4})
    238    )
    239
    240    assert_equal(
    241        unknown_number_of_arguments(
    242            0, 1, 2, a=3, b=4, c=5,
    243        ),
    244        # None
    245        ((0, 1, 2), {'a': 3, 'b': 4, 'c': 5})
    246    )
    247
    248
    249# Exceptions seen
    

    the test passes.


how Python treats starred expressions


  • I add variables for the tuple and dictionary of the first assertion

    208def test_unknown_number_of_arguments():
    209    # def unknown_number_of_arguments():
    210    # def unknown_number_of_arguments(a):
    211    # def unknown_number_of_arguments(**kwargs):
    212    # def unknown_number_of_arguments(**kwargs, x):
    213    # def unknown_number_of_arguments(x, **kwargs):
    214    # def unknown_number_of_arguments(x, y, **kwargs):
    215    # def unknown_number_of_arguments(x, y, z, **kwargs):
    216    # def unknown_number_of_arguments(*args, **kwargs):
    217    def unknown_number_of_arguments(
    218        *positional_arguments, **keyword_arguments
    219    ):
    220        return positional_arguments, keyword_arguments
    221        # return None
    222
    223    a_tuple = (0, 1)
    224    a_dictionary = {'a': 2, 'b': 3}
    225    assert_equal(
    226        unknown_number_of_arguments(
    227            0, 1, a=2, b=3,
    228        ),
    229        # None
    230        ((0, 1), {'a': 2, 'b': 3})
    231    )
    
  • I use the variables to remove repetition of the tuple and dictionary from the first assertion in test_unknown_number_of_arguments

    223    a_tuple = (0, 1)
    224    a_dictionary = {'a': 2, 'b': 3}
    225    assert_equal(
    226        unknown_number_of_arguments(
    227            # 0, 1, a=2, b=3,
    228            a_tuple, a_dictionary
    229        ),
    230        # None
    231        # ((0, 1), {'a': 2, 'b': 3})
    232        (a_tuple, a_dictionary)
    233    )
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert (((0, 1), {'a... '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 and a_dictionary) so I get a tuple with

    ├── a_tuple = (0, 1)
    ├── a_dictionary = {'a': 2, 'b': 3}
    └── unknown_number_of_arguments(
            a_tuple, a_dictionary
        ) -> ((0, 1), {'a': 2, 'b': 3}), {})
        └── def unknown_number_of_arguments(
                *positional_arguments, **keyword_arguments
            ):
            ├── positional_arguments = (a_tuple, a_dictionary)
            ├── keyword_arguments    = {}
            └── return   positional_arguments    , keyword_arguments
                return ((a_tuple, a_dictionary)  , {})
                return ((0, 1), {'a': 2, 'b': 3}), {})
    
  • I change the tuple with * so that Python breaks up its contents, allowing them to be used as separate arguments

    223    a_tuple = (0, 1)
    224    a_dictionary = {'a': 2, 'b': 3}
    225    assert_equal(
    226        unknown_number_of_arguments(
    227            # 0, 1, a=2, b=3,
    228            # a_tuple, a_dictionary
    229            *a_tuple, a_dictionary
    230        ),
    231        # None
    232        # ((0, 1), {'a': 2, 'b': 3})
    233        (a_tuple, a_dictionary)
    234    )
    

    the terminal is my friend, and shows AssertionError

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

    I still get a tuple of the arguments since they are both positional arguments, the difference is that the items of the tuple appear separately not as another tuple.


how Python treats double starred expressions


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

    223    a_tuple = (0, 1)
    224    a_dictionary = {'a': 2, 'b': 3}
    225    assert_equal(
    226        unknown_number_of_arguments(
    227            # 0, 1, a=2, b=3,
    228            # a_tuple, a_dictionary
    229            # *a_tuple, a_dictionary
    230            *a_tuple, **a_dictionary
    231        ),
    232        # None
    233        # ((0, 1), {'a': 2, 'b': 3})
    234        (a_tuple, a_dictionary)
    235    )
    

    the test passes

    ├── a_tuple = (0, 1)
    ├── a_dictionary = {'a': 2, 'b': 3}
    └── unknown_number_of_arguments(
            *a_tuple, **a_dictionary
        ) -> ((0, 1), {'a': 2, 'b': 3})
        └── def unknown_number_of_arguments(
                *positional_arguments, **keyword_arguments
            ):
            ├── positional_arguments = a_tuple
            ├── keyword_arguments    = a_dictionary
            └── return  positional_arguments, keyword_arguments
                return (a_tuple             , a_dictionary     )
                return ((0, 1)              , {'a': 2, 'b': 3} )
    

    these three statements are the same

    unknown_number_of_arguments(*a_tuple, **a_dictionary  )
    unknown_number_of_arguments(*(0, 1) , **{'a':2, 'b':3})
    unknown_number_of_arguments(0, 1    , a=2, b=3        )
    
  • I add a variable for the dictionary of the second assertion in test_unknown_number_of_arguments

    223    a_tuple = (0, 1)
    224    a_dictionary = {'a': 2, 'b': 3}
    225    assert_equal(
    226        unknown_number_of_arguments(
    227            # 0, 1, a=2, b=3,
    228            # a_tuple, a_dictionary
    229            # *a_tuple, a_dictionary
    230            *a_tuple, **a_dictionary
    231        ),
    232        # None
    233        # ((0, 1), {'a': 2, 'b': 3})
    234        (a_tuple, a_dictionary)
    235    )
    236
    237    a_dictionary = {'a': 2, 'b': 3, 'c': 4}
    238    assert_equal(
    239        unknown_number_of_arguments(
    240            0, 1, a=2, b=3, c=4,
    241        ),
    242        # ()
    243        # None
    244        ((0, 1), {'a': 2, 'b': 3, 'c': 4})
    245    )
    
  • I use the a_tuple and new a_dictionary variables to remove repetition of the tuple and dictionary from the second assertion

    237    a_dictionary = {'a': 2, 'b': 3, 'c': 4}
    238    assert_equal(
    239        unknown_number_of_arguments(
    240            # 0, 1, a=2, b=3, c=4,
    241            a_tuple, a_dictionary,
    242        ),
    243        # ()
    244        # None
    245        # ((0, 1), {'a': 2, 'b': 3, 'c': 4})
    246        (a_tuple, a_dictionary)
    247    )
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        assert (((0, 1), {'a... 'c': 4}), {})
            == ((0, 1), {'a'...': 3, 'c': 4})
    

    because passing in the values this way means I am sending in two positional arguments (a_tuple and a_dictionary) so I get a tuple with

    ├── a_tuple = (0, 1)
    ├── a_dictionary = {'a': 2, 'b': 3, 'c': 4}
    └── unknown_number_of_arguments(
            a_tuple, a_dictionary
        ) -> ((0, 1), {'a': 2, 'b': 3, 'c': 4}), {})
        └── def unknown_number_of_arguments(
                *positional_arguments, **keyword_arguments
            )
            ├── positional_arguments = (a_tuple, a_dictionary)
            ├── keyword_arguments    = {}
            └── return positional_arguments, keyword_arguments
                return ((a_tuple, a_dictionary), {})
                return ((0, 1), {'a': 2, 'b': 3, 'c': 4}), {})
    
  • I change the dictionary with ** so that Python breaks up the contents, allowing them to be used as keyword arguments

    237    a_dictionary = {'a': 2, 'b': 3, 'c': 4}
    238    assert_equal(
    239        unknown_number_of_arguments(
    240            # 0, 1, a=2, b=3, c=4,
    241            # a_tuple, a_dictionary,
    242            a_tuple, **a_dictionary,
    243        ),
    244        # ()
    245        # None
    246        # ((0, 1), {'a': 2, 'b': 3, 'c': 4})
    247        (a_tuple, a_dictionary)
    248    )
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert (((0, 1),), {...': 3, 'c': 4})
                        == ((0, 1), {'a'...': 3, 'c': 4})
    

    The tuple I get no longer has an empty dictionary, values from the input dictionary.

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

    237    a_dictionary = {'a': 2, 'b': 3, 'c': 4}
    238    assert_equal(
    239        unknown_number_of_arguments(
    240            # 0, 1, a=2, b=3, c=4,
    241            # a_tuple, a_dictionary,
    242            # a_tuple, **a_dictionary,
    243            *a_tuple, **a_dictionary,
    244        ),
    245        # ()
    246        # None
    247        # ((0, 1), {'a': 2, 'b': 3, 'c': 4})
    248        (a_tuple, a_dictionary)
    249    )
    

    the test passes.

    ├── a_tuple = (0, 1)
    ├── a_dictionary = {'a': 2, 'b': 3, 'c': 4}
    └── unknown_number_of_arguments(
            *a_tuple, **a_dictionary
        ) -> (0, 1), {'a': 2, 'b': 3, 'c': 4})
        └── def unknown_number_of_arguments(
                *positional_arguments, **keyword_arguments
            )
            ├── positional_arguments = a_tuple
            ├── keyword_arguments    = a_dictionary
            └── return positional_arguments, keyword_arguments
                return (a_tuple            , a_dictionary)
                return (0, 1), {'a': 2, 'b': 3, 'c': 4})
    

    these three statements are the same

    unknown_number_of_arguments(*a_tuple, **a_dictionary            )
    unknown_number_of_arguments(*(0, 1) , **{'a': 2, 'b': 3, 'c': 4})
    unknown_number_of_arguments(0, 1    , a=2, b=3, c=4             )
    
  • I add variables for the tuple and dictionary of the third assertion in test_unknown_number_of_arguments

    237    a_dictionary = {'a': 2, 'b': 3, 'c': 4}
    238    assert_equal(
    239        unknown_number_of_arguments(
    240            # 0, 1, a=2, b=3, c=4,
    241            # a_tuple, a_dictionary,
    242            # a_tuple, **a_dictionary,
    243            *a_tuple, **a_dictionary,
    244        ),
    245        # ()
    246        # None
    247        # ((0, 1), {'a': 2, 'b': 3, 'c': 4})
    248        (a_tuple, a_dictionary)
    249    )
    250
    251    a_tuple = (0, 1, 2)
    252    a_dictionary = {'a': 3, 'b': 4, 'c': 5}
    253    assert_equal(
    254        unknown_number_of_arguments(
    255            0, 1, 2, a=3, b=4, c=5,
    256        ),
    257        # None
    258        ((0, 1, 2), {'a': 3, 'b': 4, 'c': 5})
    259    )
    260
    261
    262# Exceptions seen
    
  • I use the variables to remove repetition of the tuple and dictionary from the third assertion

    251    a_tuple = (0, 1, 2)
    252    a_dictionary = {'a': 3, 'b': 4, 'c': 5}
    253    assert_equal(
    254        unknown_number_of_arguments(
    255            # 0, 1, 2, a=3, b=4, c=5,
    256            a_tuple, a_dictionary
    257        ),
    258        # None
    259        # ((0, 1, 2), {'a': 3, 'b': 4, 'c': 5})
    260        (a_tuple, a_dictionary)
    261    )
    262
    263
    264# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert (((0, 1, 2), ... 'c': 5}), {})
                        == ((0, 1, 2), {...': 4, 'c': 5})
    

    because passing in the values this way means I am sending in two positional arguments (a_tuple and a_dictionary) so I get a tuple with

    ├── a_tuple = (0, 1, 2)
    ├── a_dictionary = {'a': 3, 'b': 4, 'c': 5}
    └── unknown_number_of_arguments(
            a_tuple, a_dictionary
        ) -> ((0, 1, 2), {'a': 3, 'b': 4, 'c': 5}), {})
        └── def unknown_number_of_arguments(
                *positional_arguments, **keyword_arguments
            ):
            ├── positional_arguments = (a_tuple, a_dictionary)
            ├── keyword_arguments    = {}
            └── return   positional_arguments, keyword_arguments
                return ((a_tuple, a_dictionary)             , {})
                return ((0, 1, 2), {'a': 3, 'b': 4, 'c': 5}), {})
    
  • I change the inputs with * and ** so that Python breaks up the contents, allowing them to be used as separate arguments

    251    a_tuple = (0, 1, 2)
    252    a_dictionary = {'a': 3, 'b': 4, 'c': 5}
    253    assert_equal(
    254        unknown_number_of_arguments(
    255            # 0, 1, 2, a=3, b=4, c=5,
    256            # a_tuple, a_dictionary
    257            *a_tuple, **a_dictionary
    258        ),
    259        # None
    260        # ((0, 1, 2), {'a': 3, 'b': 4, 'c': 5})
    261        (a_tuple, a_dictionary)
    262    )
    263
    264
    265# Exceptions seen
    

    the test passes.

    ├── a_tuple = (0, 1, 2)
    ├── a_dictionary = {'a': 3, 'b': 4, 'c': 5}
    └── unknown_number_of_arguments(
            *a_tuple, **a_dictionary
        ) -> ((0, 1, 2), {'a': 3, 'b': 4, 'c': 5})
        └── def unknown_number_of_arguments(
                *positional_arguments, **keyword_arguments
            ):
            ├── positional_arguments = a_tuple
            ├── keyword_arguments    = a_dictionary
            └── return   positional_arguments, keyword_arguments
                return ( a_tuple , a_dictionary            )
                return ((0, 1, 2), {'a': 3, 'b': 4, 'c': 5})
    

    these three statements are the same

    unknown_number_of_arguments(
        *a_tuple  , **a_dictionary
    )
    unknown_number_of_arguments(
        *(0, 1, 2), **{'a': 2, 'b': 3, 'c': 4}
    )
    unknown_number_of_arguments(
        0, 1, 2, a=3, b=4, c=5
    )
    
  • I add an assertion with a call to unknown_number_of_arguments using only positional arguments

    251    a_tuple = (0, 1, 2)
    252    a_dictionary = {'a': 3, 'b': 4, 'c': 5}
    253    assert_equal(
    254        unknown_number_of_arguments(
    255            # 0, 1, 2, a=3, b=4, c=5,
    256            # a_tuple, a_dictionary
    257            *a_tuple, **a_dictionary
    258        ),
    259        # None
    260        # ((0, 1, 2), {'a': 3, 'b': 4, 'c': 5})
    261        (a_tuple, a_dictionary)
    262    )
    263
    264    a_tuple = (0, 1, 2, 'n')
    265    assert_equal(
    266        unknown_number_of_arguments(*a_tuple),
    267        ()
    268    )
    269
    270
    271# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert ((0, 1, 2, 'n'), {}) == ()
    

    because passing in the values this way means I am sending in only positional arguments (*a_tuple) so I get a tuple with

    ├── a_tuple = (0, 1, 2, 'n')
    └── unknown_number_of_arguments(
            *a_tuple
        ) -> ((0, 1, 2, 'n'), {})
        └── def unknown_number_of_arguments(
                *positional_arguments, **keyword_arguments
            ):
            ├── positional_arguments = a_tuple
            ├── keyword_arguments    = {}
            └── return   positional_arguments, keyword_arguments
                return ( a_tuple             , {}               )
                return ((0, 1, 2, 'n')       , {}               )
    

    these three statements are the same

    unknown_number_of_arguments(*a_tuple       )
    unknown_number_of_arguments(*(0, 1, 2, 'n'))
    unknown_number_of_arguments(0, 1, 2, 'n'   )
    
  • I change my expectation to match reality

    264    a_tuple = (0, 1, 2, 'n')
    265    assert_equal(
    266        unknown_number_of_arguments(*a_tuple),
    267        # ()
    268        (a_tuple, {})
    269    )
    270
    271
    272# Exceptions seen
    

    the test passes.

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

    264    a_tuple = (0, 1, 2, 'n')
    265    assert_equal(
    266        unknown_number_of_arguments(*a_tuple),
    267        # ()
    268        (a_tuple, {})
    269    )
    270
    271    a_dictionary = {'a': 1, 'b': 2, 'c': 3, 'd': 'n'}
    272    assert_equal(
    273        unknown_number_of_arguments(**a_dictionary),
    274        ()
    275    )
    276
    277
    278# Exceptions seen
    

    the terminal is my friend, and shows

    AssertionError: assert ((), {'a': 1,... 3, 'd': 'n'}) == ()
    

    because passing in the values this way means I am sending in only keyword arguments (**a_dictionary) so I get a tuple with

    ├── a_dictionary = {'a': 1, 'b': 2, 'c': 3, 'd': 'n'}
    └── unknown_number_of_arguments(
            **a_dictionary
        ) -> ((), {'a': 1, 'b': 2, 'c': 3, 'd': 'n'})
        └── def unknown_number_of_arguments(
                *positional_arguments, **keyword_arguments
            ):
            ├── positional_arguments = ()
            ├── keyword_arguments    = {
                   'a': 1, 'b': 2, 'c': 3, 'd': 'n'
               }
            └── return  positional_arguments, keyword_arguments
                return ((), {'a': 1, 'b': 2, 'c': 3, 'd': 'n'})
    

    these three statements are the same

    unknown_number_of_arguments(**a_dictionary                    )
    unknown_number_of_arguments({'a': 1, 'b': 2, 'c': 3, 'd': 'n'})
    unknown_number_of_arguments(  a = 1,  b = 2,  c = 3,  d = 'n' )
    
  • I change my expectation to match reality

    271    a_dictionary = {'a': 1, 'b': 2, 'c': 3, 'd': 'n'}
    272    assert_equal(
    273        unknown_number_of_arguments(**a_dictionary),
    274        # ()
    275        ((), a_dictionary)
    276    )
    277
    278
    279# Exceptions seen
    

    the test passes.

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

    271    a_dictionary = {'a': 1, 'b': 2, 'c': 3, 'd': 'n'}
    272    assert_equal(
    273        unknown_number_of_arguments(**a_dictionary),
    274        # ()
    275        ((), a_dictionary)
    276    )
    277
    278    assert_equal(
    279        unknown_number_of_arguments(), TypeError
    280    )
    281
    282
    283# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert ((), {}) == <class 'TypeError'>
    

    because unknown_number_of_arguments gets called with no arguments so I get a tuple with

  • I change my expectation to match reality

    278    assert_equal(
    279        # unknown_number_of_arguments(), TypeError
    280        unknown_number_of_arguments(), ((), {})
    281    )
    282
    283
    284# Exceptions seen
    

    the test passes.

    unknown_number_of_arguments() -> ((), {})
    └── def unknown_number_of_arguments(
            *positional_arguments, **keyword_arguments
        ):
        ├── positional_arguments = ()
        ├── keyword_arguments    = {}
        └── return  positional_arguments, keyword_arguments
            return (()                  , {}               )
    
  • I remove the commented lines from test_unknown_number_of_arguments

    208def test_unknown_number_of_arguments():
    209    def unknown_number_of_arguments(
    210        *positional_arguments, **keyword_arguments
    211    ):
    212        return positional_arguments, keyword_arguments
    213
    214    a_tuple = (0, 1)
    215    a_dictionary = {'a': 2, 'b': 3}
    216    assert_equal(
    217        unknown_number_of_arguments(
    218            *a_tuple, **a_dictionary
    219        ),
    220        (a_tuple, a_dictionary)
    221    )
    
    223    a_dictionary = {'a': 2, 'b': 3, 'c': 4}
    224    assert_equal(
    225        unknown_number_of_arguments(
    226            *a_tuple, **a_dictionary,
    227        ),
    228        (a_tuple, a_dictionary)
    229    )
    
    231    a_tuple = (0, 1, 2)
    232    a_dictionary = {'a': 3, 'b': 4, 'c': 5}
    233    assert_equal(
    234        unknown_number_of_arguments(
    235            *a_tuple, **a_dictionary
    236        ),
    237        (a_tuple, a_dictionary)
    238    )
    
    240    a_tuple = (0, 1, 2, 'n')
    241    assert_equal(
    242        unknown_number_of_arguments(*a_tuple),
    243        (a_tuple, {})
    244    )
    
    246    a_dictionary = {'a': 1, 'b': 2, 'c': 3, 'd': 'n'}
    247    assert_equal(
    248        unknown_number_of_arguments(**a_dictionary),
    249        ((), a_dictionary)
    250    )
    251
    252    assert_equal(
    253        unknown_number_of_arguments(), ((), {})
    254    )
    255
    256
    257# Exceptions seen
    258# AssertionError
    259# NameError
    260# TypeError
    261# SyntaxError
    
  • I add a git commit message in the other terminal

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

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

I can make a function that can take any number of positional or keyword arguments.


close the project

  • I close test_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 that take input

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?

I am going for a walk. Would you like to test TypeError?


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.