Part 4

More strings and lists

You are already familiar with the [] syntax for accessing a part of a string:

my_string = "exemplary"
print(my_string[3:7])
Sample output

mpla

The same syntax works with lists. Lists can be sliced just like strings:

my_list = [3,4,2,4,6,1,2,4,2]
print(my_list[3:7])
Sample output

[4, 6, 1, 2]

More slices

In fact, the [] syntax works very similarly to the range function, which means we can also give it a step:

my_string = "exemplary"
print(my_string[0:7:2])
my_list = [1,2,3,4,5,6,7,8]
print(my_list[6:2:-1])
Sample output

eepa [7, 6, 5, 4]

If we omit either of the indexes, the operator defaults to including everything. Among other things, this allows us to write a very short program to reverse a string:

my_string = input("Please type in a string: ")
print(my_string[::-1])
Sample output

Please type in a string: exemplary yralpmexe

Warning: using global variables within functions

We know it is possible to assign new variables within function definitions, but the function can also see variables assigned outside it, in the main function. Such variables are called global variables.

Using global variables from within functions is usually a bad idea. Among other issues, doing so may cause bugs which are difficult to trace.

Below is an example of a function which uses a global variable "by mistake":

def print_reversed(names: list):
    # using the global variable instead of the parameter by accident
    i = len(name_list) - 1
    while i >= 0:
        print(name_list[i])
        i -= 1

# here the global variable is assigned
name_list = ["Steve", "Jean", "Katherine", "Paul"]
print_reversed(name_list)
print()
print_reversed(["Huey", "Dewey", "Louie"])
Sample output

Paul Katherine Jean Steve

Paul Katherine Jean Steve

Even though both function calls have the right kind of argument, the function always prints out what is stored in the global variable name_list.

To make matters even more muddled, remember that all code for testing your functions should be placed within the if __name__ == "__main__": block for the automatic tests. The previous example should be modified:

def print_reversed(names: list):
    # using the global variable instead of the parameter by accident
    i = len(name_list) - 1
    while i>=0:
        print(name_list[i])
        i -= 1

# All the code for testing the function should be within this block
if __name__ == "__main__":
    # here the global variable is assigned
    name_list = ["Steve", "Jean", "Katherine", "Paul"]
    print_reversed(name_list)
    print()
    print_reversed(["Huey", "Dewey", "Louie"])

Notice the global variable is assigned within the if block now.

The automatic tests in the TMC system are executed without running any of the code in the if block. So, in this latter example the function wouldn't even theoretically work, since it refers to the variable name_list, which doesn't exist at all when the tests are executed.

Loading

Strings are immutable

Strings and lists have a lot in common, especially in the way they behave with different operators. The main difference is that strings are immutable. That means they cannot be changed.

my_string = "exemplary"
my_string[0] = "a"

Strings cannot be changed, so the execution of this program causes an error:

Sample output

TypeError: 'str' object does not support item assignment

A similar error follows if you try to sort a string with the sort method.

Strings themselves are immutable, but the variables holding them are not. A string can be replaced by another string.

The following two examples are thus fundamentally different:

my_list = [1,2,3]
my_list[0] = 10
4 6 1
my_string = "Hey"
my_string = my_string + "!"
4 6 2

The first example changes the contents of the referenced list. The second example replaces the reference to the original string with a reference to another string. The original string is still somewhere in computer memory, but there is no reference to it, and it cannot be used in the program any longer.

We will return to this subject in the next part, where references to lists are explored in more detail.

More methods for lists and strings

The method count counts the number of times the specified item or substring occurs in the target. The method works similarly with both strings and lists:

my_string = "How much wood would a woodchuck chuck if a woodchuck could chuck wood"
print(my_string.count("ch"))

my_list = [1,2,3,1,4,5,1,6]
print(my_list.count(1))
Sample output

5 3

The method will not count overlapping occurrences. For example, in the string aaaa the method counts only two occurrences of the substring aa, even though there would actually be three if overlapping occurrences were allowed.

The method replace creates a new string, where a specified substring is replaced with another string:

my_string = "Hi there"
new_string = my_string.replace("Hi", "Hey")
print(new_string)
Sample output

Hey there

The method will replace all occurrences of the substring:

sentence = "sheila sells seashells on the seashore"
print(sentence.replace("she", "SHE"))
Sample output

SHEila sells seaSHElls on the seashore

When using the replace method, a typical mistake is forgetting that strings are immutable:

my_string = "Python is fun"

# Replaces the substring but doesn't store the result...
my_string.replace("Python", "Java")
print(my_string)
Sample output

Python is fun

If the old string is no longer needed, the new string can be assigned to the same variable:

my_string = "Python is fun"

# Replaces the substring and stores the result in the same variable
my_string = my_string.replace("Python", "Java")
print(my_string)
Sample output

Java is fun

Loading
Loading
Loading
Loading

Developing a larger programming project

This fourth part culminates in a slightly larger programming project, where you get to apply many of the techniques learnt so far.

Rule No. 1 in tackling any programming project is not trying to solve everything at once. The program should be built out of smaller sections, such as helper functions. You should verify the operation of each part before moving on to the next. If you try to handle too much at once, most likely only chaos ensues.

To do this you will need a way of testing your functions outside the main function. You can achieve this by defining a main function explicitly, and calling this function from outside any other function in the program. A single function call is then easy to comment out for testing. The first steps in building the following programming project could look like this:

def main():
    points = []
    # your program code goes here

main()

Now the helper functions can be tested without running the main function:

# helper function for determining the grade based on the amount of points
def grade(points):
    # more code

def main():
    all_points = []
    # your program code goes here

# comment out the main function
#main()

# test the helper function
student_points = 35
result = grade(student_points)
print(result)

Passing data from one function to another

When a program contains multiple functions, the question arises: how do you pass data from one function to another?

The following example asks the user for some integer values. The program then prints out these values and performs an "analysis" on them. The program is divided into three separate functions:

def input_from_user(how_many: int):
    print(f"Please type in {how_many} numbers:")
    numbers = []

    for i in range(how_many):
        number = int(input(f"Number {i+1}: "))
        numbers.append(number)

    return numbers

def print_result(numbers: list):
    print("The numbers are: ")
    for number in numbers:
        print(number)

def analyze(numbers: list):
    mean = sum(numbers) / len(numbers)
    return f"There are altogether {len(numbers)} numbers, the mean is {mean}, the smallest is {min(numbers)} and the greatest is {max(numbers)}"

# the "main function" using these functions
inputs = input_from_user(5)
print_result(inputs)
analysis_result = analyze(inputs)
print(analysis_result)

When the program is executed, it could go like this:

Sample output

Please type in 5 numbers: Number 1: 10 Number 2: 34 Number 3: -32 Number 4: 99 Number 5: -53 The numbers are: 10 34 -32 99 -53 There are altogether 5 numbers, the mean is 11.6, the smallest is -53 and the greatest is 99

The idea here is that the main function "saves" all data processed by the program. In this case all that is needed is the input from the user in the variable inputs.

If the input is needed in a function, it is passed as an argument. This happens with the functions print_result and analyze. If the function produces data that is needed elsewhere in the program, the function returns it with the return command, and it is stored in a variable in the main function. This happens with the functions input_from_user and analyze.

You could use the global variable inputs from the main function directly in the helper functions. We have already covered why that is a bad idea, but here is another explanation. If functions are able to change a global variable, unexpected things may start happening in the program, especially when the number of functions grows large.

Passing data into and out of functions is best handled by arguments and return values.

You could also separate the implicit main function in the example above into its own function. Then the variable inputs would no longer be a global variable, but instead a local variable within the main function:

# your main function goes here
def main():
    inputs = input_from_user(5)
    print_result(inputs)
    analysis_result = analyze(inputs)

    print(analysis_result)

# run the main function
main()
Loading

Please respond to a quick questionnaire on this week's materials.

Loading...
:
Loading...

Log in to view the quiz

You have reached the end of this section!

You can check your current points from the blue blob in the bottom-right corner of the page.