В главе 3 вы научились создавать простые списки и работать с их отдельными элементами. В этой вы узнаете, как перебрать весь список, используя несколько строк кода (независимо от длины списка). Механизм перебора позволяет выполнить одно действие (или их набор) с каждым элементом в списке. С его помощью вы сможете эффективно работать со списками любой длины, даже состоящими из тысяч и миллионов элементов.
Типичная задача из области программирования — перебрать все элементы списка и выполнить с каждым элементом одну и ту же операцию. Например, в компьютерной игре все экранные объекты могут смещаться на одинаковую величину, или в списке чисел к каждому элементу может применяться одна и та же статистическая операция. А может быть, вам потребовалось вывести все заголовки из списка статей на сайте. В ситуациях, требующих применения одного действия к каждому элементу списка, можно воспользоваться циклами for.
Допустим, имеется список с именами фокусников, и вы хотите вывести каждое имя из списка. Конечно, можно обратиться к каждому элементу по отдельности, но такой подход создает ряд проблем. Во-первых, для очень длинных списков все сведется к однообразным повторениям. Во-вторых, при любом изменении длины списка в программу придется вносить изменения. Цикл for решает обе проблемы: Python будет следить за всеми техническими деталями в своей внутренней реализации.
В следующем примере цикл for используется для вывода имен фокусников:
magicians.py
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
print(magician)
Все начинается с определения списка, как и в главе 3. Затем определяется цикл for. С помощью этой строки Python получает указание — взять очередное имя из списка и сохранить его в переменной magician. Далее выводится имя, только что сохраненное в переменной magician. Затем строки повторяются для каждого имени в списке. Этот код можно описать так: «Для каждого фокусника в списке вывести его имя». Результат представляет собой простой перечень имен из списка:
alice
david
carolina
Концепция циклов очень важна, поскольку она представляет один из основных способов автоматизации повторяющихся задач компьютером. Например, в простом цикле, использованном в magicians.py, Python сначала читает первую строку цикла:
for magician in magicians:
Эта строка означает, что нужно взять первое значение из списка magicians и сохранить его в переменной magician. Первое значение в списке — 'alice'. Затем Python читает следующую строку:
print(magician)
Python выводит текущее значение magician, которое все еще равно 'alice'. Так как в списке еще остались другие значения, Python возвращается к первой строке цикла:
for magician in magicians:
Python берет следующее значение из списка, 'david', и сохраняет его в magician. Затем выполняет строку:
print(magician)
Python снова выводит текущее значение magician; теперь это строка 'david'. Весь цикл повторяется еще раз с последним значением в списке, 'carolina'. Так как других значений в списке не осталось, Python переходит к следующей строке в программе. В данном случае после цикла for ничего нет, поэтому программа просто завершается.
Используя циклы впервые, помните: все действия повторяются по одному разу для каждого элемента в списке независимо от их количества. Если список содержит миллион элементов, то Python повторит эти действия миллион раз — обычно это происходит очень быстро.
Кроме того, следует учесть, что при написании собственных циклов for временной переменной для текущего значения из списка можно присвоить любое имя. Однако на практике рекомендуется выбирать содержательное имя, описывающее отдельный элемент списка. Например, вот хороший способ запустить цикл for для получения списка кошек, собак и общего списка предметов:
for cat in cats:
for dog in dogs:
for item in list_of_items:
Выполнение этого правила поможет вам проследить за тем, какие действия выполняются с каждым элементом в цикле for. В зависимости от формы числа имени (единственного или множественного) вы сможете понять, с чем работает данная часть кода — с отдельным элементом или всем списком.
В цикле for с каждым элементом списка может выполняться практически любое действие. Дополним предыдущий пример, чтобы программа выводила для каждого фокусника отдельное сообщение:
magicians.py
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
print(f"{magician.title()}, that was a great trick!")
Этот код отличается от предыдущего лишь тем, что для каждого фокусника создается сообщение с его именем. При первом проходе цикла переменная magician содержит значение 'alice', поэтому Python начинает первое сообщение с имени 'Alice'. При втором проходе сообщение будет начинаться с имени 'David', а при третьем — с имени 'Carolina'.
В выводе вы увидите персональное сообщение для каждого фокусника из списка:
Alice, that was a great trick!
David, that was a great trick!
Carolina, that was a great trick!
Тело цикла for может содержать сколько угодно строк кода. Каждая строка с начальным отступом после строки for magician in magicians считается находящейся в цикле и выполняется по одному разу для каждого значения в списке. Таким образом, с каждым значением в списке можно выполнить любые операции на ваше усмотрение.
Добавим в сообщение для каждого фокусника вторую строку, в которой сообщаем каждому фокуснику, что с нетерпением ждем его следующего трюка:
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
print(f"{magician.title()}, that was a great trick!")
print(f"I can't wait to see your next trick, {magician.title()}.\n")
Поскольку оба вызова функции print() снабжены отступами, каждая строка будет выполнена по одному разу для каждого фокусника в списке. Символ новой строки ("\n") во втором вызове print() вставляет пустую строку после каждого прохода цикла. В результате будет создан набор сообщений, аккуратно сгруппированных для каждого фокусника в списке:
Alice, that was a great trick!
I can't wait to see your next trick, Alice.
David, that was a great trick!
I can't wait to see your next trick, David.
Carolina, that was a great trick!
I can't wait to see your next trick, Carolina.
Повторюсь: тело цикла for может содержать сколько угодно строк кода. На практике часто требуется выполнить в этом цикле несколько разных операций для каждого элемента списка.
Что происходит после завершения цикла for? Обычно программа выводит некую сводную информацию или переходит к другим операциям.
Каждая строка кода после цикла for, не имеющая отступа, выполняется без повторения. Допустим, вы хотите вывести сообщение для всей группы фокусников и поблагодарить их за превосходное представление. Чтобы вывести общее сообщение после всех отдельных, поместите его после цикла for, не делая отступа:
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
print(f"{magician.title()}, that was a great trick!")
print(f"I can't wait to see your next trick, {magician.title()}.\n")
print("Thank you, everyone. That was a great magic show!")
Первые два вызова функции print() повторяются по одному разу для каждого фокусника в списке, как было показано ранее. Но поскольку последняя строка не имеет отступа, это сообщение выводится только раз:
Alice, that was a great trick!
I can't wait to see your next trick, Alice.
David, that was a great trick!
I can't wait to see your next trick, David.
Carolina, that was a great trick!
I can't wait to see your next trick, Carolina.
Thank you, everyone. That was a great magic show!
При обработке данных в циклах for завершающее сообщение позволяет подвести итог операции, выполненной со всем набором данных. Например, цикл for может инициализировать игру, перебирая список персонажей и изображая каждого персонажа на экране. После цикла выполняется блок без отступа, который выводит кнопку Play Now (Начало игры) после того, как все персонажи появятся на экране.
Python использует отступы, чтобы определить, как одна строка или группа строк связана с остальной частью программы. В предыдущих примерах строки, выводившие сообщения для отдельных фокусников, были частью цикла for, поскольку имели отступы. Наличие отступов в Python сильно упрощает чтение кода. Фактически отступы заставляют разработчика писать отформатированный код, имеющий четкую визуальную структуру. В более длинных программах Python могут встречаться блоки кода с отступами нескольких разных уровней. Эти уровни помогают вам понимать общую структуру программы.
Когда разработчики только начинают писать код, работа которого зависит от правильности отступов, в их коде нередко встречаются распространенные ошибки, связанные с отступами (indentation errors). Например, иногда программисты расставляют отступы в коде, не нуждающемся в отступах, или наоборот — забывают добавлять отступы в блоках, где это необходимо. Несколько примеров помогут вам избежать подобных ошибок в будущем и успешно исправлять их, когда они встретятся в ваших программах.
Итак, рассмотрим несколько типичных ошибок, связанных с использованием отступов.
Строка после оператора for в цикле всегда должна иметь отступ. Если вы забудете добавить его, то Python напомнит вам об этом:
magicians.py
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
❶ print(magician)
Вызов функции print() ❶ должен иметь отступ, но здесь его нет. Когда Python ожидает увидеть блок с отступом, но не находит его, появляется сообщение с указанием номера строки:
File "magicians.py", line 3
print(magician)
^
IndentationError: expected an indented block after 'for' statement on line 2
Обычно для устранения подобных ошибок достаточно поставить отступ в строке (или строках), следующей (-их) непосредственно после оператора for.
Иногда цикл выполняется без ошибок, но не выдает ожидаемых результатов. Такое часто происходит, когда вы пытаетесь выполнить несколько операций в цикле, но забываете добавить отступ в некоторые из строк.
Например, вот что происходит, если вы забудете добавить отступ во вторую строку в цикле:
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
print(f"{magician.title()}, that was a great trick!")
❶ print(f"I can't wait to see your next trick, {magician.title()}.\n")
Второй вызов функции print() ❶ должен иметь отступ, но поскольку Python находит хотя бы одну строку с отступом после оператора for, сообщение об ошибке не выдается. В результате первый вызов print() будет выполнен для каждого элемента в списке, поскольку в нем есть отступ. Во втором вызове print() отступа нет, поэтому он будет выполнен только раз после завершения цикла. Так как последним значением magician является строка 'carolina', второе сообщение будет выведено только с этим именем:
Alice, that was a great trick!
David, that was a great trick!
Carolina, that was a great trick!
I can't wait to see your next trick, Carolina.
Это пример логической ошибки (logical error). Синтаксис является допустимым, но код не приводит к желаемому результату, поскольку проблема кроется в его логике. Если какое-то действие должно повторяться для каждого элемента в списке, но выполняется только раз, то проверьте, не нужно ли добавить отступы в одной или нескольких строках кода.
Если вы случайно поставите отступ в строке, в которой он не нужен, то Python сообщит об этом:
hello_world.py
message = "Hello Python world!"
print(message)
Отступ для вызова функции print() не нужен, поскольку эта строка не подчинена предшествующей; Python сообщает об ошибке:
File "hello_world.py", line 2
print(message)
^
IndentationError: unexpected indent
Чтобы избежать непредвиденных ошибок с отступами, используйте их только там, где для этого есть конкретные причины. В тех программах, которые вы пишете на данной стадии изучения Python, отступы нужны только в строках действий, повторяемых для каждого элемента в цикле for.
Если вы случайно добавите отступ в код, который должен выполняться после завершения цикла, этот код будет выполнен для каждого элемента. Иногда Python выводит сообщение об ошибке, но часто дело ограничивается простой логической ошибкой.
Например, посмотрим, что произойдет, если случайно добавить отступ в строку, в которой мы благодарим фокусников за хорошее представление:
magicians.py
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
print(f"{magician.title()}, that was a great trick!")
print(f"I can't wait to see your next trick, {magician.title()}.\n")
❶ print("Thank you everyone, that was a great magic show!")
Поскольку в последней строке ❶ отступ есть, сообщение будет продублировано для каждого фокусника в списке:
Alice, that was a great trick!
I can't wait to see your next trick, Alice.
Thank you everyone, that was a great magic show!
David, that was a great trick!
I can't wait to see your next trick, David.
Thank you everyone, that was a great magic show!
Carolina, that was a great trick!
I can't wait to see your next trick, Carolina.
Thank you everyone, that was a great magic show!
Это еще один пример логической ошибки, подобной той, которая описана в подразделе «Пропущенные отступы в других строках». Python не знает, что вы пытаетесь сделать с помощью кода, поэтому просто выполняет весь код, не нарушающий правил синтаксиса. Если действие, которое должно выполняться один раз, происходит многократно, то проверьте, нет ли лишнего отступа в соответствующей строке кода; если есть — удалите.
Двоеточие в конце оператора for сообщает Python, что следующая строка является началом цикла.
magicians = ['alice', 'david', 'carolina']
❶ for magician in magicians
print(magician)
Если вы случайно забудете поставить двоеточие, как показано в примере ❶, то произойдет синтаксическая ошибка, поскольку Python не понимает, что именно вы пытаетесь сделать:
File "magicians.py", line 2
for magician in magicians
^
SyntaxError: expected ':'
Python не знает, забыли ли вы указать двоеточие или хотели дополнить код операторами, чтобы создать более сложный цикл. Если интерпретатор способен определить возможное исправление, то предложит его, например добавит двоеточие в конец строки, как это сделано в строке expected ':'. Одни ошибки легко исправляются благодаря подобным предложениям в трассировках Python. Другие исправить гораздо сложнее, даже если в конечном счете исправление касается лишь одного символа. Не расстраивайтесь, если на поиск небольшого исправления уходит много времени; не вы первый, не вы последний.
Упражнения
4.1. Пицца. Вспомните по крайней мере три названия ваших любимых видов пиццы. Сохраните их в списке и используйте цикл for для вывода всех названий.
• Измените цикл for так, чтобы вместо простого названия пиццы выводилось сообщение, содержащее это название. Таким образом, для каждого элемента должна выводиться строка с простым текстом вида «Я люблю пиццу пеперони».
• Добавьте в конец программы (после цикла for) строку, в которой будет указано, насколько вы любите пиццу. Таким образом, вывод должен состоять из трех (и более) строк с названиями пиццы и дополнительного предложения — скажем, «Я очень люблю пиццу!»
4.2. Животные. Создайте список из трех (и более) животных, обладающих общей характеристикой. Используйте цикл for для вывода названий каждого животного.
• Измените программу так, чтобы она выводила сообщение о каждом животном, — например, «Собака — отличное домашнее животное».
• Добавьте в конец программы строку с описанием общего свойства. Например, можно вывести сообщение «Любое из этих животных — отличное домашнее животное!»
Необходимость хранения наборов чисел возникает в программах по многим причинам. Например, в компьютерной игре могут храниться координаты каждого персонажа на экране, таблицы рекордов и т.д. В программах визуализации данных пользователь почти всегда работает с наборами чисел: температурой, расстоянием, численностью населения, широтой/долготой и другими числовыми данными.
Списки идеально подходят для хранения наборов чисел, а Python предоставляет специальные средства, позволяющие эффективно работать с числовыми списками. Достаточно один раз понять, как пользоваться этими средствами, — и ваш код будет хорошо работать даже в том случае, если список содержит миллионы элементов.
Функция range() упрощает создание числовых последовательностей. Например, с ее помощью можно легко вывести серию чисел:
first_numbers.py
for value in range(1,5):
print(value)
И хотя на первый взгляд может показаться, что код должен вывести числа от 1 до 5, на самом деле число 5 не выводится:
1
2
3
4
В этом примере функция range() выводит только числа от 1 до 4. Перед вами еще один вариант явления «смещения на 1», часто встречающегося в языках программирования. При выполнении range() Python начинает отсчет от первого переданного значения и прекращает его при достижении второго. Так как на втором значении происходит остановка, конец интервала (в данном случае 5) не встречается в выводе.
Чтобы вывести числа от 1 до 5, используйте функцию range(1, 6):
for value in range(1, 6):
print(value)
На этот раз вывод начинается с единицы и завершается цифрой 5:
1
2
3
4
5
Если ваша программа при использовании функции range() выводит не тот результат, на который вы рассчитывали, попробуйте увеличить конечное значение на 1.
Кроме того, функции range() можно передать только один аргумент; в этом случае последовательность чисел будет начинаться с 0. Например, range(6) вернет числа от 0 до 5.
Если вы хотите создать числовой список, то преобразуйте результаты range() в список с помощью функции list(). Если заключить вызов range() в функцию list(), то результат будет представлять собой список с числовыми элементами.
В примере из предыдущего подраздела числовая последовательность просто выводилась на экран. Тот же набор чисел можно преобразовать в список с помощью функции list():
numbers = list(range(1, 6))
print(numbers)
Результат:
[1, 2, 3, 4, 5]
Кроме того, функция range() может генерировать числовые последовательности, пропуская числа в заданном диапазоне. Если вы передадите в range() третий аргумент, то Python будет использовать это значение в качестве величины шага при генерации чисел.
Например, список четных чисел от 1 до 10 создается так:
even_numbers.py
even_numbers = list(range(2, 11, 2))
print(even_numbers)
В этом примере функция range() начинает со значения 2, а затем увеличивает его на 2. Приращение 2 последовательно применяется до тех пор, пока не будет достигнуто или пройдено конечное значение 11, после чего выводится результат:
[2, 4, 6, 8, 10]
С помощью функции range() можно создать практически любой диапазон чисел. Например, как бы вы создали список квадратов всех целых чисел от 1 до 10? В языке Python операция возведения в степень обозначается двумя звездочками (**). Один из возможных вариантов списка квадратов выглядит так:
square_numbers.py
squares = []
for value in range(1, 11):
❶ square = value ** 2
❷ squares.append(square)
print(squares)
Сначала создается пустой список squares. Затем Python перебирает все значения от 1 до 10 с помощью функции range(). В цикле текущее значение возводится во вторую степень, а результат сохраняется в переменной square ❶. Каждое новое значение square присоединяется к списку squares ❷. Наконец, после завершения цикла выводится список квадратов:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
Чтобы сделать код более компактным, можно опустить временную переменную square и присоединять каждое новое значение прямо к списку:
squares = []
for value in range(1, 11):
squares.append(value**2)
print(squares)
Эта строка выполняет ту же работу, что и код цикла for, показанный выше. Каждое значение в цикле возводится во вторую степень, а затем немедленно присоединяется к списку квадратов.
При создании более сложных списков можно применять любой из двух подходов. В одних случаях использование временной переменной упрощает чтение кода; в других — чрезмерно удлиняет код. Сначала сосредоточьтесь на написании четкого и понятного кода, который делает именно то, что нужно, и только потом переходите к анализу кода и поиску более эффективных решений.
Некоторые функции Python предназначены для работы с числовыми списками. Например, вы можете легко узнать минимум, максимум и сумму числового списка:
>>> digits = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
>>> min(digits)
0
>>> max(digits)
9
>>> sum(digits)
45
ПРИМЕЧАНИЕ
В примерах этого раздела используются короткие списки чисел, но это делается только для того, чтобы данные помещались на странице. Примеры будут работать и в том случае, если список содержит миллионы чисел.
Описанный выше пример генерирования списка squares состоял из трех или четырех строк кода. Генератор списка (list comprehension) позволяет создать тот же список всего одной строкой, объединяя цикл for и создание новых элементов в одну строку и автоматически добавляя к списку все новые элементы. В учебниках для начинающих программистов не всегда рассказывается о генераторах списков, но я привожу этот материал, поскольку вы с большой вероятностью встретите данную конструкцию, как только начнете просматривать код других разработчиков.
В следующем примере знакомый вам список квадратов создается с помощью генератора списка:
squares.py
squares = [value**2 for value in range(1, 11)]
print(squares)
Чтобы использовать этот синтаксис, начните с описательного имени списка — например, squares. Затем откройте квадратные скобки и определите выражение для значений, которые должны быть сохранены в новом списке. В данном примере это выражение value**2, которое возводит значение во вторую степень. Затем напишите цикл for для генерирования чисел, которые должны передаваться выражению, и закройте квадратные скобки. Цикл for в данном примере — for value in range(1, 11) — передает значения с 1 до 10 выражению value**2. Обратите внимание на отсутствие двоеточия в конце оператора for.
Результатом будет уже знакомый вам список квадратов:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
Чтобы успешно писать собственные генераторы списков, необходим определенный опыт. Тем не менее, освоив создание обычных списков, вы оцените возможности генераторов. Когда после очередного трех-четырехстрочного блока вам надоест создавать списки, подумайте о написании генераторов.
Упражнения
4.3. Считаем до 20. Используйте цикл for для вывода чисел от 1 до 20 включительно.
4.4. Миллион. Создайте список чисел от 1 до 1 000 000, затем воспользуйтесь циклом for для вывода чисел. (Если вывод занимает слишком много времени, то остановите его, нажав Ctrl+C или закрыв окно вывода.)
4.5. Суммирование миллиона чисел. Создайте список чисел от 1 до 1 000 000, затем воспользуйтесь функциями min() и max() и убедитесь, что список действительно начинается с 1 и заканчивается 1 000 000. Вызовите функцию sum() и посмотрите, насколько быстро Python сможет суммировать миллион чисел.
4.6. Нечетные числа. Используйте третий аргумент функции range(), чтобы создать список нечетных чисел от 1 до 20. Выведите все числа в цикле for.
4.7. Тройки. Создайте список чисел, кратных 3, в диапазоне от 3 до 30. Выведите все числа списка в цикле for.
4.8. Кубы. Результат возведения числа в третью степень называется кубом. Например, куб 2 записывается в языке Python как 2**3. Создайте список первых 10 кубов (то есть кубов всех целых чисел от 1 до 10) и с помощью цикла for выведите значения каждого куба.
4.9. Генератор кубов. Используйте конструкцию генератора списка для создания списка первых 10 кубов.
В главе 3 вы узнали, как обращаться к отдельным элементам списка, а в этой мы занимались перебором всех его элементов. Можно работать и с конкретным подмножеством элементов списка; в Python такие подмножества называются срезами (slices).
Чтобы создать срез списка, следует задать индексы первого и последнего элементов, с которыми вы намереваетесь работать. Как и в случае с функцией range(), Python останавливается на элементе, предшествующем второму индексу. Например, чтобы вывести первые три элемента списка, запросите индексы с 0 по 3 и получите элементы 0, 1 и 2.
В следующем примере используется список игроков команды:
players.py
players = ['charles', 'martina', 'michael', 'florence', 'eli']
print(players[0:3])
Здесь выводится часть списка. Вывод сохраняет структуру списка, но содержит только первых трех игроков:
['charles', 'martina', 'michael']
Подмножество может содержать любую часть списка. Например, чтобы ограничиться вторым, третьим и четвертым элементами списка, создайте срез, который начинается с индекса 1 и заканчивается на индексе 4:
players = ['charles', 'martina', 'michael', 'florence', 'eli']
print(players[1:4])
На этот раз срез начинается с элемента 'martina' и заканчивается элементом 'florence':
['martina', 'michael', 'florence']
Если первый индекс среза не указан, то Python автоматически начинает срез от начала списка:
players = ['charles', 'martina', 'michael', 'florence', 'eli']
print(players[:4])
Без начального индекса Python берет элементы от начала списка:
['charles', 'martina', 'michael', 'florence']
Аналогичный синтаксис работает и для срезов, содержащих конец списка. Например, если вам нужны все элементы с третьего до последнего, то начните с индекса 2 и не указывайте второй индекс:
players = ['charles', 'martina', 'michael', 'florence', 'eli']
print(players[2:])
Python возвращает все элементы с третьего до конца списка:
['michael', 'florence', 'eli']
Этот синтаксис позволяет вывести все элементы от любой позиции до конца списка независимо от его длины. Вспомните, что отрицательный индекс возвращает элемент, находящийся на определенном расстоянии от конца списка; следовательно, вы можете получить любой срез от конца списка. Например, чтобы отобрать последних трех игроков из списка, используйте срез players[–3:]:
players = ['charles', 'martina', 'michael', 'florence', 'eli']
print(players[-3:])
Программа выводит имена трех последних игроков, причем продолжает работать даже при изменении размера списка.
ПРИМЕЧАНИЕ
В квадратные скобки, определяющие срез, можно добавить третье значение. Если оно присутствует, то сообщает Python, сколько элементов следует пропускать при выборе элементов в заданном диапазоне.
Если вы хотите перебрать элементы, входящие в подмножество элементов, используйте срез в цикле for. В следующем примере программа перебирает первых трех игроков и выводит их имена как часть простого списка:
players = ['charles', 'martina', 'michael', 'florence', 'eli']
print("Here are the first three players on my team:")
❶ for player in players[:3]:
print(player.title())
Вместо того чтобы перебирать весь список игроков, Python ограничивается первыми тремя именами ❶:
Here are the first three players on my team:
Charles
Martina
Michael
Срезы очень полезны во многих ситуациях. Например, при создании компьютерной игры итоговый счет игрока может добавляться в список после окончания текущей партии. После этого программа может получить три лучших результата игрока, отсортировав список по уменьшению и получив срез, содержащий только три элемента. При работе с данными срезы могут использоваться для обработки данных блоками заданного размера. Или при создании веб-приложения срезы можно использовать для постраничного вывода информации так, чтобы на каждой странице выводился соответствующий объем информации.
Часто разработчик берет существующий список и на его основе создает совершенно новый. Посмотрим, как работает копирование списков, и разберем одну ситуацию, в которой копирование списка может принести пользу.
Чтобы скопировать список, создайте срез, содержащий весь исходный список без указания первого и второго индексов ([:]). Эта конструкция создает срез, который начинается с первого элемента и завершается последним; в результате создается копия всего списка.
Представьте, что вы создали список своих любимых блюд и теперь хотите создать отдельный список блюд, которые нравятся вашему другу. Пока вашему другу нравятся все блюда из нашего списка, поэтому вы можете создать другой список, просто скопировав наш:
foods.py
my_foods = ['pizza', 'falafel', 'carrot cake']
❶ friend_foods = my_foods[:]
print("My favorite foods are:")
print(my_foods)
print("\nMy friend's favorite foods are:")
print(friend_foods)
Сначала создается список блюд my_foods. Затем создается другой список friend_foods. Чтобы создать копию my_foods, программа запрашивает срез my_foods без указания индексов ❶ и сохраняет копию в friend_foods. При выводе обоих списков становится видно, что они содержат одинаковые данные:
My favorite foods are:
['pizza', 'falafel', 'carrot cake']
My friend's favorite foods are:
['pizza', 'falafel', 'carrot cake']
Чтобы доказать, что на самом деле речь идет о двух разных списках, добавим новое блюдо в каждый список и покажем, что каждый из них отслеживает любимые блюда человека:
my_foods = ['pizza', 'falafel', 'carrot cake']
❶ friend_foods = my_foods[:]
❷ my_foods.append('cannoli')
❸ friend_foods.append('ice cream')
print("My favorite foods are:")
print(my_foods)
print("\nMy friend's favorite foods are:")
print(friend_foods)
Исходные элементы my_foods копируются в новый список friend_foods ❶. Затем в каждый список добавляется новый элемент: 'cannoli' в my_foods ❷, а 'ice cream' в friend_foods ❸. После этого вывод двух списков наглядно показывает, что каждое блюдо находится в соответствующем списке:
My favorite foods are:
['pizza', 'falafel', 'carrot cake', 'cannoli']
My friend's favorite foods are:
['pizza', 'falafel', 'carrot cake', 'ice cream']
Вывод показывает, что элемент 'cannoli' находится в списке my_foods, а элемент 'ice cream' — нет. Видно, что 'ice cream' входит в список friend_foods, а элемент 'cannoli' — нет. Если бы два этих списка просто совпадали, то их содержимое не различалось бы. Например, вот что происходит при попытке копирования списка без использования среза:
my_foods = ['pizza', 'falafel', 'carrot cake']
# Не работает:
friend_foods = my_foods
my_foods.append('cannoli')
friend_foods.append('ice cream')
print("My favorite foods are:")
print(my_foods)
print("\nMy friend's favorite foods are:")
print(friend_foods)
Вместо того чтобы сохранять копию my_foods в friend_foods, мы присваиваем переменной friend_foods значение переменной my_foods. На самом деле этот синтаксис сообщает Python, что новая переменная friend_foods должна быть связана со списком, уже хранящимся в my_foods, поэтому теперь обе переменные связаны с одним списком. В результате при добавлении элемента 'cannoli' в my_foods этот элемент также появляется в friend_foods. Аналогичным образом элемент 'ice cream' оказывается в обоих списках, хотя на первый взгляд был добавлен только в friend_foods.
Вывод показывает, что оба списка содержат одинаковые элементы, а это совсем не то, что требовалось:
My favorite foods are:
['pizza', 'falafel', 'carrot cake', 'cannoli', 'ice cream']
My friend's favorite foods are:
['pizza', 'falafel', 'carrot cake', 'cannoli', 'ice cream']
ПРИМЕЧАНИЕ
Если какие-то подробности в этом примере кажутся непонятными, то не огорчайтесь. Если при работе с копией списка происходит что-то непредвиденное, убедитесь в том, что копируете список с помощью среза, как мы делали в первом примере.
Упражнения
4.10. Срезы. Добавьте в конец одной из программ, написанных в этой главе, фрагмент, который делает следующее:
• выводит сообщение «Первые три пункта в списке — это:», а затем использует срез для вывода первых трех элементов из списка;
• выводит сообщение «Три пункта из середины списка:», а затем использует срез для вывода первых трех элементов из середины списка;
• выводит сообщение «Последние три пункта в списке — это:», а затем использует срез для вывода последних трех элементов из списка.
4.11. Моя пицца, твоя пицца. Начните с программы из упражнения 4.1. Создайте копию списка с видами пиццы, присвойте ему имя friend_pizzas. Затем сделайте следующее:
• добавьте новую пиццу в исходный список;
• добавьте другую пиццу в список friend_pizzas;
• докажите, что в программе существуют два разных списка. Выведите сообщение «Мои любимые пиццы:», а затем первый список в цикле for. Выведите сообщение «Любимые пиццы моего друга:», а затем второй список в цикле for. Убедитесь в том, что каждая новая пицца находится в соответствующем списке.
4.12. Больше циклов. Во всех версиях foods.py из этого раздела мы избегали использования цикла for при выводе для экономии места. Выберите версию foods.py и напишите два цикла for для вывода каждого списка.
Списки позволяют хранить наборы элементов, которые могут изменяться на протяжении жизненного цикла программы. Например, возможность изменения списков необходима при работе со списками пользователей сайта или списками персонажей игры. Однако в некоторых ситуациях требуется создать список элементов, который не может изменяться. Кортежи (tuples) предоставляют именно такую возможность. В языке Python значения, которые не могут изменяться, называются неизменяемыми (immutable), а неизменяемый список называется кортежем.
Кортеж выглядит как список, за исключением того, что вместо квадратных скобок используются круглые. После определения кортежа вы можете обращаться к его отдельным элементам по индексам точно так же, как это делается при работе со списком.
Допустим, у нас в программе имеется прямоугольник, у которого всегда должны быть строго определенные размеры. Чтобы гарантировать их неизменность, можно объединить их в кортеж:
dimensions.py
dimensions = (200, 50)
print(dimensions[0])
print(dimensions[1])
В данном коде определяется кортеж dimensions, при этом вместо квадратных скобок ставятся круглые. Затем каждый элемент кортежа выводится по отдельности с помощью того же синтаксиса, который использовался для обращения к элементу списка:
200
50
Посмотрим, что произойдет при попытке изменения одного из элементов в кортеже dimensions:
dimensions = (200, 50)
dimensions[0] = 250
Код пытается изменить первое значение, но Python возвращает ошибку типа. По сути, мы пытаемся изменить кортеж, а эта операция недопустима для объектов данного типа, так что Python сообщает о невозможности присваивания нового значения элементу в кортеже:
Traceback (most recent call last):
File "dimensions.py", line 2, in <module>
dimensions[0] = 250
TypeError: 'tuple' object does not support item assignment
И это хорошо, поскольку мы хотим, чтобы Python сообщал о попытке изменения размеров прямоугольника в программе, выдавая сообщение об ошибке.
ПРИМЕЧАНИЕ
Формально кортеж определяется наличием запятой; круглые скобки просто делают запись более аккуратной и понятной. Если вы хотите задать кортеж, состоящий из одного элемента, то добавьте завершающую запятую:
my_t = (3,)
Обычно создание кортежа из одного элемента не имеет особого смысла. Тем не менее это может произойти при автоматическом генерировании кортежей.
Для данной операции используется цикл for, как и при работе со списками:
dimensions = (200, 50)
for dimension in dimensions:
print(dimension)
Python возвращает все элементы кортежа по аналогии с тем, как это делается со списком:
200
50
Элементы кортежа не могут изменяться, но вы можете присвоить новое значение переменной, в которой хранится кортеж. Таким образом, для изменения размеров прямоугольника следует переопределить весь кортеж:
dimensions = (200, 50)
print("Original dimensions:")
for dimension in dimensions:
print(dimension)
dimensions = (400, 100)
print("\nModified dimensions:")
for dimension in dimensions:
print(dimension)
Первые четыре строки кода определяют исходный кортеж и выводят исходные размеры. Затем в переменной dimensions сохраняется новый кортеж, после чего выводятся новые размеры. На этот раз Python не выдает сообщений об ошибках, поскольку замена значения переменной является допустимой операцией:
Original dimensions:
200
50
Modified dimensions:
400
100
По сравнению со списками структуры данных кортежи относительно просты. Используйте их для хранения наборов значений, которые не должны изменяться на протяжении жизненного цикла программы.
Упражнения
4.13. Шведский стол. Меню шведского стола в ресторане состоит всего из пяти пунктов. Придумайте пять простых блюд и сохраните их в кортеже.
• Используйте цикл for для вывода всех блюд, предлагаемых рестораном.
• Попробуйте изменить один из элементов и убедитесь в том, что Python отказывается вносить изменения.
• Ресторан обновляет меню, заменяя два элемента другими блюдами. Добавьте блок кода, который заменяет кортеж, и используйте цикл for для вывода каждого элемента нового меню.
Итак, вы постепенно начинаете писать более длинные программы, и вам стоит познакомиться с некоторыми рекомендациями по форматированию кода. Не жалейте времени на то, чтобы сделать ваш код максимально читабельным. Понятный код помогает следить за тем, что делает программа, и упрощает его изучение другими разработчиками.
Программисты Python выработали ряд правил по стилю, чтобы код, написанный разными программистами, имел хотя бы отдаленно похожую структуру. Научившись писать «чистый» код Python, вы сможете понять общую структуру кода, написанного любым другим программистом, соблюдающим те же рекомендации. Если вы рассчитываете когда-нибудь стать профессиональным программистом, то стоит как можно скорее начать следовать этим рекомендациям, чтобы выработать полезную привычку.
Когда кто-нибудь хочет внести изменения в язык Python, он пишет документ PEP (Python Enhancement Proposal, предложение по улучшению Python). Один из самых старых PEP — документ PEP 8 с рекомендациями по форматированию кода. Он довольно длинный, но бо́льшая часть документа посвящена более сложным программным структурам, чем те, которые встречались вам до настоящего момента.
Руководство по стилю Python было написано на основании того факта, что код читается чаще, чем пишется. Вы пишете код один раз, а потом начинаете читать его, когда переходите к отладке. При расширении функциональности программы вы снова читаете код. А когда им начинают пользоваться другие программисты, они тоже читают его.
Выбирая между написанием кода, который проще пишется, и кодом, который проще читается, программисты Python почти всегда рекомендуют второй вариант. Следующие советы помогут вам с самого начала писать чистый, понятный код.
PEP 8 рекомендует обозначать уровень отступа четырьмя пробелами. Их использование упрощает чтение программы и при этом оставляет достаточно места для нескольких уровней отступов в каждой строке.
В программах форматирования текста для создания отступов вместо пробелов часто используется табуляция. Такой способ хорошо работает в текстовых процессорах, но интерпретатор Python приходит в замешательство, когда знаки табуляции смешиваются с пробелами. В любом редакторе кода есть параметр конфигурации, который заменяет нажатие клавиши табуляции заданным количеством пробелов. Конечно, этой клавишей удобно пользоваться, но вы должны проследить за тем, чтобы редактор вставлял в документ пробелы вместо знака табуляции.
Смешение знаков табуляции и пробелов в файле может создать проблемы, сильно затрудняющие диагностику. Если вы думаете, что в программе знаки табуляции смешались с пробелами, то можете преобразовать все знаки табуляции в пробелы — в большинстве редакторов есть такая возможность.
Многие программисты Python рекомендуют ограничивать длину строк 80 символами. Исторически эта рекомендация появилась из-за того, что в большинстве компьютеров в одной строке терминального окна помещалось всего 79 символов. В настоящее время на экранах помещаются куда более длинные строки, но для использования стандартной длины строки в 79 символов существуют и другие причины.
Профессиональные программисты часто открывают на одном экране сразу несколько файлов; стандартная длина строки позволяет видеть все строки в двух или трех файлах, открытых на экране одновременно. Кроме того, PEP 8 рекомендует ограничивать комментарии 72 символами на строку, поскольку некоторые служебные программы, автоматически генерирующие документацию в больших проектах, добавляют символы форматирования в начале каждой строки комментария.
Рекомендации PEP 8 по выбору длины строки не являются незыблемыми, и некоторые программисты предпочитают ограничение в 99 символов. Пока вы учитесь, длина строки в коде не так важна, но учтите, что при совместной работе в группах почти всегда соблюдаются рекомендации PEP 8. В большинстве редакторов можно установить визуальный ориентир (обычно вертикальную линию на экране), показывающий, где проходит граница.
ПРИМЕЧАНИЕ
В приложении Б описано, как настроить редактор кода, чтобы он всегда вставлял четыре пробела при нажатии клавиши табуляции и отображал вертикальную линию, позволяющую соблюдать ограничение длины строки в 79 символов.
Пустые строки применяются для визуальной группировки частей программы. Используйте их для структурирования файлов, но не злоупотребляйте. Примеры, приведенные в книге, помогут вам выработать нужный баланс. Например, если в программе пять строк кода создают список, а затем следующие три строки что-то делают с этим списком, то два фрагмента уместно разделить пустой строкой. Однако между ними не стоит вставлять три или четыре пустые строки.
Пустые строки не влияют на работу кода, но отражаются на его удобочитаемости. Интерпретатор Python использует горизонтальные отступы для интерпретации смысла кода, но игнорирует межстрочные интервалы.
PEP 8 содержит много других рекомендаций по стилю, но они в основном относятся к программам, более сложным, чем те, которые вы пишете на данный момент. По мере изучения более сложных элементов Python я буду приводить соответствующие фрагменты рекомендаций PEP 8.
Упражнения
4.14. Просмотрите оригинальное руководство по стилю PEP 8 по адресу https://python.org/dev/peps/pep-0008/. На данном этапе вы будете пользоваться им относительно редко, но просмотреть его будет интересно.
4.15. Анализ кода. Выберите три программы, написанные в этой главе, и измените каждую в соответствии с рекомендациями PEP 8.
• Используйте четыре пробела для каждого уровня отступов. Настройте редактор кода так, чтобы он вставлял четыре пробела при каждом нажатии клавиши табуляции, если не сделали этого ранее (за инструкциями обращайтесь к приложению Б).
• Используйте менее 80 символов в каждой строке. Настройте редактор так, чтобы он отображал вертикальную линию на месте 80-го символа.
• Не злоупотребляйте пустыми строками в файлах программ.
В этой главе вы научились эффективно работать с элементами списка. Вы узнали, как работать со списком в цикле for, как Python использует отступы для определения структуры программы и как избежать некоторых типичных ошибок при использовании отступов. Вы научились создавать простые числовые списки, а также изучили некоторые операции с числовыми списками. Вы узнали, как создать срез списка, позволяющий работать с подмножеством элементов, и как правильно копировать списки с помощью среза. Кроме того, вы познакомились с кортежами, до определенной степени защищающими наборы значений, которые не должны изменяться, и изучили рекомендации по форматированию вашего кода (сложность которого со временем только возрастает), позволяющие упростить его чтение.
В главе 5 вы узнаете, как обрабатывать различные условия с помощью операторов if. Вы научитесь группировать относительно сложные наборы проверок, позволяющие обрабатывать именно ту ситуацию или информацию, которая вам нужна. Кроме того, вы узнаете, как использовать операторы if при переборе элементов списка и выполнять действия с элементами, отобранными по какому-то критерию.