what is a function?

A function is code that is callable, this 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

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():
    the body of the function
    ...

preview

I have these tests by the end of the chapter

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

questions about functions

Questions to think about as I go through the chapter


start the project

  • I name this project functions

  • I open a terminal

  • I use uv to make a directory for the project and initialize it

    uv init functions
    

    the terminal shows

    Initialized project `functions`
    at `.../pumping_python/functions`
    

    then goes back to the command line.

  • I change directory to the project

    cd functions
    

    the terminal shows I am in the functions folder

    .../pumping_python/functions
    
  • I make a directory for the source code

    mkdir src
    

    the terminal goes back to the command line.

    .../pumping_python/functions
    
  • I use the mv program to change the name of main.py to functions.py and move it to the src folder

    mv main.py src/functions.py
    
    Move-Item main.py src/functions.py
    

    the terminal goes back to the command line.

  • I make a directory for the tests

    mkdir tests
    

    the terminal goes back to the command line.

  • I make the tests directory a Python package

    Danger

    use 2 underscores (__) before and after init for __init__.py not _init_.py

    touch tests/__init__.py
    
    New-Item tests/__init__.py
    

    the terminal goes back to the command line.

  • I make a Python file for the tests in the tests directory

    touch tests/test_functions.py
    
    New-Item tests/test_functions.py
    

    the terminal goes back to the command line.

  • I open test_functions.py

  • I add the first failing test to test_functions.py

    1import unittest
    2
    3
    4class TestFunctions(unittest.TestCase):
    5
    6    def test_failure(self):
    7        self.assertFalse(True)
    
  • I go back to the terminal to make a requirements file for the Python packages I need

    echo "pytest" > requirements.txt
    

    the terminal goes back to the command line.

  • I add pytest-watcher to the requirements file

    echo "pytest-watcher" >> requirements.txt
    

    the terminal goes back to the command line.

  • I use uv to install pytest-watcher with the requirements file

    uv add --requirement requirements.txt
    

    the terminal shows that it installed pytest-watcher and its dependencies.

  • I add the new files and folders to git for tracking

    git add .
    

    the terminal goes back to the command line.

  • I add a git commit message

    git commit --all --message 'setup project'
    

    the terminal shows

    [main (root-commit) a0b12c3] setup project
     9 files changed, 148 insertions(+)
     create mode 100644 .gitignore
     create mode 100644 .python-version
     create mode 100644 README.md
     create mode 100644 pyproject.toml
     create mode 100644 requirements.txt
     create mode 100644 src/functions.py
     create mode 100644 tests/__init__.py
     create mode 100644 tests/test_functions.py
     create mode 100644 uv.lock
    

    then goes back to the command line.

  • I use pytest-watcher to run the tests automatically

    uv run pytest-watcher . --now
    

    the terminal is my friend, and shows AssertionError

    ======================== FAILURES ========================
    _______________________ TestFunctions.test_failure _______________________
    
    self = <tests.test_functions.TestFunctions testMethod=test_failure>
    
        def test_failure(self):
    >       self.assertFalse(True)
    E       AssertionError: True is not false
    
    tests/test_functions.py:7: AssertionError
    ================ short test summary info =================
    FAILED tests/test_functions.py::TestFunctions::test_failure - AssertionError: True is not false
    =================== 1 failed in X.YZs ====================
    

    because True is NOT False

    if the terminal does not show the same error, then check

    • if your tests/__init__.py has two underscores (__) before and after init for __init__.py not _init_.py

    • if you ran echo "pytest-watcher" >> requirements.txt, to add pytest-watcher to the requirements file

    fix those errors and try to run uv run pytest-watcher . --now again

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

     4class TestFunctions(unittest.TestCase):
     5
     6    def test_failure(self):
     7        self.assertFalse(True)
     8
     9
    10# Exceptions seen
    11# AssertionError
    
  • then I change True to False in the assertion

    7        self.assertFalse(False)
    

    the test passes.


test_why_use_a_function

Why would I use a function when I can just write code to do the thing I want?


RED: make it fail


  • I change test_failure to test_why_use_a_function with an assertion

     4class TestFunctions(unittest.TestCase):
     5
     6    def test_why_use_a_function(self):
     7        reality = 1 + 0
     8        my_expectation = 0
     9        self.assertEqual(reality, my_expectation)
    10
    11
    12# Exceptions seen
    13# AssertionError
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 1 != 0
    

    because 1 is NOT equal to 0

    • reality is the name or variable I gave to the result of 1 + 0

    • my_expectation is the name or variable I gave to 0

    • assertEqual(reality, my_expectation) is asking Python if 1 + 0 is equal to 0


GREEN: make it pass


I change my_expectation to match reality

 6      def test_why_use_a_function(self):
 7          reality = 1 + 0
 8          # my_expectation = 0
 9          my_expectation = 1
10          self.assertEqual(reality, my_expectation)
11
12
13  # Exceptions seen

the test passes.


REFACTOR: make it better


  • I add another assertion

     6    def test_why_use_a_function(self):
     7        reality = 1 + 0
     8        # my_expectation = 0
     9        my_expectation = 1
    10        self.assertEqual(reality, my_expectation)
    11
    12        reality = 1 + 1
    13        my_expectation = 1
    14        self.assertEqual(reality, my_expectation)
    15
    16
    17# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 2 != 1
    
  • I change my_expectation to 2

    11        reality = 1 + 1
    12        # my_expectation = 1
    13        my_expectation = 2
    14        self.assertEqual(reality, my_expectation)
    15
    16
    17# Exceptions seen
    

    the test passes.

  • I add another assertion

    12        reality = 1 + 1
    13        # my_expectation = 1
    14        my_expectation = 2
    15        self.assertEqual(reality, my_expectation)
    16
    17        reality = 1 + 2
    18        my_expectation = 2
    19        self.assertEqual(reality, my_expectation)
    20
    21
    22# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 3 != 2
    
  • I change my_expectation to 3

    17        reality = 1 + 2
    18        # my_expectation = 2
    19        my_expectation = 3
    20        self.assertEqual(reality, my_expectation)
    21
    22
    23# Exceptions seen
    

    the test passes.

  • I add an assertion

    17        reality = 1 + 2
    18        # my_expectation = 2
    19        my_expectation = 3
    20        self.assertEqual(reality, my_expectation)
    21
    22        reality = 1 + 3
    23        my_expectation = 3
    24        self.assertEqual(reality, my_expectation)
    25
    26
    27# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 4 != 3
    
  • I change my_expectation to 4

    19        reality = 1 + 3
    20        # my_expectation = 3
    21        my_expectation = 4
    22        self.assertEqual(reality, my_expectation)
    23
    24
    25# Exceptions seen
    

    the test passes.

  • I add an assertion

    22        reality = 1 + 3
    23        # my_expectation = 3
    24        my_expectation = 4
    25        self.assertEqual(reality, my_expectation)
    26
    27        reality = 1 + 4
    28        my_expectation = 4
    29        self.assertEqual(reality, my_expectation)
    30
    31
    32# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 5 != 4
    
  • I change my_expectation to 5

    27        reality = 1 + 4
    28        # my_expectation = 4
    29        my_expectation = 5
    30        self.assertEqual(reality, my_expectation)
    31
    32
    33# Exceptions seen
    

    the test passes.

  • I add an assertion

    27        reality = 1 + 4
    28        # my_expectation = 4
    29        my_expectation = 5
    30        self.assertEqual(reality, my_expectation)
    31
    32        reality = 1 + 5
    33        my_expectation = 5
    34        self.assertEqual(reality, my_expectation)
    35
    36
    37# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 6 != 5
    
  • I change my_expectation to 6

    32        reality = 1 + 5
    33        # my_expectation = 5
    34        my_expectation = 6
    35        self.assertEqual(reality, my_expectation)
    36
    37
    38# Exceptions seen
    

    the test passes.

  • I add another assertion

    32        reality = 1 + 5
    33        # my_expectation = 5
    34        my_expectation = 6
    35        self.assertEqual(reality, my_expectation)
    36
    37        reality = 1 + 6
    38        my_expectation = 6
    39        self.assertEqual(reality, my_expectation)
    40
    41
    42# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 7 != 6
    
  • I change my_expectation to 7

    37        reality = 1 + 6
    38        # my_expectation = 6
    39        my_expectation = 7
    40        self.assertEqual(reality, my_expectation)
    41
    42
    43# Exceptions seen
    

    the test passes.

  • I add an assertion

    37        reality = 1 + 6
    38        # my_expectation = 6
    39        my_expectation = 7
    40        self.assertEqual(reality, my_expectation)
    41
    42        reality = 1 + 7
    43        my_expectation = 7
    44        self.assertEqual(reality, my_expectation)
    45
    46
    47# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 8 != 7
    
  • I change my_expectation to 8

    35        reality = 1 + 7
    36        # my_expectation = 7
    37        my_expectation = 8
    38        self.assertEqual(reality, my_expectation)
    39
    40
    41# Exceptions seen
    

    the test passes.

  • I add another assertion

    42        reality = 1 + 7
    43        # my_expectation = 7
    44        my_expectation = 8
    45        self.assertEqual(reality, my_expectation)
    46
    47        reality = 1 + 8
    48        my_expectation = 8
    49        self.assertEqual(reality, my_expectation)
    50
    51
    52# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 9 != 8
    
  • I change my_expectation to 9

    47        reality = 1 + 8
    48        # my_expectation = 8
    49        my_expectation = 9
    50        self.assertEqual(reality, my_expectation)
    51
    52
    53# Exceptions seen
    

    the test passes.

  • I add an assertion

    47        reality = 1 + 8
    48        # my_expectation = 8
    49        my_expectation = 9
    50        self.assertEqual(reality, my_expectation)
    51
    52        reality = 1 + 9
    53        my_expectation = 9
    54        self.assertEqual(reality, my_expectation)
    55
    56
    57# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 10 != 9
    
  • I change my_expectation to 10

     6    def test_why_use_a_function(self):
     7        reality = 1 + 0
     8        # my_expectation = 0
     9        my_expectation = 1
    10        self.assertEqual(reality, my_expectation)
    11
    12        reality = 1 + 1
    13        # my_expectation = 1
    14        my_expectation = 2
    15        self.assertEqual(reality, my_expectation)
    16
    17        reality = 1 + 2
    18        # my_expectation = 2
    19        my_expectation = 3
    20        self.assertEqual(reality, my_expectation)
    21
    22        reality = 1 + 3
    23        # my_expectation = 3
    24        my_expectation = 4
    25        self.assertEqual(reality, my_expectation)
    26
    27        reality = 1 + 4
    28        # my_expectation = 4
    29        my_expectation = 5
    30        self.assertEqual(reality, my_expectation)
    31
    32        reality = 1 + 5
    33        # my_expectation = 5
    34        my_expectation = 6
    35        self.assertEqual(reality, my_expectation)
    36
    37        reality = 1 + 6
    38        # my_expectation = 6
    39        my_expectation = 7
    40        self.assertEqual(reality, my_expectation)
    41
    42        reality = 1 + 7
    43        # my_expectation = 7
    44        my_expectation = 8
    45        self.assertEqual(reality, my_expectation)
    46
    47        reality = 1 + 8
    48        # my_expectation = 8
    49        my_expectation = 9
    50        self.assertEqual(reality, my_expectation)
    51
    52        reality = 1 + 9
    53        # my_expectation = 9
    54        my_expectation = 10
    55        self.assertEqual(reality, my_expectation)
    56
    57
    58# Exceptions seen
    

    the test passes.

  • all those assertions test what happens when I add a number to 1, what 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 reality variables

     6    def test_why_use_a_function(self):
     7        # reality = 1 + 0
     8        reality = 2 + 0
     9        # my_expectation = 0
    10        my_expectation = 1
    11        self.assertEqual(reality, my_expectation)
    12
    13        # reality = 1 + 1
    14        reality = 2 + 1
    15        # my_expectation = 1
    16        my_expectation = 2
    17        self.assertEqual(reality, my_expectation)
    18
    19        # reality = 1 + 2
    20        reality = 2 + 2
    21        # my_expectation = 2
    22        my_expectation = 3
    23        self.assertEqual(reality, my_expectation)
    24
    25        # reality = 1 + 3
    26        reality = 2 + 3
    27        # my_expectation = 3
    28        my_expectation = 4
    29        self.assertEqual(reality, my_expectation)
    30
    31        # reality = 1 + 4
    32        reality = 2 + 4
    33        # my_expectation = 4
    34        my_expectation = 5
    35        self.assertEqual(reality, my_expectation)
    36
    37        # reality = 1 + 5
    38        reality = 2 + 5
    39        # my_expectation = 5
    40        my_expectation = 6
    41        self.assertEqual(reality, my_expectation)
    42
    43        # reality = 1 + 6
    44        reality = 2 + 6
    45        # my_expectation = 6
    46        my_expectation = 7
    47        self.assertEqual(reality, my_expectation)
    48
    49        # reality = 1 + 7
    50        reality = 2 + 7
    51        # my_expectation = 7
    52        my_expectation = 8
    53        self.assertEqual(reality, my_expectation)
    54
    55        # reality = 1 + 8
    56        reality = 2 + 8
    57        # my_expectation = 8
    58        my_expectation = 9
    59        self.assertEqual(reality, my_expectation)
    60
    61        # reality = 1 + 9
    62        reality = 2 + 9
    63        # my_expectation = 9
    64        my_expectation = 10
    65        self.assertEqual(reality, my_expectation)
    66
    67
    68# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 2 != 1
    
  • I change my_expectation for each assertion

     6    def test_why_use_a_function(self):
     7        # reality = 1 + 0
     8        reality = 2 + 0
     9        # my_expectation = 0
    10        # my_expectation = 1
    11        my_expectation = 2
    12        self.assertEqual(reality, my_expectation)
    13
    14        # reality = 1 + 1
    15        reality = 2 + 1
    16        # my_expectation = 1
    17        # my_expectation = 2
    18        my_expectation = 3
    19        self.assertEqual(reality, my_expectation)
    20
    21        # reality = 1 + 2
    22        reality = 2 + 2
    23        # my_expectation = 2
    24        # my_expectation = 3
    25        my_expectation = 4
    26        self.assertEqual(reality, my_expectation)
    27
    28        # reality = 1 + 3
    29        reality = 2 + 3
    30        # my_expectation = 3
    31        # my_expectation = 4
    32        my_expectation = 5
    33        self.assertEqual(reality, my_expectation)
    34
    35        # reality = 1 + 4
    36        reality = 2 + 4
    37        # my_expectation = 4
    38        # my_expectation = 5
    39        my_expectation = 6
    40        self.assertEqual(reality, my_expectation)
    41
    42        # reality = 1 + 5
    43        reality = 2 + 5
    44        # my_expectation = 5
    45        # my_expectation = 6
    46        my_expectation = 7
    47        self.assertEqual(reality, my_expectation)
    48
    49        # reality = 1 + 6
    50        reality = 2 + 6
    51        # my_expectation = 6
    52        # my_expectation = 7
    53        my_expectation = 8
    54        self.assertEqual(reality, my_expectation)
    55
    56        # reality = 1 + 7
    57        reality = 2 + 7
    58        # my_expectation = 7
    59        # my_expectation = 8
    60        my_expectation = 9
    61        self.assertEqual(reality, my_expectation)
    62
    63        # reality = 1 + 8
    64        reality = 2 + 8
    65        # my_expectation = 8
    66        # my_expectation = 9
    67        my_expectation = 10
    68        self.assertEqual(reality, my_expectation)
    69
    70        # reality = 1 + 9
    71        reality = 2 + 9
    72        # my_expectation = 9
    73        # my_expectation = 10
    74        my_expectation = 11
    75        self.assertEqual(reality, my_expectation)
    76
    77
    78# Exceptions seen
    

    the test passes.

  • I open a new terminal then change directories to functions

    cd functions
    

    the terminal shows I am in the functions folder

    .../pumping_python/functions
    
  • 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 to test_functions.py

    1import unittest
    2
    3
    4def add_x(number):
    5    return 2 + number
    6
    7
    8class TestFunctions(unittest.TestCase):
    
  • I use the new function for reality in the first assertion

     8class TestFunctions(unittest.TestCase):
     9
    10    def test_why_use_a_function(self):
    11        # reality = 1 + 0
    12        # reality = 2 + 0
    13        reality = add_x(0)
    14        # my_expectation = 0
    15        # my_expectation = 1
    16        my_expectation = 2
    17        self.assertEqual(reality, my_expectation)
    

    the test is still green.

  • I use the add_x function for the other assertions

    10    def test_why_use_a_function(self):
    11        # reality = 1 + 0
    12        # reality = 2 + 0
    13        reality = add_x(0)
    14        # my_expectation = 0
    15        # my_expectation = 1
    16        my_expectation = 2
    17        self.assertEqual(reality, my_expectation)
    18
    19        # reality = 1 + 1
    20        # reality = 2 + 1
    21        reality = add_x(1)
    22        # my_expectation = 1
    23        # my_expectation = 2
    24        my_expectation = 3
    25        self.assertEqual(reality, my_expectation)
    26
    27        # reality = 1 + 2
    28        # reality = 2 + 2
    29        reality = add_x(2)
    30        # my_expectation = 2
    31        # my_expectation = 3
    32        my_expectation = 4
    33        self.assertEqual(reality, my_expectation)
    34
    35        # reality = 1 + 3
    36        # reality = 2 + 3
    37        reality = add_x(3)
    38        # my_expectation = 3
    39        # my_expectation = 4
    40        my_expectation = 5
    41        self.assertEqual(reality, my_expectation)
    42
    43        # reality = 1 + 4
    44        # reality = 2 + 4
    45        reality = add_x(4)
    46        # my_expectation = 4
    47        # my_expectation = 5
    48        my_expectation = 6
    49        self.assertEqual(reality, my_expectation)
    50
    51        # reality = 1 + 5
    52        # reality = 2 + 5
    53        reality = add_x(5)
    54        # my_expectation = 5
    55        # my_expectation = 6
    56        my_expectation = 7
    57        self.assertEqual(reality, my_expectation)
    58
    59        # reality = 1 + 6
    60        # reality = 2 + 6
    61        reality = add_x(6)
    62        # my_expectation = 6
    63        # my_expectation = 7
    64        my_expectation = 8
    

    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

    1import unittest
    2
    3
    4def add_x(number):
    5    # return 2 + number
    6    return 3 + number
    7
    8
    9class TestFunctions(unittest.TestCase):
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 3 != 2
    
  • I change the expectations for the assertions one at a time

     11    def test_why_use_a_function(self):
     12        # reality = 1 + 0
     13        # reality = 2 + 0
     14        reality = add_x(0)
     15        # my_expectation = 0
     16        # my_expectation = 1
     17        # my_expectation = 2
     18        my_expectation = 3
     19        self.assertEqual(reality, my_expectation)
     20
     21        # reality = 1 + 1
     22        # reality = 2 + 1
     23        reality = add_x(1)
     24        # my_expectation = 1
     25        # my_expectation = 2
     26        # my_expectation = 3
     27        my_expectation = 4
     28        self.assertEqual(reality, my_expectation)
     29
     30        # reality = 1 + 2
     31        # reality = 2 + 2
     32        reality = add_x(2)
     33        # my_expectation = 2
     34        # my_expectation = 3
     35        # my_expectation = 4
     36        my_expectation = 5
     37        self.assertEqual(reality, my_expectation)
     38
     39        # reality = 1 + 3
     40        # reality = 2 + 3
     41        reality = add_x(3)
     42        # my_expectation = 3
     43        # my_expectation = 4
     44        # my_expectation = 5
     45        my_expectation = 6
     46        self.assertEqual(reality, my_expectation)
     47
     48        # reality = 1 + 4
     49        # reality = 2 + 4
     50        reality = add_x(4)
     51        # my_expectation = 4
     52        # my_expectation = 5
     53        # my_expectation = 6
     54        my_expectation = 7
     55        self.assertEqual(reality, my_expectation)
     56
     57        # reality = 1 + 5
     58        # reality = 2 + 5
     59        reality = add_x(5)
     60        # my_expectation = 5
     61        # my_expectation = 6
     62        # my_expectation = 7
     63        my_expectation = 8
     64        self.assertEqual(reality, my_expectation)
     65
     66        # reality = 1 + 6
     67        # reality = 2 + 6
     68        reality = add_x(6)
     69        # my_expectation = 6
     70        # my_expectation = 7
     71        # my_expectation = 8
     72        my_expectation = 9
     73        self.assertEqual(reality, my_expectation)
     74
     75        # reality = 1 + 7
     76        # reality = 2 + 7
     77        reality = add_x(7)
     78        # my_expectation = 7
     79        # my_expectation = 8
     80        # my_expectation = 9
     81        my_expectation = 10
     82        self.assertEqual(reality, my_expectation)
     83
     84        # reality = 1 + 8
     85        # reality = 2 + 8
     86        reality = add_x(8)
     87        # my_expectation = 8
     88        # my_expectation = 9
     89        # my_expectation = 10
     90        my_expectation = 11
     91        self.assertEqual(reality, my_expectation)
     92
     93        # reality = 1 + 9
     94        # reality = 2 + 9
     95        reality = add_x(9)
     96        # my_expectation = 9
     97        # my_expectation = 10
     98        # my_expectation = 11
     99        my_expectation = 12
    100        self.assertEqual(reality, my_expectation)
    101
    102
    103# Exceptions seen
    

    the test passes.

  • 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?


test_making_a_function_w_pass

I can make a function with the pass keyword


RED: make it fail


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

  • I add a new test to test_functions.py

     93        # reality = 1 + 9
     94        # reality = 2 + 9
     95        reality = add_x(9)
     96        # my_expectation = 9
     97        # my_expectation = 10
     98        # my_expectation = 11
     99        my_expectation = 12
    100        self.assertEqual(reality, my_expectation)
    101
    102    def test_making_a_function_w_pass(self):
    103        self.assertIs(src.functions.w_pass(), None)
    104
    105
    106# Exceptions seen
    

    the terminal is my friend, and shows NameError

    NameError: name 'src' is not defined
    

    because Python does not know what I mean by src since I do not have a definition for it in test_functions.py


GREEN: make it pass


I can make a function with pass.


test_making_a_function_w_return

I can also make a function with a return statement


RED: make it fail


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

  • I add a new failing test to test_functions.py

    103    def test_making_a_function_w_pass(self):
    104        self.assertIs(src.functions.w_pass(), None)
    105
    106    def test_making_a_function_w_return(self):
    107        self.assertIs(src.functions.w_return(), None)
    108
    109
    110# Exceptions seen
    

    the terminal is my friend, and shows AttributeError

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

    because functions.py in the src folder does not have anything with the name w_return in it


GREEN: make it pass


I add the new function with the pass keyword to functions.py

1def w_pass():
2    pass
3
4
5def w_return():
6    pass

the test passes.


REFACTOR: make it better


I have two functions with different statements, and the tests show that they both return None

src.functions.w_pass()
pass
src.functions.w_return()
return

their contents are different, their results are the same because “all functions return None by default, as if they have an invisible line that says return None”, which leads me to the next test.

I can make a function with a return statement


test_making_a_function_w_return_none

I can make a function with a return statement that says exactly what the function returns


RED: make it fail


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

  • I add another failing test to test_functions.py

    106    def test_making_a_function_w_return(self):
    107        self.assertIs(src.functions.w_return(), None)
    108
    109    def test_making_a_function_w_return_none(self):
    110        self.assertIs(
    111            src.functions.w_return_none(), None
    112        )
    113
    114
    115# Exceptions seen
    

    the terminal is my friend, and shows AttributeError

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

    because w_return_none is not defined in functions.py in the src folder


GREEN: make it pass


I add a function definition to functions.py

 5def w_return():
 6    return
 7
 8
 9def w_return_none():
10    return

the test passes.


REFACTOR: make it better


  • I add None to the return statement

     9def w_return_none():
    10    # return
    11    return None
    

    the test is still green.

  • I change None to 'something'

     9def w_return_none():
    10    # return
    11    # return None
    12    return 'something'
    

    the terminal is my friend, and shows AssertionError

    AssertionError: 'something' is not None
    

    because the assertion expects None and the function returns 'something'

  • I undo the change

     9def w_return_none():
    10    # return
    11    return None
    12    # return 'something'
    

    the test is green again.

  • I remove the commented lines

     9def w_return_none():
    10    return None
    
  • I add a git commit message in the other terminal

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

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

I have three functions with different statements, and the tests show that they all return None

src.functions.w_pass()
pass
src.functions.w_return()
return
src.functions.w_return_none()
return None

their contents are different, their results are the same because “all functions return None by default, as if they have an invisible line that says …

I like to write my functions with return None, so that anyone can see what the function returns without having to think about it.

I can make a function with return None.


test_what_happens_after_functions_return

The return statement is the last thing to run in a function


RED: make it fail


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

  • I add a test to test_functions.py

    109    def test_making_a_function_w_return_none(self):
    110        self.assertIs(
    111            src.functions.w_return_none(), None
    112        )
    113
    114    def test_what_happens_after_functions_return(self):
    115        self.assertIs(
    116            src.functions.return_is_last(), None
    117        )
    118
    119
    120# Exceptions seen
    

    the terminal is my friend, and shows AttributeError

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

    because functions.py does not have a definition for it, yet


GREEN: make it pass


I add a function to functions.py

 9def w_return_none():
10    return None
11
12
13def return_is_last():
14    return None

the test passes.


REFACTOR: make it better



test_constant_function

constant functions always return the same thing when they are called


RED: make it fail


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

  • I add a test to test_functions.py

    114    def test_what_happens_after_a_function_returns(self):
    115        self.assertIs(
    116            src.functions.return_is_last(), None
    117        )
    118
    119    def test_constant_function(self):
    120        reality = src.functions.constant()
    121        my_expectation = 'the same thing'
    122        self.assertEqual(reality, my_expectation)
    123
    124
    125# Exceptions seen
    

    the terminal is my friend, and shows AttributeError

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

    because I have not added a definition for constant in functions.py in the src folder


GREEN: make it pass


  • I add the function to functions.py

    13def return_is_last():
    14    return None
    15    return 'will never run'
    16
    17
    18def constant():
    19    return None
    

    the terminal is my friend, and shows AssertionError

    AssertionError: None != 'the same thing'
    

    because the assertion expects 'the same thing' and the function returns None

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

    18def constant():
    19    # return None
    20    return 'the same thing'
    

    the test passes.

  • I remove the commented line

    18def constant():
    19    return 'the same thing'
    
  • I add a git commit message in the other terminal

    git commit --all --message 'add test_constant_function'
    

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

A constant function always returns the same thing when called, I can use them in place of variables, though the number of cases where they are faster than variables is pretty small. It is something like if the function is called less than 10 times (who’s counting?)

a constant function always returns the same thing.


test_identity_function

The identity function returns its input as output, it is also in the Truth Table chapter in test_logical_identity


RED: make it fail


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

  • I add a failing test to test_functions.py

    119    def test_constant_function(self):
    120        reality = src.functions.constant()
    121        my_expectation = 'the same thing'
    122        self.assertEqual(reality, my_expectation)
    123
    124    def test_identity_function(self):
    125        reality = src.functions.identity(None)
    126        my_expectation = None
    127        self.assertEqual(reality, my_expectation)
    128
    129
    130# Exceptions seen
    

    the terminal is my friend, and shows AttributeError

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

    is it because functions.py has no identity?


GREEN: make it pass


  • I add a function for identity to functions.py

    18def constant():
    19    return 'the same thing'
    20
    21
    22def identity():
    23    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: identity() takes 0 positional arguments
               but 1 was given
    

    because the definition for identity does not allow calling it with inputs and the test sends None as input

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

    130# Exceptions seen
    131# AssertionError
    132# NameError
    133# AttributeError
    134# TypeError
    
  • I add a name in parentheses for the identity function to take input, in functions.py

    22# def identity():
    23def identity(the_input):
    24    return None
    

    the test passes. I am genius.


REFACTOR: make it better


The requirement 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? Time to test it

  • I add a new assertion to test_identity_function in test_functions.py

    124    def test_identity_function(self):
    125        reality = src.functions.identity(None)
    126        my_expectation = None
    127        self.assertEqual(reality, my_expectation)
    128
    129        reality = src.functions.identity(object)
    130        my_expectation = object
    131        self.assertEqual(reality, my_expectation)
    132
    133
    134# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: None != <class 'object'>
    

    because the function always returns None not <class 'object'> or what it receives as input. I am not all the way genius, yet

  • I make the identity function in functions.py return what it gets

    22# def identity():
    23def identity(the_input):
    24    # return None
    25    return the_input
    

    the test passes.

  • I remove the commented lines

    22def identity(the_input):
    23    return the_input
    
  • I add a git commit message in the other terminal

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

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

I sometimes use the Identity Function when I am testing, to see if my test is connected to what I am testing. If I can send something (input) and get it back, I can start making changes to see how it affects the output.

The Identity Function returns its input as output.


test_w_positional_arguments

So far, the functions take no input or one input, the 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 failing test to test_functions.py

    129        reality = src.functions.identity(object)
    130        my_expectation = object
    131        self.assertEqual(reality, my_expectation)
    132
    133    def test_w_positional_arguments(self):
    134        reality = src.functions.w_positional_arguments(
    135            'first', 'last',
    136        )
    137        my_expectation = ('first', 'last')
    138        self.assertEqual(reality, my_expectation)
    139
    140
    141# Exceptions seen
    

    the terminal is my friend, and shows AttributeError

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

    because …


GREEN: make it pass


  • I add the function to functions.py

    22def identity(the_input):
    23    return the_input
    24
    25
    26def w_positional_arguments():
    27    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: w_positional_arguments() takes
               0 positional arguments but 2 were given
    

    because the definition for w_positional_arguments does not allow inputs and the test sends two in the call ('first' and 'last')

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

    26# def w_positional_arguments():
    27def w_positional_arguments(first_input):
    28    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: w_positional_arguments() takes
               1 positional argument but 2 were given
    

    because the definition for w_positional_arguments now allows only one input and the test sends two in the call ('first' and 'last')

  • I make w_positional_arguments take another input by adding another name in parentheses

    26# def w_positional_arguments():
    27# def w_positional_arguments(first_input):
    28def w_positional_arguments(first_input, last_input):
    29    return None
    

    the terminal is my friend, and shows AssertionError

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

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

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

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

    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, which is equal to my_expectation


REFACTOR: make it better


  • I remove the commented lines

    22def identity(the_input):
    23    return the_input
    24
    25
    26def w_positional_arguments(first_input, last_input):
    27    return first_input, last_input
    
  • The problem with giving arguments this way is that they always have to be in the order the function expects or I get something different. I add an assertion to show this in test_w_positional_arguments in test_functions.py

    133    def test_w_positional_arguments(self):
    134        reality = src.functions.w_positional_arguments(
    135            'first', 'last',
    136        )
    137        my_expectation = ('first', 'last')
    138        self.assertEqual(reality, my_expectation)
    139
    140        reality = src.functions.w_positional_arguments(
    141            'last', 'first',
    142        )
    143        my_expectation = ('first', 'last')
    144        self.assertEqual(reality, my_expectation)
    145
    146
    147# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

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

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

  • I change my_expectation to match reality

    140        reality = src.functions.w_positional_arguments(
    141            'last', 'first',
    142        )
    143        # my_expectation = ('first', 'last')
    144        my_expectation = ('last', 'first')
    145        self.assertEqual(reality, my_expectation)
    146
    147
    148# Exceptions seen
    

    the test passes.

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

    133    def test_w_positional_arguments(self):
    134        first, last = 'first', 'last'
    135
    136        reality = src.functions.w_positional_arguments(
    137            'first', 'last',
    138        )
    139        my_expectation = ('first', 'last')
    140        self.assertEqual(reality, my_expectation)
    
  • I use the new variables to remove repetition of 'first' and 'last'

    133    def test_w_positional_arguments(self):
    134        first, last = 'first', 'last'
    135
    136        reality = src.functions.w_positional_arguments(
    137            # 'first', 'last',
    138            first, last,
    139        )
    140        # my_expectation = ('first', 'last')
    141        my_expectation = (first, last)
    142        self.assertEqual(reality, my_expectation)
    143
    144        reality = src.functions.w_positional_arguments(
    145            # 'last', 'first',
    146            last, first,
    147        )
    148        # my_expectation = ('first', 'last')
    149        # my_expectation = ('last', 'first')
    150        my_expectation = (last, first)
    151        self.assertEqual(reality, my_expectation)
    152
    153
    154# Exceptions seen
    

    the test is still green.

  • I add another assertion

    144        reality = src.functions.w_positional_arguments(
    145            # 'last', 'first',
    146            last, first,
    147        )
    148        # my_expectation = ('first', 'last')
    149        # my_expectation = ('last', 'first')
    150        my_expectation = (last, first)
    151        self.assertEqual(reality, my_expectation)
    152
    153        first_number, second_number = 0, 1
    154        reality = src.functions.w_positional_arguments(
    155            first_number, second_number,
    156        )
    157        my_expectation = (second_number, first_number)
    158        self.assertEqual(reality, my_expectation)
    159
    160
    161# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: Tuples differ: (0, 1) != (1, 0)
    

    because the function always returns first_input, last_input and the call in this test sends 0 as first_input and 1 as last_input

  • I change my_expectation to match reality

    153        first_number, second_number = 0, 1
    154        reality = src.functions.w_positional_arguments(
    155            first_number, second_number,
    156        )
    157        # my_expectation = (second_number, first_number)
    158        my_expectation = (first_number, second_number)
    159        self.assertEqual(reality, my_expectation)
    160
    161
    162# Exceptions seen
    

    the test passes.

  • I add one more assertion

    153        first_number, second_number = 0, 1
    154        reality = src.functions.w_positional_arguments(
    155            first_number, second_number,
    156        )
    157        # my_expectation = (second_number, first_number)
    158        my_expectation = (first_number, second_number)
    159        self.assertEqual(reality, my_expectation)
    160
    161        a_tuple = (1, 2, 3, 'n')
    162        a_list = [1, 2, 3, 'n']
    163        reality = src.functions.w_positional_arguments(
    164            a_list, a_tuple,
    165        )
    166        my_expectation = (a_tuple, a_list)
    167        self.assertEqual(reality, my_expectation)
    168
    169
    170# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

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

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

  • I change reality to match my_expectation

    161        a_tuple = (1, 2, 3, 'n')
    162        a_list = [1, 2, 3, 'n']
    163        reality = src.functions.w_positional_arguments(
    164            # a_list, a_tuple,
    165            a_tuple, a_list,
    166        )
    167        my_expectation = (a_tuple, a_list)
    168        self.assertEqual(reality, my_expectation)
    169
    170
    171# Exceptions seen
    

    the test passes.

  • I remove the commented lines

    133    def test_w_positional_arguments(self):
    134        first, last = 'first', 'last'
    135
    136        reality = src.functions.w_positional_arguments(
    137            first, last,
    138        )
    139        my_expectation = (first, last)
    140        self.assertEqual(reality, my_expectation)
    141
    142        reality = src.functions.w_positional_arguments(
    143            last, first,
    144        )
    145        my_expectation = (last, first)
    146        self.assertEqual(reality, my_expectation)
    147
    148        first_number, second_number = 0, 1
    149        reality = src.functions.w_positional_arguments(
    150            first_number, second_number,
    151        )
    152        my_expectation = (first_number, second_number)
    153        self.assertEqual(reality, my_expectation)
    154
    155        a_tuple = (1, 2, 3, 'n')
    156        a_list = [1, 2, 3, 'n']
    157        reality = src.functions.w_positional_arguments(
    158            a_tuple, a_list,
    159        )
    160        my_expectation = (a_tuple, a_list)
    161        self.assertEqual(reality, my_expectation)
    162
    163
    164# Exceptions seen
    
  • I add a git commit message in the other terminal

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

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

I can call functions with positional arguments


test_w_keyword_arguments

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

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


RED: make it fail


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

  • I add a new test to test_functions.py

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

    the terminal is my friend, and shows AttributeError

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

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


GREEN: make it pass


  • I add a function definition to functions.py

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

    the terminal is my friend, and shows TypeError

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

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

  • I add the name of the unexpected argument in parentheses

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

    the terminal is my friend, and shows TypeError

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

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

  • I add a name for the second argument in parentheses

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

    the terminal is my friend, and shows TypeError

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

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

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

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

    the terminal is my friend, and shows AssertionError

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

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

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

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

    the test passes.


REFACTOR: make it better


  • I remove the commented lines

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

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

    the terminal is my friend, and shows AssertionError

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

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

    Compare this call that uses positional arguments and its result

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

    with this call that uses keyword arguments and its result

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

    in both cases the function returns first_input, last_input

  • I change my_expectation to match reality

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

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

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

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

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

    the test is still green.

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

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

    the terminal is my friend, and shows AssertionError

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

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

  • I change my_expectation to match reality

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

    the test passes.

  • I add another assertion

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

    the terminal is my friend, and shows AssertionError

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

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

    the test passes.

  • I add an assertion

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

    the terminal is my friend, and shows AssertionError

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

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

    the test passes.

  • w_keyword_arguments and w_positional_arguments are the same functions, they always

    return first_input, last_input
    

    Their names are different

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

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

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

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

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

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

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

    the terminal is my friend, and shows AssertionError

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

    because these two calls are the same

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

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

    the test passes.

  • I remove the commented lines

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

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

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

I can call a function with keyword arguments


test_w_args_and_kwargs

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


RED: make it fail


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

  • I add a failing test to test_functions.py

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

    the terminal is my friend, and shows SyntaxError

    SyntaxError: positional argument follows keyword argument
    

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


GREEN: make it pass


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

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

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

    the terminal is my friend, and shows AttributeError

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

    because functions.py does not have anything named w_args_and_kwargs

  • I add a function to functions.py

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

    the terminal is my friend, and shows TypeError

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

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

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

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

    the terminal is my friend, and shows TypeError

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

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

  • I add another name in parentheses to make it clearer

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

    the terminal is my friend, and shows TypeError

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

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

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

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

    the terminal is my friend, and shows SyntaxError

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

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

    the terminal is my friend, and shows AssertionError

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

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

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

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

    the test passes.

  • I remove the commented lines

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

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

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

    the test is still green.

  • I remove the commented lines

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

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

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

I can call a function with positional and keyword arguments


test_w_optional_arguments

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


RED: make it fail


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

  • I add a failing test to test_functions.py

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

    the terminal is my friend, and shows AttributeError

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

    because functions.py does not have a definition for w_optional_arguments


GREEN: make it pass


I add a function named w_optional_arguments to functions.py

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

the test passes.


REFACTOR: make it better


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

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

    the terminal is my friend, and shows TypeError

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

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

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

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

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

    src.functions.w_optional_arguments('jane')
    

    is the same as

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

    is the same as

    return 'jane', 'doe'
    

    because w_optional_arguments will always

    return first_input, last_input
    

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

  • I remove the commented lines

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

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

    the terminal is my friend, and shows AssertionError

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

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

    the test passes.

  • I add another assertion

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

    the terminal is my friend, and shows AssertionError

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

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

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

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

    is the same as

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

    is the same as

    return 'john', 'doe'
    

    because w_optional_arguments will always

    return first_input, last_input
    

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

  • I add one more assertion

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

    the terminal is my friend, and shows AssertionError

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

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

    the test passes.

  • I remove the commented lines

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

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

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

Note

these four functions

  • w_keyword_arguments

  • w_positional_arguments

  • w_args_and_kwargs

  • w_optional_arguments

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

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

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

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

Tip

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


test_w_unknown_arguments

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


RED: make it fail


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

  • I add a new test to test_functions.py

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

    the terminal is my friend, and shows AttributeError

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

    because functions.py does not have w_unknown_arguments


GREEN: make it pass


  • I add the function to functions.py

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

    the terminal is my friend, and shows TypeError

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

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

    the terminal is my friend, and shows TypeError

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

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


double starred expressions


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

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

    the terminal is my friend, and shows TypeError

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

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

    the terminal is my friend, and shows SyntaxError

    SyntaxError: arguments cannot follow var-keyword argument
    

    a reminder that I cannot put positional arguments after keyword arguments

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

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

    the terminal is my friend, and shows TypeError

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

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

    the test passes.


REFACTOR: make it better


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

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

    the terminal is my friend, and shows AssertionError

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

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

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

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

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

    the terminal is my friend, and shows TypeError

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

    the function definition only allows two positional arguments

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

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

    the terminal is my friend, and shows TypeError

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

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


starred expressions


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

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

    the test passes.

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

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

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

    the terminal is my friend, and shows

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

    I get a tuple that has a tuple and a dictionary

  • I change my_expectation to match reality in the first assertion

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

    the terminal is my friend, and shows AssertionError

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

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

    the terminal is my friend, and shows AssertionError

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

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

    the test passes.

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

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

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

    the terminal is my friend, and shows AssertionError

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

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

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

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

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

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

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

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

    the terminal is my friend, and shows AssertionError

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

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

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

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

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

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

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

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

    the terminal is my friend, and shows AssertionError

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

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

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

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

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


how Python reads positional arguments

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

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

the terminal is my friend, and shows AssertionError

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

I change my_expectation to match reality

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

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


how Python reads keyword arguments

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

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

the terminal is my friend, and shows

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

I change my_expectation to match reality

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

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


how Python reads positional and keyword arguments

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

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

    the terminal is my friend, and shows AssertionError

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

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

    the test passes.

  • I remove the commented lines

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

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

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

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

Note

these statements are the same

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

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

return positional_arguments, keyword_arguments

in this case

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

are positional arguments which are taken as a tuple and

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

are keyword arguments which are taken as a dictionary.

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

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


close the project

  • I close test_functions.py and functions.py

  • I click in the terminal where the tests are running

  • I use q on the keyboard to leave the tests. The terminal goes back to the command line.

  • I change directory to the parent of functions

    cd ..
    

    the terminal shows

    .../pumping_python
    

    I am back in the pumping_python directory


review

I ran tests to show that I can make functions with

as a reminder

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

Optional continuations in the same functions project (after you know classes and AssertionError 2):

  • Functions 2: use class attributes - remove repetition of literal values in test_w_positional_arguments, test_w_keyword_arguments and test_w_args_and_kwargs by using class attributes on TestFunctions (no setUp needed for these constants)

  • functions 3 - use for loops (and list comprehensions) to DRY the repetitive test_why_use_a_function

Would you like to test how to pass values from tests to functions with assert methods?


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.