Part 4

Еще о строках и списках

Вы уже знакомы с синтаксисом [] для доступа к части строки:

my_string = "exemplary"
print(my_string[3:7])
Пример вывода

mpla

Тот же синтаксис работает со списками. Списки можно нарезать срезами так же, как строки:

my_list = [3,4,2,4,6,1,2,4,2]
print(my_list[3:7])
Пример вывода

[4, 6, 1, 2]

Больше срезов

На самом деле синтаксис [] работает очень похоже на функцию range, а значит, мы также можем задать ему шаг:

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])
Пример вывода

eepa [7, 6, 5, 4]

Если мы пропускаем любой из индексов, оператор по умолчанию включает все. Среди прочего, это позволяет написать очень короткую программу для разворота строки:

my_string = input("Please type in a string: ")
print(my_string[::-1])
Пример вывода

Please type in a string: exemplary yralpmexe

Предупреждение: использование глобальных переменных внутри функций

Мы знаем, что внутри определений функций можно присваивать новые переменные, но функция также может видеть переменные, присвоенные вне нее, в главной функции. Такие переменные называются глобальными переменными.

Использовать глобальные переменные изнутри функций обычно плохая идея. Среди прочих проблем это может вызвать ошибки, которые трудно отследить.

Ниже пример функции, которая использует глобальную переменную "по ошибке":

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"])
Пример вывода

Paul Katherine Jean Steve

Paul Katherine Jean Steve

Несмотря на то что оба вызова функции имеют аргумент правильного типа, функция всегда выводит то, что сохранено в глобальной переменной name_list.

Чтобы еще больше все запутать, помните, что весь код для тестирования ваших функций должен быть помещен внутрь блока if __name__ == "__main__": для автоматических тестов. Предыдущий пример следует изменить:

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"])

Обратите внимание, что теперь глобальная переменная присваивается внутри блока if.

Автоматические тесты в системе TMC выполняются без запуска какого-либо кода из блока if. Поэтому в этом последнем примере функция даже теоретически не сработала бы, поскольку она обращается к переменной name_list, которой вообще не существует при выполнении тестов.

Loading

Строки неизменяемы

У строк и списков много общего, особенно в том, как они ведут себя с разными операторами. Главное отличие в том, что строки неизменяемы. Это значит, что их нельзя изменить.

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

Строки нельзя менять, поэтому выполнение этой программы вызывает ошибку:

Пример вывода

TypeError: 'str' object does not support item assignment

Похожая ошибка возникнет, если попытаться отсортировать строку методом sort.

Сами строки неизменяемы, но переменные, которые их хранят, нет. Строку можно заменить другой строкой.

Поэтому следующие два примера фундаментально отличаются:

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

Первый пример изменяет содержимое списка, на который есть ссылка. Второй пример заменяет ссылку на исходную строку ссылкой на другую строку. Исходная строка все еще где-то в памяти компьютера, но ссылки на нее нет, и ее больше нельзя использовать в программе.

Мы вернемся к этой теме в следующей части, где ссылки на списки будут рассмотрены подробнее.

Больше методов для списков и строк

Метод count подсчитывает, сколько раз указанный элемент или подстрока встречается в цели. Метод работает похожим образом и со строками, и со списками:

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))
Пример вывода

5 3

Метод не считает перекрывающиеся появления. Например, в строке aaaa метод считает только два появления подстроки aa, хотя на самом деле их было бы три, если бы перекрывающиеся появления были разрешены.

Метод replace создает новую строку, где указанная подстрока заменяется другой строкой:

my_string = "Hi there"
new_string = my_string.replace("Hi", "Hey")
print(new_string)
Пример вывода

Hey there

Метод заменит все появления подстроки:

sentence = "sheila sells seashells on the seashore"
print(sentence.replace("she", "SHE"))
Пример вывода

SHEila sells seaSHElls on the seashore

При использовании метода replace типичная ошибка - забыть, что строки неизменяемы:

my_string = "Python is fun"

# Replaces the substring but doesn't store the result...
my_string.replace("Python", "Java")
print(my_string)
Пример вывода

Python is fun

Если старая строка больше не нужна, новую строку можно присвоить той же переменной:

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)
Пример вывода

Java is fun

Loading
Loading
Loading
Loading

Разработка более крупного программного проекта

Эта четвертая часть завершается немного более крупным программным проектом, где вы сможете применить многие из изученных до сих пор техник.

Правило N 1 при работе над любым программным проектом - не пытаться решить все сразу. Программа должна строиться из меньших частей, например вспомогательных функций. Нужно проверять работу каждой части, прежде чем переходить к следующей. Если пытаться охватить слишком многое сразу, скорее всего, получится только хаос.

Для этого вам понадобится способ тестировать функции вне главной функции. Этого можно добиться, явно определив главную функцию и вызвав ее вне любой другой функции программы. Тогда один вызов функции легко закомментировать для тестирования. Первые шаги построения следующего программного проекта могли бы выглядеть так:

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

main()

Теперь вспомогательные функции можно тестировать без запуска главной функции:

# 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)

Передача данных из одной функции в другую

Когда программа содержит несколько функций, возникает вопрос: как передавать данные из одной функции в другую?

Следующий пример спрашивает у пользователя несколько целочисленных значений. Затем программа выводит эти значения и выполняет их "анализ". Программа разделена на три отдельные функции:

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)

При выполнении программа могла бы работать так:

Пример вывода

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

Идея здесь в том, что главная функция "сохраняет" все данные, обработанные программой. В этом случае все, что нужно, - это ввод пользователя в переменной inputs.

Если ввод нужен в функции, он передается как аргумент. Это происходит с функциями print_result и analyze. Если функция создает данные, которые нужны где-то еще в программе, функция возвращает их с помощью команды return, и они сохраняются в переменной в главной функции. Это происходит с функциями input_from_user и analyze.

Можно было бы использовать глобальную переменную inputs из главной функции напрямую во вспомогательных функциях. Мы уже разбирали, почему это плохая идея, но вот еще одно объяснение. Если функции могут менять глобальную переменную, в программе могут начать происходить неожиданные вещи, особенно когда количество функций становится большим.

Передачу данных в функции и из функций лучше всего обрабатывать с помощью аргументов и возвращаемых значений.

Также можно выделить неявную главную функцию из примера выше в собственную функцию. Тогда переменная inputs больше не была бы глобальной переменной, а стала бы локальной переменной внутри функции main:

# 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

Ответьте, пожалуйста, на короткий опрос по материалам этой недели.

Вы дошли до конца этого раздела!

Текущие баллы можно посмотреть в синем индикаторе в правом нижнем углу страницы.