Programming in PythonFunctions
A good rule of thumb is that a function should be sufficiently general to be re-usable without duplicating internal logic, but specific enough that you can actually implement it.
Exercise
How could the design of the following code be improved?
def remove_one_leading_space(S):
if S[0] == " ":
return S[1:]
else:
return S
def remove_two_leading_spaces(S):
if S[0:2] == " ":
return S[2:]
else:
return S
def remove_three_leading_spaces(S):
if S[0:3] == " ":
return S[3:]
else:
return S
Solution. We should have a single function to remove whatever number of leading spaces the string happens to have. The design above has the problem that we have to figure out how many leading spaces there are before we can call the appropriate function, which means that most of the work that should be performed by the function will have to be performed when the function is called. Thus separation of concerns is not achieved.
Arguments
The objects supplied to a function when it's called are referred to as the function's arguments. The variables which represent the arguments in the function definition are called parameters. The indented block of code that runs when the function is called is the body of the function.
Exercise
In the following block of code, s
is "hello"
is
def duplicate(s):
return s + s
duplicate("hello")
We can give parameters default values and supply arguments for those parameters optionally when calling the function.
def line(m, x, b=0): return m * x + b line(2,3) # returns 6 line(5,4,b=2) # returns 22
The arguments 2, 3, 4 and 5 in this example are called positional arguments, and b=2
is a keyword argument.
If the function body begins with a string literal, that string will be interpreted as documentation for the function. This docstring helps you and other users of your functions quickly ascertain how they are meant to be used. A function's docstring can accessed in a Python session using the function help
. For example, help(print)
pulls up the docstring for the built-in print
function.
Anonymous functions
A function may be defined without assigning a name to it. Such a function is said to be anonymous. Python's anonymous function lambda
. A common situation where anonymous functions can be useful is when supplying one function to another as an argument. For example:
def apply_three_times(f, x): return f(f(f(x))) apply_three_times(lambda x: x*x, 2)
A multi-argument function works similarly, with the parameters separated by commas: the addition operator can be written as lambda x,y: x + y
.
Exercise
Write a function that takes two arguments a
and b
and a function f
and returns a
if f(a) < f(b)
and b
otherwise. Then use anonymous function syntax to call your function with two numbers and the negation function .
Solution. Here's an example solution:
def which_smaller(a, b, f):
if f(a) < f(b):
return a
else:
return b
which_smaller(4, 6, lambda x: -x)
Scope
The scope of a variable is the region in the program where it is accessible. For example, if you define x
to be 47
on line 413 of your file and get an error because you tried to use x
on line 35, the problem is that the variable wasn't in scope yet.
A variable defined in the main body of a file has global scope, meaning that it is visible throughout the program from its point of definition.
A variable defined in the body of a function is in that function's local scope. For example:
def f(x): y = 2 return x + y y
Exercise
Try nesting one function definition inside another. Are variables in the enclosing function body available in the inner function. What about vice versa?
def f(): def g(): j = 2 return i print(j) i = 1 return g() f()
Solution. The variable defined in the inner function is not in scope in the body of the outer function, but the variable defined in the body of the outer function is in scope in the body of the inner function.
Testing
It's highly recommended to write tests to accompany your functions, so you can confirm that each function behaves as expected. This is especially important as your codebase grows, because changes in one function can lead to problems in other functions that use it. Having a way to test functions throughout your codebase helps you discover these breakages quickly, before they cause harm.
One common way to do this (which you have already seen several times in this course) is to write functions whose names begin with test_
and which contain assert
statements. An assert
statement throws an error if the following expression evaluates to False
. You can run the test functions directly, or you can use a tool like pytest to find and run all of the test functions in your codebase.
def space_concat(s,t): """ Concatenate strings s and t, ensuring a space between them if s ends with a non-space character and t begins with a non-space character """ if s[-1] == " " or t[0] == " ": return s + t else: return s + " " + t def test_space_concat(): assert space_concat("foo", "bar") == "foo bar" assert space_concat("foo ", "bar") == "foo bar" test_space_concat() space_concat("foo", "bar")
Exercise
The test cases above don't cover the degenerate situation where one of the strings is empty. Does the function return correct values for these degenerate cases?
Solution. We check the empty string conditions prior to checking the last/first characters. This solves the problem because or
is short-circuiting: if the first bool is True
in an or
operation, the second is never evaluated.
def space_concat(s,t): """ Concatenate strings s and t, ensuring a space between them if s ends with a non-space character and t begins with a non-space character. """ if s == "" or t == "" or s[-1] == " " or t[0] == " ": return s + t else: return s + " " + t def test_space_concat(): assert space_concat("foo", "bar") == "foo bar" assert space_concat("foo ", "bar") == "foo bar" assert space_concat("foo", "") == "foo" assert space_concat("", "bar") == "bar"
Exercises
Exercise
Write a function which accepts two strings as input and returns the concatenation of those two strings in alphabetical order.
Hint: Make a guess about which operator can be used to compare strings alphabetically.
def alphabetical_concat(s,t): pass # add code here def test_concat(): assert alphabetical_concat("alphabet", "soup") == "alphabetsoup" assert alphabetical_concat("socks", "red") == "redsocks" return "Tests passed!" test_concat()
Solution.
def alphabetical_concat(s,t): if s < t: return s + t else: return t + s def test_concat(): alphabetical_concat("alphabet", "soup") == "alphabetsoup" alphabetical_concat("food", "brain") == "brainfood" return "Tests passed!" test_concat()