functions that take input

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

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

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

input_data -> process -> output_data

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

\[f(x) -> y\]

in other words

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

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


how to make a function that takes input

functions_ are made with

  • the def keyword

  • a name

  • parentheses and a colon at the end

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

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

preview

I have these tests by the end of the chapter

  1def test_making_a_function_w_pass():
  2    def w_pass():
  3        pass
  4
  5    assert w_pass() is None
  6
  7
  8def test_making_a_function_w_return():
  9    def w_return():
 10        return
 11
 12    assert w_return() is None
 13
 14
 15def test_making_a_function_w_return_none():
 16    def w_return_none():
 17        return None
 18
 19    assert w_return_none() is None
 20
 21
 22def test_what_happens_after_functions_return():
 23    def return_leaves_the_function():
 24        return None
 25        return 'only one way for this line to run'
 26
 27    assert return_leaves_the_function() is None
 28
 29
 30def test_constant_function():
 31    def constant():
 32        return 'the same thing'
 33
 34    assert constant() is 'the same thing'
 35
 36
 37def test_identity_function():
 38    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    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
 61def positional_arguments(first_input, last_input):
 62    return first_input, last_input
 63
 64
 65def test_positional_arguments():
 66    first, last = 'first', 'last'
 67
 68    assert (
 69        positional_arguments(first, last)
 70     == (first, last)
 71    )
 72    assert (
 73        positional_arguments(last, first)
 74     == (last, first)
 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    a_set = {0, 1, 2, 'n'}
 89    a_dictionary = {'key': 'value'}
 90    assert (
 91        keyword_arguments(
 92            a_set, a_dictionary,
 93        )
 94     == (a_set, a_dictionary)
 95    )
 96
 97
 98def keyword_arguments(first_input, last_input):
 99    return first_input, last_input
100
101
102def test_keyword_arguments():
103    first, last = 'first', 'last'
104
105    assert (
106        keyword_arguments(
107            first_input=first, last_input=last,
108        )
109     == (first, last)
110    )
111    assert (
112        keyword_arguments(
113            last_input=last, first_input=first,
114        )
115     == (first, last)
116    )
117    assert (
118        keyword_arguments(
119            last_input=0, first_input=1,
120        )
121     == (1, 0)
122    )
123
124    a_tuple = (0, 1, 2, 'n')
125    a_list = [0, 1, 2, 'n']
126    assert (
127        keyword_arguments(
128            first_input=a_tuple,
129            last_input=a_list,
130        )
131     == (a_tuple, a_list)
132    )
133
134    a_set = {0, 1, 2, 'n'}
135    a_dictionary = {'key': 'value'}
136    assert (
137        positional_arguments(
138            last_input=a_dictionary,
139            first_input=a_set,
140        )
141     == (a_set, a_dictionary)
142    )
143
144
145def test_args_and_kwargs():
146    def args_and_kwargs(first_input, last_input):
147        return first_input, last_input
148
149    first, last = 'first', 'last'
150
151    assert (
152        args_and_kwargs(
153            first, last_input=last,
154        )
155     == (first, last)
156    )
157
158
159def test_optional_arguments():
160    def optional_arguments(
161        first_input, last_input='doe',
162    ):
163        return first_input, last_input
164
165    first_name, last_name = 'jane', 'doe'
166
167    assert (
168        optional_arguments(
169            first_name,
170        )
171     == (first_name, last_name)
172    )
173
174    first_name, blow = 'joe', 'blow'
175    assert (
176        optional_arguments(
177            first_name, blow
178        )
179     == (first_name, blow)
180    )
181
182    first_name = 'john'
183    assert (
184        optional_arguments(
185            first_input=first_name
186        )
187     == (first_name, last_name)
188    )
189
190    last_name = 'smith'
191    assert (
192        optional_arguments(
193            last_input=last_name,
194            first_input=first_name,
195        )
196     == (first_name, last_name)
197    )
198
199
200def test_unknown_number_of_arguments():
201    def unknown_number_of_arguments(
202        *positional_arguments, **keyword_arguments
203    ):
204        return positional_arguments, keyword_arguments
205
206    a_tuple = (0, 1)
207    a_dictionary = {'a': 2, 'b': 3}
208    assert (
209        unknown_number_of_arguments(
210            *a_tuple, **a_dictionary
211        )
212     == (a_tuple, a_dictionary)
213    )
214
215    a_tuple = (0, 1)
216    a_dictionary = {'a': 2, 'b': 3, 'c': 4}
217    assert (
218        unknown_number_of_arguments(
219            *a_tuple, **a_dictionary
220        )
221     == (a_tuple, a_dictionary)
222    )
223
224    a_tuple = (0, 1, 2)
225    a_dictionary = {'a': 3, 'b': 4, 'c': 5}
226    assert (
227        unknown_number_of_arguments(
228            *a_tuple, **a_dictionary
229        )
230     == (a_tuple, a_dictionary)
231    )
232
233    a_tuple = (0, 1, 2, 'n')
234    assert (
235        unknown_number_of_arguments(*a_tuple)
236     == (a_tuple, {})
237    )
238
239    a_dictionary = {'a': 1, 'b': 2, 'c': 3, 'd': 'n'}
240    assert (
241        unknown_number_of_arguments(**a_dictionary)
242     == ((), a_dictionary)
243    )
244
245    assert (
246        unknown_number_of_arguments()
247     == ((), {})
248    )
249
250
251# Exceptions seen
252# AssertionError
253# NameError
254# TypeError
255# SyntaxError

questions about functions that take input

Questions to think about as I go through the chapter


open the project

  • I open a terminal

  • I change directory to the project

    cd functions
    

    the terminal shows I am in the functions folder

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

  • I use pytest-watcher to run the tests automatically

    uv run pytest-watcher . --now
    

    the terminal shows

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

test_identity_function

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


RED: make it fail


I add a test to test_functions.py

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

the terminal is my friend, and shows NameError

NameError: name 'identity' is not defined

is it because test_functions.py has no identity?


GREEN: make it pass


I add a function for identity

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

the test passes.


how to call a function with input

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


RED: make it fail


I add input to the function call

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

the terminal is my friend, and shows TypeError

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

because

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

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

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


GREEN: make it pass


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

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

the test passes. I am genius.


REFACTOR: make it better


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

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

  • I add an assertion to test_identity_function in

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

    the terminal is my friend, and shows AssertionError

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

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

    the test passes.

  • I remove the commented lines

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

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

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

The Identity Function returns its input as output.

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


test_why_use_a_function

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


RED: make it fail


  • I add a test

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

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 0) == 0
    

    because 1 + 0 is NOT equal to 0.


GREEN: make it pass


I change the assertion to make it True

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

the test passes.


REFACTOR: make it better


  • I add an assertion for 1 + 1

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

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 1) == 1
    

    because 1 + 1 is NOT equal to 1.

  • I change the assertion to make it True

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

    the test passes.

  • I add an assertion for 1 + 2

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

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 2) == 2
    

    because 1 + 2 is NOT equal to 2.

  • I change the assertion to make it True

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

    the test passes.

  • I add an assertion for 1 + 3

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

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 3) == 3
    

    because 1 + 3 is NOT equal to 3.

  • I change the assertion to make it True

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

    the test passes.

  • I add an assertion for 1 + 3

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

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 3) == 3
    

    because 1 + 3 is NOT equal to 3.

  • I change the assertion to make it True

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

    the test passes.

  • I add an assertion for 1 + 4

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

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 4) == 4
    

    because 1 + 4 is NOT equal to 4.

  • I change the assertion to make it True

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

    the test passes.

  • I add an assertion for 1 + 5

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

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 5) == 5
    

    because 1 + 5 is NOT equal to 5.

  • I change the assertion to make it True

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

    the test passes.

  • I add an assertion for 1 + 6

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

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 6) == 6
    

    because 1 + 6 is NOT equal to 6.

  • I change the assertion to make it True

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

    the test passes.

  • I add an assertion for 1 + 7

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

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 7) == 7
    

    because 1 + 7 is NOT equal to 7.

  • I change the assertion to make it True

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

    the test passes.

  • I add an assertion for 1 + 8

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

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 8) == 8
    

    because 1 + 8 is NOT equal to 8.

  • I change the assertion to make it True

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

    the test passes.

  • I add an assertion for 1 + 9

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

    the terminal is my friend, and shows AssertionError

    E       assert (1 + 9) == 9
    

    because 1 + 9 is NOT equal to 9.

  • I change the assertion to make it True

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

    the test passes.

  • all 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 one

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

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

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

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

    2 + 0 is equal to 2.

  • I use the add_x function for the other assertions

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

    still green.

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

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

    the terminal is my friend, and shows AssertionError

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

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

    the test passes.

  • I remove the commented lines

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

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

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

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

  • I can use a function to organize tests


test_positional_arguments

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


RED: make it fail


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

  • I add a test

    58    assert add_x(9) == 12
    59
    60
    61def test_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.


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 called positional_arguments which belongs to test_positional_arguments with one input ('first').

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

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

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

    61def test_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.

  • 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 called positional_arguments which belongs to test_positional_arguments with two inputs('first' and 'last').

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

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

  • I make the function take two inputs by changing the name of the first input to be clearer, and adding a name in 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. Using substitution since I can treat a call to a function as the object it returns

    positional_arguments('first'    , 'last'    )
    positional_arguments(first_input, last_input)
        return None
    
    assert positional_arguments('first', 'last') == ('first', 'last')
    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)

    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.

  • The problem with giving arguments this way is that they always have to be in the order in the function definition or I get something different. 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 the call in this assertion sends 'last' as first_input and 'first' as last_input. Using substitution since I can treat a call to a function as the object it returns

    positional_arguments('last'     , 'first'   )
    positional_arguments(first_input, last_input)
        return first_input, last_input
        return 'last'     , 'first'
    
    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'

    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'

    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

    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    assert (
    88        positional_arguments(0, 1)
    89     == (1, 0)
    90    )
    91
    92
    93# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

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

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

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

    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    assert (
    88        positional_arguments(0, 1)
    89    #  == (1, 0)
    90     == (0, 1)
    91    )
    92
    93
    94# Exceptions seen
    

    the test passes.

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

     74    assert (
     75    #     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    assert (
     88        positional_arguments(0, 1)
     89    #  == (1, 0)
     90     == (0, 1)
     91    )
     92
     93    a_tuple = (0, 1, 2, 'n')
     94    a_list = [0, 1, 2, 'n']
     95    assert (
     96        positional_arguments(a_list, a_tuple)
     97     == (a_tuple, a_list)
     98    )
     99
    100
    101# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        assert ([1, 2, 3, 'n...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

    a_tuple = (0, 1, 2, 'n')
    a_list = [0, 1, 2, 'n']
    
    positional_arguments(a_list        , a_tuple       )
    positional_arguments([0, 1, 2, 'n'], (0, 1, 2, 'n'))
        return first_input   , last_input
        return [0, 1, 2, 'n'], (0, 1, 2, 'n')
    
    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.

  • 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    assert (
    76        positional_arguments(0, 1)
    77     == (0, 1)
    78    )
    79
    80    a_tuple = (0, 1, 2, 'n')
    81    a_list = [0, 1, 2, 'n']
    82    assert (
    83        positional_arguments(a_tuple, a_list)
    84     == (a_tuple, a_list)
    85    )
    86
    87
    88# Exceptions seen
    
  • I add a git commit message in the other terminal

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

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

I can call functions with positional arguments.


test_keyword_arguments

The last test 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 new test to test_functions.py

    80    a_tuple = (0, 1, 2, 'n')
    81    a_list = [0, 1, 2, 'n']
    82    assert (
    83        positional_arguments(a_tuple, a_list)
    84     == (a_tuple, a_list)
    85    )
    86
    87
    88def test_keyword_arguments():
    89    assert keyword_arguments() == None
    90
    91
    92# 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

88def test_keyword_arguments():
89    def keyword_arguments():
90        return None
91
92    assert keyword_arguments() == None
93
94
95# Exceptions seen

the test passes.


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.


RED: make it fail


  • I add input to the function call with a name

    88def test_keyword_arguments():
    89    def keyword_arguments():
    90        return None
    91
    92    # assert keyword_arguments() == None
    93    assert keyword_arguments(first_input='first') == None
    94
    95
    96# 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 called keyword_arguments which belongs to test_keyword_arguments with a name (first_input) and a value for the name ('first').

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

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

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

    88def test_keyword_arguments():
    89    # def keyword_arguments():
    90    def keyword_arguments(the_input):
    91        return None
    92
    93    # assert keyword_arguments() == None
    94    assert keyword_arguments(first_input='first') == None
    95
    96
    97# 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 to match the one used in the call

    88def test_keyword_arguments():
    89    # def keyword_arguments():
    90    # def keyword_arguments(the_input):
    91    def keyword_arguments(first_input):
    92        return None
    93
    94    # assert keyword_arguments() == None
    95    assert keyword_arguments(first_input='first') == None
    96
    97
    98# Exceptions seen
    

    the test passes because I use the same name in the function definition when I call it with a keyword argument.

  • I add another input to the function call

     88def test_keyword_arguments():
     89    # def keyword_arguments():
     90    # def keyword_arguments(the_input):
     91    def keyword_arguments(first_input):
     92        return None
     93
     94    # assert keyword_arguments() == None
     95    # assert keyword_arguments(first_input='first') == None
     96    assert (
     97        keyword_arguments(
     98            first_input='first', last_input='last',
     99        )
    100     == None
    101    )
    102
    103
    104# 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 called keyword_arguments which belongs to test_keyword_arguments with two names (first_input and last_input) and values for the names ('first' and 'last').

    • The function definition (signature) of keyword_arguments only allows one input when it is called.

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

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

     88def test_keyword_arguments():
     89    # def keyword_arguments():
     90    # def keyword_arguments(the_input):
     91    # def keyword_arguments(first_input):
     92    def keyword_arguments(first_input, second_input):
     93        return None
     94
     95    # assert keyword_arguments() == None
     96    # assert keyword_arguments(first_input='first') == None
     97    assert (
     98        keyword_arguments(
     99            first_input='first', last_input='last',
    100        )
    101     == None
    102    )
    103
    104
    105# Exceptions seen
    

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

  • I change name of the input to match the one used in the call

     88def test_keyword_arguments():
     89    # def keyword_arguments():
     90    # def keyword_arguments(the_input):
     91    # def keyword_arguments(first_input):
     92    # def keyword_arguments(first_input, second_input):
     93    def keyword_arguments(first_input, last_input):
     94        return None
     95
     96    # assert keyword_arguments() == None
     97    # assert keyword_arguments(first_input='first') == None
     98    assert (
     99        keyword_arguments(
    100            first_input='first', last_input='last',
    101        )
    102     == None
    103    )
    104
    105
    106# Exceptions seen
    

    the test passes because I use the same name in the function definition when I call it with a keyword argument.

  • I change the expectation of the assertion

     88def test_keyword_arguments():
     89    # def keyword_arguments():
     90    # def keyword_arguments(the_input):
     91    # def keyword_arguments(first_input):
     92    # def keyword_arguments(first_input, the_input):
     93    def keyword_arguments(first_input, last_input):
     94        return None
     95
     96    # assert keyword_arguments() == None
     97    # assert keyword_arguments(first_input='first') == None
     98    assert (
     99        keyword_arguments(
    100            first_input='first', last_input='last',
    101        )
    102    #  == None
    103     == ('first', 'last')
    104    )
    105
    106
    107# 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

    keyword_arguments(first_input='first', last_input='last')
    keyword_arguments(first_input        , last_input       )
        return None
    
    assert keyword_arguments(first_input='first', last_input='last')
                         == ('first', 'last')
    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)

     88def test_keyword_arguments():
     89    # def keyword_arguments():
     90    # def keyword_arguments(the_input):
     91    # def keyword_arguments(first_input):
     92    # def keyword_arguments(first_input, the_input):
     93    def keyword_arguments(first_input, last_input):
     94        # return None
     95        return first_input, last_input
     96
     97    # assert keyword_arguments() == None
     98    # assert keyword_arguments(first_input='first') == None
     99    assert (
    100        keyword_arguments(
    101            first_input='first', last_input='last',
    102        )
    103    #  == None
    104     == ('first', 'last')
    105    )
    106
    107
    108# 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'.

  • The problem with giving arguments this way, is I must use the exact names. The advantage of giving arguments this way is that they do not have to be in the order in the function definition. I add an assertion with the keyword arguments given out of order

     97    # assert keyword_arguments() == None
     98    # assert keyword_arguments(first_input='first') == None
     99    assert (
    100        keyword_arguments(
    101            first_input='first', last_input='last',
    102        )
    103    #  == None
    104     == ('first', 'last')
    105    )
    106    assert (
    107        keyword_arguments(
    108            last_input='last', first_input='first',
    109        )
    110     == ('last', 'first')
    111    )
    112
    113
    114# 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 the call in this assertion sends 'last' as last_input and 'first' as first_input, the order does not matter because I used the names. Using substitution since I can treat a call to a function as the object it returns

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

     97    # assert keyword_arguments() == None
     98    # assert keyword_arguments(first_input='first') == None
     99    assert (
    100        keyword_arguments(
    101            first_input='first', last_input='last',
    102        )
    103    #  == None
    104     == ('first', 'last')
    105    )
    106    assert (
    107        keyword_arguments(
    108            last_input='last', first_input='first',
    109        )
    110    #  == ('last', 'first')
    111     == ('first', 'last')
    112    )
    113
    114
    115# Exceptions seen
    
  • I add variables for 'first' and 'last'

     97    # assert keyword_arguments() == None
     98    # assert keyword_arguments(first_input='first') == None
     99
    100    first, last = 'first', 'last'
    101
    102    assert (
    103        keyword_arguments(
    104            first_input='first', last_input='last',
    105        )
    106    #  == None
    107     == ('first', 'last')
    108    )
    109    assert (
    110        keyword_arguments(
    111            last_input='last', first_input='first',
    112        )
    113    #  == ('last', 'first')
    114     == ('first', 'last')
    115    )
    116
    117
    118# Exceptions seen
    
  • I use the variables to remove repetition of 'first' and 'last'

     97    # assert keyword_arguments() == None
     98    # assert keyword_arguments(first_input='first') == None
     99
    100    first, last = 'first', 'last'
    101
    102    assert (
    103        keyword_arguments(
    104            # first_input='first', last_input='last',
    105            first_input=first, last_input=last,
    106        )
    107    #  == None
    108    #  == ('first', 'last')
    109     == (first, last)
    110    )
    111    assert (
    112        keyword_arguments(
    113            # last_input='last', first_input='first',
    114            last_input=last, first_input=first,
    115        )
    116    #  == ('last', 'first')
    117    #  == ('first', 'last')
    118     == (first, last)
    119    )
    120
    121
    122# Exceptions seen
    

    the test is still green.

  • I add another assertion

    102    assert (
    103        keyword_arguments(
    104            # first_input='first', last_input='last',
    105            first_input=first, last_input=last,
    106        )
    107    #  == None
    108    #  == ('first', 'last')
    109     == (first, last)
    110    )
    111    assert (
    112        keyword_arguments(
    113            # last_input='last', first_input='first',
    114            last_input=last, first_input=first,
    115        )
    116    #  == ('last', 'first')
    117    #  == ('first', 'last')
    118     == (first, last)
    119    )
    120    assert (
    121        keyword_arguments(
    122            last_input=0, first_input=1,
    123        )
    124     == (0, 1)
    125    )
    126
    127
    128# 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 the call in this assertion sends 0 as last_input and 1 as first_input, the order does not matter because I used the names. Using substitution since I can treat a call to a function as the object it returns

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

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

    the test passes.

  • I add an assertion

    102    assert (
    103        keyword_arguments(
    104            # first_input='first', last_input='last',
    105            first_input=first, last_input=last,
    106        )
    107    #  == None
    108    #  == ('first', 'last')
    109     == (first, last)
    110    )
    111    assert (
    112        keyword_arguments(
    113            # last_input='last', first_input='first',
    114            last_input=last, first_input=first,
    115        )
    116    #  == ('last', 'first')
    117    #  == ('first', 'last')
    118     == (first, last)
    119    )
    120    assert (
    121        keyword_arguments(
    122            last_input=0, first_input=1,
    123        )
    124    #  == (0, 1)
    125     == (1, 0)
    126    )
    127
    128    a_tuple = (0, 1, 2, 'n')
    129    a_list = [0, 1, 2, 'n']
    130    assert (
    131        keyword_arguments(
    132            first_input=a_list,
    133            last_input=a_tuple,
    134        )
    135     == (a_tuple, a_list)
    136    )
    137
    138
    139# 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 assertion sends [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. Using substitution since I can treat a call to a function as the object it returns

    keyword_arguments(
        first_input=[0, 1, 2, 'n'], last_input=(0, 1, 2, 'n')
    )
    keyword_arguments(first_input, last_input)
        return first_input   , last_input
        return [0, 1, 2, 'n'], (0, 1, 2, 'n')
    
    assert keyword_arguments(
        first_input=[0, 1, 2, 'n'], last_input=(0, 1, 2, 'n')
    )   == ((0, 1, 2, 'n'), [0, 1, 2, 'n'])
    assert  [0, 1, 2, 'n'], (0, 1, 2, 'n')
        == ((0, 1, 2, 'n'), [0, 1, 2, 'n'])
    
  • I change reality to match my expectation

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

    the test passes.

  • keyword_arguments and positional_arguments are the same functions, 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

      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 add an assertion to show that the two functions are the same, by calling the positional_arguments function with keyword arguments

    128    a_tuple = (0, 1, 2, 'n')
    129    a_list = [0, 1, 2, 'n']
    130    assert (
    131        keyword_arguments(
    132            # first_input=a_list,
    133            # last_input=a_tuple,
    134            first_input=a_tuple,
    135            last_input=a_list,
    136        )
    137     == (a_tuple, a_list)
    138    )
    139
    140    a_set = {0, 1, 2, 'n'}
    141    a_dictionary = {'key': 'value'}
    142    assert (
    143        positional_arguments(
    144            last_input=a_dictionary,
    145            first_input=a_set,
    146        )
    147     == (a_set, a_dictionary)
    148    )
    149
    150
    151# 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.

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

    58    assert add_x(9) == 12
    59
    60
    61def positional_arguments(first_input, last_input):
    62    return first_input, last_input
    63
    64
    65def test_positional_arguments():
    66    first, last = 'first', 'last'
    67
    68    assert (
    69        positional_arguments(first, last)
    70     == (first, last)
    71    )
    72    assert (
    73        positional_arguments(last, first)
    74     == (last, first)
    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
    89def test_keyword_arguments():
    

    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,
    )
    
  • I add an assertion to test_positional_arguments to show that I can call the keyword_arguments function with positional arguments

    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    a_set = {0, 1, 2, 'n'}
    89    a_dictionary = {'key': 'value'}
    90    assert (
    91        keyword_arguments(
    92            a_set, a_dictionary,
    93        )
    94     == (a_set, a_dictionary)
    95    )
    96
    97
    98def 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 and I cannot reach it from outside.

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

     88    a_set = {0, 1, 2, 'n'}
     89    a_dictionary = {'key': 'value'}
     90    assert (
     91        keyword_arguments(
     92            a_set, a_dictionary,
     93        )
     94     == (a_set, a_dictionary)
     95    )
     96
     97
     98def keyword_arguments(first_input, last_input):
     99    return first_input, last_input
    100
    101
    102def test_keyword_arguments():
    103    # def keyword_arguments():
    104    # def keyword_arguments(the_input):
    105    # def keyword_arguments(first_input):
    106    # def keyword_arguments(first_input, the_input):
    107        # return None
    108
    109    # assert keyword_arguments() == None
    110    # assert keyword_arguments(first_input='first') == None
    111
    112    first, last = 'first', 'last'
    

    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,
    )
    
  • I remove the commented lines

     88    a_set = {0, 1, 2, 'n'}
     89    a_dictionary = {'key': 'value'}
     90    assert (
     91        keyword_arguments(
     92            a_set, a_dictionary,
     93        )
     94     == (a_set, a_dictionary)
     95    )
     96
     97
     98def keyword_arguments(first_input, last_input):
     99    return first_input, last_input
    100
    101
    102def test_keyword_arguments():
    103    first, last = 'first', 'last'
    104
    105    assert (
    106        keyword_arguments(
    107            first_input=first, last_input=last,
    108        )
    109     == (first, last)
    110    )
    111    assert (
    112        keyword_arguments(
    113            last_input=last, first_input=first,
    114        )
    115     == (first, last)
    116    )
    117    assert (
    118        keyword_arguments(
    119            last_input=0, first_input=1,
    120        )
    121     == (1, 0)
    122    )
    123
    124    a_tuple = (0, 1, 2, 'n')
    125    a_list = [0, 1, 2, 'n']
    126    assert (
    127        keyword_arguments(
    128            first_input=a_tuple,
    129            last_input=a_list,
    130        )
    131     == (a_tuple, a_list)
    132    )
    133
    134    a_set = {0, 1, 2, 'n'}
    135    a_dictionary = {'key': 'value'}
    136    assert (
    137        positional_arguments(
    138            last_input=a_dictionary,
    139            first_input=a_set,
    140        )
    141     == (a_set, a_dictionary)
    142    )
    143
    144
    145# 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

I can call functions with both positional.


RED: make it fail


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

  • I add a test

    134    a_set = {0, 1, 2, 'n'}
    135    a_dictionary = {'key': 'value'}
    136    assert (
    137        positional_arguments(
    138            last_input=a_dictionary,
    139            first_input=a_set,
    140        )
    141     == (a_set, a_dictionary)
    142    )
    143
    144
    145def test_args_and_kwargs():
    146    assert (
    147        args_and_kwargs(
    148            last_input='last', 'first',
    149        )
    150     == ('first', 'last')
    151    )
    152
    153
    154# 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

    153# Exceptions seen
    154# AssertionError
    155# NameError
    156# TypeError
    157# SyntaxError
    
  • I change the order of the arguments to follow Python rules

    145def test_args_and_kwargs():
    146    assert (
    147        args_and_kwargs(
    148            # last_input='last', 'first',
    149            'first', last_input='last',
    150        )
    151     == ('first', 'last')
    152    )
    153
    154
    155# 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

    145def test_args_and_kwargs():
    146    def args_and_kwargs():
    147        return None
    148
    149    assert (
    150        args_and_kwargs(
    151            # last_input='last', 'first',
    152            'first', last_input='last',
    153        )
    154     == ('first', 'last')
    155    )
    156
    157
    158# 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 the name in parentheses

    145def test_args_and_kwargs():
    146    # def args_and_kwargs():
    147    def args_and_kwargs(last_input):
    148        return None
    149
    150    assert (
    151        args_and_kwargs(
    152            # last_input='last', 'first',
    153            'first', last_input='last',
    154        )
    155     == ('first', 'last')
    156    )
    157
    158
    159# 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 the definition for args_and_kwargs takes one argument (last_input), and the assertion calls the function with two arguments ('first', last_input='last'). How does Python know which value to use for last_input if I use the position and the keyword?

  • I add another name in parentheses to make it clearer

    145def test_args_and_kwargs():
    146    # def args_and_kwargs():
    147    # def args_and_kwargs(last_input):
    148    def args_and_kwargs(last_input, first_input):
    149        return None
    150
    151    assert (
    152        args_and_kwargs(
    153            # last_input='last', 'first',
    154            'first', last_input='last',
    155        )
    156     == ('first', 'last')
    157    )
    158
    159
    160# Exceptions seen
    

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

  • The call tells it that the values for last_input are both 'first' and 'last', it would be like defining the function with the same name twice

    145def test_args_and_kwargs():
    146    # def args_and_kwargs():
    147    # def args_and_kwargs(last_input):
    148    # def args_and_kwargs(last_input, first_input):
    149    def args_and_kwargs(last_input, last_input):
    150        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

    145def test_args_and_kwargs():
    146    # def args_and_kwargs():
    147    # def args_and_kwargs(last_input):
    148    # def args_and_kwargs(last_input, first_input):
    149    # def args_and_kwargs(last_input, last_input):
    150    def args_and_kwargs(first_input, last_input):
    151        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. Using substitution

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

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

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

    145def test_args_and_kwargs():
    146    # def args_and_kwargs():
    147    # def args_and_kwargs(last_input):
    148    # def args_and_kwargs(last_input, first_input):
    149    # def args_and_kwargs(last_input, last_input):
    150    def args_and_kwargs(first_input, last_input):
    151        # return None
    152        return first_input, last_input
    153
    154    assert (
    155        args_and_kwargs(
    156            # last_input='last', 'first',
    157            'first', last_input='last',
    158        )
    159     == ('first', 'last')
    160    )
    161
    162
    163# Exceptions seen
    

    the test passes.

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

    145def test_args_and_kwargs():
    146    # def args_and_kwargs():
    147    # def args_and_kwargs(last_input):
    148    # def args_and_kwargs(last_input, first_input):
    149    # def args_and_kwargs(last_input, last_input):
    150    def args_and_kwargs(first_input, last_input):
    151        # return None
    152        return first_input, last_input
    153
    154    first, last = 'first', 'last'
    155
    156    assert (
    157        args_and_kwargs(
    158            # last_input='last', 'first',
    159            'first', last_input='last',
    160        )
    161     == ('first', 'last')
    162    )
    163
    164
    165# Exceptions seen
    
  • I the new variables to remove repetition of 'first' and 'last'

    145def test_args_and_kwargs():
    146    # def args_and_kwargs():
    147    # def args_and_kwargs(last_input):
    148    # def args_and_kwargs(last_input, first_input):
    149    # def args_and_kwargs(last_input, last_input):
    150    def args_and_kwargs(first_input, last_input):
    151        # return None
    152        return first_input, last_input
    153
    154    first, last = 'first', 'last'
    155
    156    assert (
    157        args_and_kwargs(
    158            # last_input='last', 'first',
    159            # 'first',last_input='last',
    160            first, last_input=last,
    161        )
    162    #  == ('first', 'last')
    163     == (first, last)
    164    )
    165
    166
    167# Exceptions seen
    

    the test is still green.

  • I remove the commented lines

    145def test_args_and_kwargs():
    146    def args_and_kwargs(first_input, last_input):
    147        return first_input, last_input
    148
    149    first, last = 'first', 'last'
    150
    151    assert (
    152        args_and_kwargs(
    153            first, last_input=last,
    154        )
    155     == (first, last)
    156    )
    157
    158
    159# 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

    145def test_args_and_kwargs():
    146    def args_and_kwargs(first_input, last_input):
    147        return first_input, last_input
    148
    149    first, last = 'first', 'last'
    150
    151    assert (
    152        args_and_kwargs(
    153            first, last_input=last,
    154        )
    155     == (first, last)
    156    )
    157
    158
    159def test_optional_arguments():
    160    first_name, last_name = 'jane', 'doe'
    161    assert (
    162        optional_arguments(
    163            first_name, last_input=last_name,
    164        )
    165     == (first_name, last_name)
    166    )
    167
    168
    169# 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

159def test_optional_arguments():
160    def optional_arguments(first_input, last_input):
161        return first_input, last_input
162
163    first_name, last_name = 'jane', 'doe'
164    assert (
165        optional_arguments(
166            first_name, last_input=last_name,
167        )
168      == (first_name, last_name)
169    )
170
171
172# 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

    159def test_optional_arguments():
    160    def optional_arguments(first_input, last_input):
    161        return first_input, last_input
    162
    163    first_name, last_name = 'jane', 'doe'
    164    assert (
    165        optional_arguments(
    166            # first_name, last_input=last_name,
    167            first_name,
    168        )
    169     == (first_name, last_name)
    170    )
    171
    172
    173# 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

    159def test_optional_arguments():
    160    # def optional_arguments(first_input, last_input):
    161    def optional_arguments(
    162        first_input, last_input='doe',
    163    ):
    164        return first_input, last_input
    165
    166    first_name, last_name = 'jane', 'doe'
    167    assert (
    168        optional_arguments(
    169            # first_name, last_input=last_name,
    170            first_name,
    171        )
    172     == (first_name, last_name)
    173    )
    174
    175
    176# 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 the default value for the last_input parameter of the function is doe

    optional_arguments('jane')
    optional_arguments(first_input, last_input='doe')
    

    is the same as

    optional_arguments('jane'     , last_input='doe')
    optional_arguments(first_input, last_input='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

    168    assert (
    169        optional_arguments(
    170            # first_name, last_input=last_name,
    171            first_name,
    172        )
    173     == (first_name, last_name)
    174    )
    175
    176    first_name, blow = 'joe', 'blow'
    177    assert (
    178        optional_arguments(
    179            first_name, blow
    180        )
    181     == ()
    182    )
    183
    184
    185# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

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

    176    first_name, blow = 'joe', 'blow'
    177    assert (
    178        optional_arguments(
    179            first_name, blow
    180        )
    181    #  == ()
    182     == (first_name, blow)
    183    )
    184
    185
    186# Exceptions seen
    

    the test passes.

  • I add another assertion

    176    first_name, blow = 'joe', 'blow'
    177    assert (
    178        optional_arguments(
    179            first_name, blow
    180        )
    181    #  == ()
    182     == (first_name, blow)
    183    )
    184
    185    first_name = 'john'
    186    assert (
    187        optional_arguments(
    188            first_input=first_name
    189        )
    190     == ()
    191    )
    192
    193
    194# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

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

    185    first_name = 'john'
    186    assert (
    187        optional_arguments(
    188            first_input=first_name
    189        )
    190    #  == ()
    191     == (first_name, last_name)
    192    )
    193
    194
    195# Exceptions seen
    

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

    optional_arguments('john')
    optional_arguments(first_input, last_input='doe')
    

    is the same as

    optional_arguments('john'     , last_input='doe')
    optional_arguments(first_input, last_input='doe')
    

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

  • I add one more assertion

    185    first_name = 'john'
    186    assert (
    187        optional_arguments(
    188            first_input=first_name
    189        )
    190    #  == ()
    191     == (first_name, last_name)
    192    )
    193
    194    last_name = 'smith'
    195    assert (
    196        optional_arguments(
    197            last_input=last_name,
    198            first_input=first_name,
    199        )
    200     == (last_name, first_name)
    201    )
    202
    203
    204# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

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

    194    last_name = 'smith'
    195    assert (
    196        optional_arguments(
    197            last_input=last_name,
    198            first_input=first_name,
    199        )
    200    #  == (last_name, first_name)
    201     == (first_name, last_name)
    202    )
    203
    204
    205# Exceptions seen
    

    the test passes.

  • I remove the commented lines

    159def test_optional_arguments():
    160    def optional_arguments(
    161        first_input, last_input='doe',
    162    ):
    163        return first_input, last_input
    164
    165    first_name, last_name = 'jane', 'doe'
    166
    167    assert (
    168        optional_arguments(
    169            first_name,
    170        )
    171     == (first_name, last_name)
    172    )
    173
    174    first_name, blow = 'joe', 'blow'
    175    assert (
    176        optional_arguments(
    177            first_name, blow
    178        )
    179     == (first_name, blow)
    180    )
    181
    182    first_name = 'john'
    183    assert (
    184        optional_arguments(
    185            first_input=first_name
    186        )
    187     == (first_name, last_name)
    188    )
    189
    190    last_name = 'smith'
    191    assert (
    192        optional_arguments(
    193            last_input=last_name,
    194            first_input=first_name,
    195        )
    196     == (first_name, last_name)
    197    )
    198
    199
    200# 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

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 a new test to test_functions.py

    190    last_name = 'smith'
    191    assert (
    192        optional_arguments(
    193            last_input=last_name,
    194            first_input=first_name,
    195        )
    196     == (first_name, last_name)
    197    )
    198
    199
    200def test_unknown_number_of_arguments():
    201    assert (
    202        unknown_number_of_arguments(
    203            0, 1, a=2, b=3,
    204        )
    205     == None
    206    )
    207
    208
    209# 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

    200def test_unknown_number_of_arguments():
    201    def unknown_number_of_arguments():
    202        return None
    203
    204    assert (
    205        unknown_number_of_arguments(
    206            0, 1, a=2, b=3,
    207        )
    208     == None
    209    )
    210
    211
    212# 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 the name to the function definition

    200def test_unknown_number_of_arguments():
    201    # def unknown_number_of_arguments():
    202    def unknown_number_of_arguments(a):
    203        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.


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 expressions (**).

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

    200def test_unknown_number_of_arguments():
    201    # def unknown_number_of_arguments():
    202    # def unknown_number_of_arguments(a):
    203    def unknown_number_of_arguments(**kwargs):
    204        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 a name for the first positional argument

    200def test_unknown_number_of_arguments():
    201    # def unknown_number_of_arguments():
    202    # def unknown_number_of_arguments(a):
    203    # def unknown_number_of_arguments(**kwargs):
    204    def unknown_number_of_arguments(**kwargs, x):
    205        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

    200def test_unknown_number_of_arguments():
    201    # def unknown_number_of_arguments():
    202    # def unknown_number_of_arguments(a):
    203    # def unknown_number_of_arguments(**kwargs):
    204    # def unknown_number_of_arguments(**kwargs, x):
    205    def unknown_number_of_arguments(x, **kwargs):
    206        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 a name for the other positional argument

    200def test_unknown_number_of_arguments():
    201    # def unknown_number_of_arguments():
    202    # def unknown_number_of_arguments(a):
    203    # def unknown_number_of_arguments(**kwargs):
    204    # def unknown_number_of_arguments(**kwargs, x):
    205    # def unknown_number_of_arguments(x, **kwargs):
    206    def unknown_number_of_arguments(x, y, **kwargs):
    207        return None
    208
    209    assert (
    210        unknown_number_of_arguments(
    211            0, 1, a=2, b=3,
    212        )
    213     == None
    214    )
    215
    216
    217# Exceptions seen
    

    the test passes.


REFACTOR: make it better


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

    209    assert (
    210        unknown_number_of_arguments(
    211            0, 1, a=2, b=3,
    212        )
    213     == None
    214    )
    215
    216    assert (
    217        unknown_number_of_arguments(
    218            0, 1, a=2, b=3, c=4,
    219        )
    220     == None
    221    )
    222
    223
    224# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

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

    216    assert (
    217        unknown_number_of_arguments(
    218            0, 1, a=2, b=3, c=4,
    219        )
    220    #  == ()
    221     == None
    222    )
    223
    224
    225# Exceptions seen
    

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

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

    216    assert (
    217        unknown_number_of_arguments(
    218            0, 1, a=2, b=3, c=4,
    219        )
    220    #  == ()
    221     == None
    222    )
    223
    224    assert (
    225        unknown_number_of_arguments(
    226            0, 1, 2, a=3, b=4, c=5,
    227        )
    228     == None
    229    )
    230
    231
    232# 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

    200def test_unknown_number_of_arguments():
    201    # def unknown_number_of_arguments():
    202    # def unknown_number_of_arguments(a):
    203    # def unknown_number_of_arguments(**kwargs):
    204    # def unknown_number_of_arguments(**kwargs, x):
    205    # def unknown_number_of_arguments(x, **kwargs):
    206    # def unknown_number_of_arguments(x, y, **kwargs):
    207    def unknown_number_of_arguments(x, y, z, **kwargs):
    208        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 assertion calls the function with two positional arguments and it now requires three.


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 starred expressions (*).

  • I use a starred expressions to replace the positional arguments

    200def test_unknown_number_of_arguments():
    201    # def unknown_number_of_arguments():
    202    # def unknown_number_of_arguments(a):
    203    # def unknown_number_of_arguments(**kwargs):
    204    # def unknown_number_of_arguments(**kwargs, x):
    205    # def unknown_number_of_arguments(x, **kwargs):
    206    # def unknown_number_of_arguments(x, y, **kwargs):
    207    # def unknown_number_of_arguments(x, y, z, **kwargs):
    208    def unknown_number_of_arguments(*args, **kwargs):
    209        return None
    

    the test passes.

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

    200def test_unknown_number_of_arguments():
    201    # def unknown_number_of_arguments():
    202    # def unknown_number_of_arguments(a):
    203    # def unknown_number_of_arguments(**kwargs):
    204    # def unknown_number_of_arguments(**kwargs, x):
    205    # def unknown_number_of_arguments(x, **kwargs):
    206    # def unknown_number_of_arguments(x, y, **kwargs):
    207    # def unknown_number_of_arguments(x, y, z, **kwargs):
    208    # def unknown_number_of_arguments(*args, **kwargs):
    209    def unknown_number_of_arguments(
    210        *positional_arguments, **keyword_arguments
    211    ):
    212        return None
    

how Python reads starred and double starred expressions


how Python reads starred expressions

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

    200def test_unknown_number_of_arguments():
    201    # def unknown_number_of_arguments():
    202    # def unknown_number_of_arguments(a):
    203    # def unknown_number_of_arguments(**kwargs):
    204    # def unknown_number_of_arguments(**kwargs, x):
    205    # def unknown_number_of_arguments(x, **kwargs):
    206    # def unknown_number_of_arguments(x, y, **kwargs):
    207    # def unknown_number_of_arguments(x, y, z, **kwargs):
    208    # def unknown_number_of_arguments(*args, **kwargs):
    209    def unknown_number_of_arguments(
    210        *positional_arguments, **keyword_arguments
    211    ):
    212        # return None
    213        return positional_arguments, keyword_arguments
    214
    215    a_tuple = (0, 1)
    216    a_dictionary = {'a': 2, 'b': 3}
    217    assert (
    218        unknown_number_of_arguments(
    219            0, 1, a=2, b=3,
    220        )
    221    #  == None
    222     == ((0, 1), {'a': 2, 'b': 3})
    223    )
    
  • I use the variables to remove repetition of the values

    215    a_tuple = (0, 1)
    216    a_dictionary = {'a': 2, 'b': 3}
    217    assert (
    218        unknown_number_of_arguments(
    219            # 0, 1, a=2, b=3,
    220            a_tuple, a_dictionary
    221        )
    222    #  == None
    223    #  == ((0, 1), {'a': 2, 'b': 3})
    224     == (a_tuple, a_dictionary)
    225    )
    

    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

    Using substitution

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

    215    a_tuple = (0, 1)
    216    a_dictionary = {'a': 2, 'b': 3}
    217    assert (
    218        unknown_number_of_arguments(
    219            # 0, 1, a=2, b=3,
    220            # a_tuple, a_dictionary
    221            *a_tuple, **a_dictionary
    222        )
    223    #  == None
    224    #  == ((0, 1), {'a': 2, 'b': 3})
    225     == (a_tuple, a_dictionary)
    226    )
    227
    228    assert (
    229        unknown_number_of_arguments(
    230            0, 1, a=2, b=3, c=4,
    231        )
    232    #  == ()
    233    #  == None
    234     == ((0, 1), {'a': 2, 'b': 3, 'c': 4})
    235    )
    

    the test passes because given

    a_tuple = (0, 1)
    a_dictionary = {'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 variables for the tuple and dictionary of the second assertion

    215    a_tuple = (0, 1)
    216    a_dictionary = {'a': 2, 'b': 3}
    217    assert (
    218        unknown_number_of_arguments(
    219            # 0, 1, a=2, b=3,
    220            # a_tuple, a_dictionary
    221            *a_tuple, **a_dictionary
    222        )
    223    #  == None
    224    #  == ((0, 1), {'a': 2, 'b': 3})
    225     == (a_tuple, a_dictionary)
    226    )
    227
    228    a_tuple = (0, 1)
    229    a_dictionary = {'a': 2, 'b': 3, 'c': 4}
    230    assert (
    231        unknown_number_of_arguments(
    232            0, 1, a=2, b=3, c=4,
    233        )
    234    #  == ()
    235    #  == None
    236     == ((0, 1), {'a': 2, 'b': 3, 'c': 4})
    237    )
    
  • I use the variables to remove repetition of the tuple and dictionary

    228    a_tuple = (0, 1)
    229    a_dictionary = {'a': 2, 'b': 3, 'c': 4}
    230    assert (
    231        unknown_number_of_arguments(
    232            # 0, 1, a=2, b=3, c=4,
    233            a_tuple, a_dictionary
    234        )
    235    #  == ()
    236    #  == None
    237    #  == ((0, 1), {'a': 2, 'b': 3, 'c': 4})
    238     == (a_tuple, a_dictionary)
    239    )
    

    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

    Using substitution

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

    228    a_tuple = (0, 1)
    229    a_dictionary = {'a': 2, 'b': 3, 'c': 4}
    230    assert (
    231        unknown_number_of_arguments(
    232            # 0, 1, a=2, b=3, c=4,
    233            # a_tuple, a_dictionary
    234            *a_tuple, **a_dictionary
    235        )
    236    #  == ()
    237    #  == None
    238    #  == ((0, 1), {'a': 2, 'b': 3, 'c': 4})
    239     == (a_tuple, a_dictionary)
    240    )
    

    the test passes because given

    a_tuple = (0, 1)
    a_dictionary = {'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 last assertion

    228    a_tuple = (0, 1)
    229    a_dictionary = {'a': 2, 'b': 3, 'c': 4}
    230    assert (
    231        unknown_number_of_arguments(
    232            # 0, 1, a=2, b=3, c=4,
    233            # a_tuple, a_dictionary
    234            *a_tuple, **a_dictionary
    235        )
    236    #  == ()
    237    #  == None
    238    #  == ((0, 1), {'a': 2, 'b': 3, 'c': 4})
    239     == (a_tuple, a_dictionary)
    240    )
    241
    242    a_tuple = (0, 1, 2)
    243    a_dictionary = {'a': 3, 'b': 4, 'c': 5}
    244    assert (
    245        unknown_number_of_arguments(
    246            0, 1, 2, a=3, b=4, c=5,
    247        )
    248    #  == None
    249     == ((0, 1, 2), {'a': 3, 'b': 4, 'c': 5})
    250    )
    251
    252
    253# Exceptions seen
    
  • I use the variables to remove repetition of the tuple and dictionary from the last assertion

    242    a_tuple = (0, 1, 2)
    243    a_dictionary = {'a': 3, 'b': 4, 'c': 5}
    244    assert (
    245        unknown_number_of_arguments(
    246            # 0, 1, 2, a=3, b=4, c=5,
    247            a_tuple, a_dictionary
    248        )
    249    #  == None
    250    #  == ((0, 1, 2), {'a': 3, 'b': 4, 'c': 5})
    251     == (a_tuple, a_dictionary)
    252    )
    253
    254
    255# 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

    Using substitution

    a_tuple = (0, 1, 2)
    a_dictionary = {'a': 3, 'b': 4, 'c': 5}
    unknown_number_of_arguments(
        a_tuple, a_dictionary
    )
    unknown_number_of_arguments(
        *positional_arguments, **keyword_arguments
    )
        positional_arguments = (a_tuple, a_dictionary)
        keyword_arguments = {}
        return  positional_arguments, keyword_arguments
        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

    242    a_tuple = (0, 1, 2)
    243    a_dictionary = {'a': 3, 'b': 4, 'c': 5}
    244    assert (
    245        unknown_number_of_arguments(
    246            # 0, 1, 2, a=3, b=4, c=5,
    247            # a_tuple, a_dictionary
    248            *a_tuple, **a_dictionary
    249        )
    250    #  == None
    251    #  == ((0, 1, 2), {'a': 3, 'b': 4, 'c': 5})
    252     == (a_tuple, a_dictionary)
    253    )
    254
    255
    256# Exceptions seen
    

    the test passes because given

    a_tuple = (0, 1, 2)
    a_dictionary = {'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

    242    a_tuple = (0, 1, 2)
    243    a_dictionary = {'a': 3, 'b': 4, 'c': 5}
    244    assert (
    245        unknown_number_of_arguments(
    246            # 0, 1, 2, a=3, b=4, c=5,
    247            # a_tuple, a_dictionary
    248            *a_tuple, **a_dictionary
    249        )
    250    #  == None
    251    #  == ((0, 1, 2), {'a': 3, 'b': 4, 'c': 5})
    252     == (a_tuple, a_dictionary)
    253    )
    254
    255    a_tuple = (0, 1, 2, 'n')
    256    assert (
    257        unknown_number_of_arguments(*a_tuple)
    258     == ()
    259    )
    260
    261
    262# 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

    given

    a_tuple = (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'   )
    

    Using substitution

    unknown_number_of_arguments(*a_tuple)
    unknown_number_of_arguments(
        *positional_arguments, **keyword_arguments
    )
        positional_arguments = (0, 1, 2, 'n')
        keyword_arguments = {}
        return  positional_arguments, keyword_arguments
        return ((0, 1, 2, 'n')      , {})
    
  • I change my expectation to match reality

    255    a_tuple = (0, 1, 2, 'n')
    256    assert (
    257        unknown_number_of_arguments(*a_tuple)
    258    #  == ()
    259     == (a_tuple, {})
    260    )
    261
    262
    263# Exceptions seen
    

    the test passes.


how Python reads double starred expressions

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


RED: make it fail


255      a_tuple = (0, 1, 2, 'n')
256      assert (
257          unknown_number_of_arguments(*a_tuple)
258      #  == ()
259       == (a_tuple, {})
260      )
261
262      a_dictionary = {'a': 1, 'b': 2, 'c': 3, 'd': 'n'}
263      assert (
264          unknown_number_of_arguments(**a_dictionary)
265       == ()
266      )
267
268
269  # 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

given

a_dictionary = {'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' )

Using substitution

unknown_number_of_arguments(**a_dictionary)
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'})

GREEN: make it pass


I change my expectation to match reality

262    a_dictionary = {'a': 1, 'b': 2, 'c': 3, 'd': 'n'}
263    assert (
264        unknown_number_of_arguments(**a_dictionary)
265    #  == ()
266     == ((), a_dictionary)
267    )
268
269
270# Exceptions seen

the test passes.


REFACTOR: make it better


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

    262    a_dictionary = {'a': 1, 'b': 2, 'c': 3, 'd': 'n'}
    263    assert (
    264        unknown_number_of_arguments(**a_dictionary)
    265    #  == ()
    266     == ((), a_dictionary)
    267    )
    268
    269    assert (
    270        unknown_number_of_arguments()
    271     == TypeError
    272    )
    273
    274
    275# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert ((), {}) == TypeError
    

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

    Using substitution

    unknown_number_of_arguments()
    unknown_number_of_arguments(
        *positional_arguments, **keyword_arguments
    )
        positional_arguments = ()
        keyword_arguments = {}
        return  positional_arguments, keyword_arguments
        return (()                  , {}               )
    
  • I change my expectation to match reality

    269    assert (
    270        unknown_number_of_arguments()
    271    #  == TypeError
    272     == ((), {})
    273    )
    274
    275
    276# Exceptions seen
    

    the test passes.

  • I remove the commented lines

    200def test_unknown_number_of_arguments():
    201    def unknown_number_of_arguments(
    202        *positional_arguments, **keyword_arguments
    203    ):
    204        return positional_arguments, keyword_arguments
    205
    206    a_tuple = (0, 1)
    207    a_dictionary = {'a': 2, 'b': 3}
    208    assert (
    209        unknown_number_of_arguments(
    210            *a_tuple, **a_dictionary
    211        )
    212     == (a_tuple, a_dictionary)
    213    )
    214
    215    a_tuple = (0, 1)
    216    a_dictionary = {'a': 2, 'b': 3, 'c': 4}
    217    assert (
    218        unknown_number_of_arguments(
    219            *a_tuple, **a_dictionary
    220        )
    221     == (a_tuple, a_dictionary)
    222    )
    223
    224    a_tuple = (0, 1, 2)
    225    a_dictionary = {'a': 3, 'b': 4, 'c': 5}
    226    assert (
    227        unknown_number_of_arguments(
    228            *a_tuple, **a_dictionary
    229        )
    230     == (a_tuple, a_dictionary)
    231    )
    232
    233    a_tuple = (0, 1, 2, 'n')
    234    assert (
    235        unknown_number_of_arguments(*a_tuple)
    236     == (a_tuple, {})
    237    )
    238
    239    a_dictionary = {'a': 1, 'b': 2, 'c': 3, 'd': 'n'}
    240    assert (
    241        unknown_number_of_arguments(**a_dictionary)
    242     == ((), a_dictionary)
    243    )
    244
    245    assert (
    246        unknown_number_of_arguments()
    247     == ((), {})
    248    )
    249
    250
    251# Exceptions seen
    252# AssertionError
    253# NameError
    254# TypeError
    255# 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?

you have covered a bit so far and know

I am going for a walk. Would you like to test using a function to make a string from input?


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.