Glosar

Selectați unul dintre cuvintele cheie din stânga ...

Programming in PythonFunctions

Timp de citit: ~25 min

Functions can be used to organize code and achieve separation of concerns: once a function is written, it may be relied upon to perform its designated task without the programmer having to think about how it accomplishes that task. This conceptual aid is crucial for writing maintainable code to solve large, complex problems.

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 , while "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 syntax uses the keyword 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 x\mapsto -x.

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? Add test cases for this, and fix the function so that they pass.

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()
Bruno
Bruno Bruno