Изучаем Python. Программирование игр, визуализация данных, веб-приложения

Мэтиз Эрик

4. Работа со списками

 

В главе 3 вы научились создавать простые списки и работать с отдельными элементами списков. В этой главе вы узнаете, как перебрать весь список всего в нескольких строках кода (независимо от длины списка). Механизм перебора позволяет выполнить одно действие или набор действий с каждым элементом в списке. С его помощью вы сможете эффективно работать со списками любой длины, даже состоящими из тысяч и миллионов элементов.

 

Перебор всего списка

Типичная задача из области программирования — перебрать все элементы списка и выполнить с каждым элементом одну и ту же операцию. Например, в компьютерной игре все экранные объекты могут смещаться на одинаковую величину, или в списке чисел к каждому элементу может применяться одна и та же статистическая операция. А может быть, вам нужно вывести все заголовки из списка статей на ­сайте. В ситуациях, требующих применения одного действия к каждому элементу списка, можно воспользоваться циклами for.

Допустим, имеется список с именами фокусников, и вы хотите вывести каждое имя из списка. Конечно, можно обратиться к каждому элементу по отдельности, но такой подход создает ряд проблем. Во-первых, для очень длинных списков все сведется к однообразным повторениям. Во-вторых, при любом изменении длины списка в программу придется вносить изменения. Цикл for решает обе проблемы: Python будет следить за всеми техническими деталями в своей внутренней реализации.

В следующем примере цикл for используется для вывода имен фокусников:

magicians.py

(1) magicians = ['alice', 'david', 'carolina']

(2)for magician in magicians:

(3)print(magician)

Все начинается с определения списка (1) , как и в главе 3. В точке (2) определяется цикл for. Эта строка приказывает Python взять очередное имя из списка и сохранить его в переменной magician. В точке (3) выводится имя, только что сохраненное в переменной magician. Затем строки (1) и (2) повторяются для каждого имени в списке. Этот код можно описать так: «Для каждого фокусника в списке вывести его имя». Результат представляет собой простой перечень имен из списка:

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 cat in cats:

for dog in dogs:

for item in list_of_items:

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

 

Более сложные действия в циклах for

В цикле for с каждым элементом списка может выполняться практически любое действие. Дополним предыдущий пример, чтобы программа выводила для каждого фокусника отдельное сообщение:

magicians = ['alice', 'david', 'carolina']

for magician in magicians:

(1) . .print(magician.title() + ", that was a great trick!")

Единственное отличие этого кода от предыдущего заключается в том, что в точке (1) для каждого фокусника строится сообщение с его именем. При первом проходе цикла переменная 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(magician.title() + ", that was a great trick!")

(1) . .print("I can't wait to see your next trick, " + magician. title() + ".\n")

Так как обе команды print снабжены отступами, каждая строка будет выполнена по одному разу для каждого фокусника в списке. Символ новой строки ("\n") во второй команде print (1) вставляет пустую строку после каждого прохода цикла. В результате будет создан набор сообщений, аккуратно сгруппированных для каждого фокусника в списке:

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? Обычно программа выводит сводную информацию или переходит к другим операциям.

Каждая строка кода после цикла for, не имеющая отступа, выполняется без повторения. Допустим, вы хотите вывести сообщение для всей группы фокусников и поблагодарить их за превосходное представление. Чтобы вывести общее сообщение после всех отдельных сообщений, разместите его после цикла for без отступа:

magicians = ['alice', 'david', 'carolina']

for magician in magicians:

. .print(magician.title() + ", that was a great trick!")

. .print("I can't wait to see your next trick, " + magician.title() + ".\n")

. .

(1) print("Thank you, everyone. That was a great magic show!")

Первые две команды print повторяются по одному разу для каждого фокусника в списке, как было показано ранее. Но поскольку строка (1) отступа не имеет, это сообщение выводится только один раз:

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 может инициализировать игру, перебирая список персонажей и изображая каждого персонажа на экране. После цикла выполняется блок без отступа, который выводит кнопку Начать игру после того, как все персонажи появятся на экране.

 

Предотвращение ошибок с отступами

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

Когда разработчики только начинают писать код, работа которого зависит от ­правильности отступов, в их коде нередко встречаются распространенные ошибки. Например, иногда они расставляют отступы в коде, в котором эти отступы не нужны, или наоборот — забывают ставить отступы в блоках, где это необходимо. Несколько примеров помогут вам избежать подобных ошибок в будущем и успешно исправлять их, когда они встретятся в ваших программах.

Итак, рассмотрим несколько типичных ошибок при использовании отступов.

 

Пропущенный отступ

Строка после команды for в цикле всегда должна снабжаться отступом. Если вы забудете поставить отступ, Python напомнит вам об этом:

magicians.py

magicians = ['alice', 'david', 'carolina']

for magician in magicians:

(1) print(magician)

Команда print в точке (1) должна иметь отступ, но здесь его нет. Когда Python ожидает увидеть блок с отступом, но не находит его, появляется сообщение с указанием номера строки:

. File "magicians.py", line 3

. .print(magician)

. . . .^

IndentationError: expected an indented block

Обычно для устранения подобных ошибок достаточно поставить отступ в строке (или строках), следующей непосредственно после команды for.

 

Пропущенные отступы в других строках

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

Например, вот что происходит, если вы забудете снабдить отступом вторую строку в цикле:

magicians = ['alice', 'david', 'carolina']

for magician in magicians:

. .print(magician.title() + ", that was a great trick!")

(1) print("I can't wait to see your next trick, " + magician.title() + ".\n")

Команда print в точке (1) должна быть снабжена отступом, но, поскольку 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.

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

 

Лишние отступы

Если вы случайно поставите отступ в строке, в которой он не нужен, Python сообщит об этом:

hello_world.py

message = "Hello Python world!"

(1) . . print(message)

Отступ команды print в точке (1) не нужен, потому что эта строка не подчинена предшествующей; Python сообщает об ошибке:

. File "hello_world.py", line 2

. .print(message)

. .^

IndentationError: unexpected indent

Чтобы избежать непредвиденных ошибок с отступами, используйте их только там, где для этого существуют конкретные причины. В тех программах, которые вы ­пишете на этой стадии изучения Python, отступы нужны только в строках действий, повторяемых для каждого элемента в цикле for.

 

Лишние отступы после цикла

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

Например, что произойдет, если случайно снабдить отступом строку с выводом завершающего приветствия для группы фокусников?

magicians = ['alice', 'david', 'carolina']

for magician in magicians:

. .print(magician.title() + ", that was a great trick!")

. .print("I can't wait to see your next trick, " + magician.title() + ".\n")

. .

(1) . .print("Thank you everyone, that was a great magic show!")

Так как строка (1) имеет отступ, сообщение будет продублировано для каждого фокусника в списке (2):

Alice, that was a great trick!

I can't wait to see your next trick, Alice.

(2)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.

(2)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.

(2)Thank you everyone, that was a great magic show!

Это еще один пример логической ошибки, наподобие описанной в разделе «Пропущенные отступы в других строках» на с. 66. Python не знает, что вы пытаетесь сделать в своем коде, поэтому он просто выполняет весь код, не нарушающий правил синтаксиса. Если действие, которое должно выполняться один раз, выполняется многократно, проверьте, нет ли лишнего отступа в соответствующей строке кода.

 

Пропущенное двоеточие

Двоеточие в конце команды for сообщает Python, что следующая строка является началом цикла.

magicians = ['alice', 'david', 'carolina']

(1) for magician in magicians

. .print(magician)

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

Упражнения

4-1. Пицца: вспомните по крайней мере три ваши любимые разновидности пиццы. Сохраните их в списке и используйте цикл for для вывода всех названий.

• Измените цикл for так, чтобы вместо простого названия пиццы выводилось сообщение, включающее это название. Таким образом, для каждого элемента должна выводиться строка с простым текстом вида «I like pepperoni pizza».

• Добавьте в конец программы (после цикла for) строку с завершающим сообщением. Таким образом, вывод должен состоять из трех (и более) строк с названиями пиццы и дополнительного сообщения, скажем, «I really love pizza!».

4-2. Животные: создайте список из трех (и более) животных, обладающих общей характеристикой. Используйте цикл for для вывода названий всех животных.

• Измените программу так, чтобы вместо простого названия выводилось сообщение, включающее это название, например «A dog would make a great pet».

• Добавьте в конец программы строку с описанием общего свойства. Например, можно вывести сообщение «Any of these animals would make a great pet!».

 

Создание числовых списков

Необходимость хранения наборов чисел возникает в программах по многим причинам. Например, в компьютерной игре могут храниться координаты каждого персонажа на экране, таблицы рекордов и т.д. В программах визуализации данных пользователь почти всегда работает с наборами чисел: температурой, расстоянием, численностью населения, широтой/долготой и другими числовыми данными.

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

 

Функция range()

Функция range() упрощает построение числовых последовательностей. Например, с ее помощью можно легко вывести серию чисел:

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)

На этот раз вывод начинается с 1 и завершается 5:

1

2

3

4

5

Если ваша программа при использовании range() выводит не тот результат, на ­который вы рассчитывали, попробуйте увеличить конечное значение на 1.

 

Использование range() для создания числового списка

Если вы хотите создать числовой список, преобразуйте результаты range() в список при помощи функции list(). Если заключить вызов range() в list(), то результат будет представлять собой список с числовыми элементами.

В примере из предыдущего раздела числовая последовательность просто выводилась на экран. Тот же набор чисел можно преобразовать в список вызовом list():

numbers = list(range(1,6))

print(numbers)

Результат:

[1, 2, 3, 4, 5]

Функция range() также может генерировать числовые последовательности, пропуская числа в заданном диапазоне. Например, построение списка четных чисел от 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 операция возведения в степень обозначается двумя звездочками (**). Один из возможных вариантов выглядит так:

squares.py

(1) squares = []

(2)for value in range(1,11):

(3) . .square = value**2

(4) . .squares.append(square)

(5)print(squares)

Сначала в точке (1) создается пустой список с именем squares. В точке (2) вы приказываете Python перебрать все значения от 1 до 10 при помощи функции range(). В цикле текущее значение возводится во вторую степень, а результат сохраняется в переменной square в точке (3). В точке (4) каждое новое значение square присоединяется к списку squares. Наконец, после завершения цикла список квадратов выводится в точке (5):

[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Чтобы сделать код более компактным, можно опустить временную переменную square и присоединять каждое новое значение прямо к списку:

squares = []

for value in range(1,11):

(1) . .squares.append(value**2)

print(squares)

Конструкция (1) выполняет ту же работу, что и строки (3) и (4) в squares.py. Каждое значение в цикле возводится во вторую степень, а затем немедленно присоединяется к списку квадратов.

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

 

Простая статистика с числовыми списками

Некоторые функции 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']

(1) print(players[0:3])

В точке (1) выводится срез списка, включающий только первых трех игроков. Вывод сохраняет структуру списка, но включает только первых трех игроков:

['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:])

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

 

Перебор содержимого среза

Если вы хотите перебрать элементы, входящие в подмножество элементов, используйте срез в цикле for. В следующем примере программа перебирает первых трех игроков и выводит их имена:

players = ['charles', 'martina', 'michael', 'florence', 'eli']

print("Here are the first three players on my team:")

(1) for player in players[:3]:

. .print(player.title())

Вместо того чтобы перебирать весь список игроков (1) , Python ограничивается первыми тремя именами:

Here are the first three players on my team:

Charles

Martina

Michael

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

 

Копирование списка

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

Чтобы скопировать список, создайте срез, включающий весь исходный список без указания первого и второго индекса ([:]). Эта конструкция создает срез, который начинается с первого элемента и завершается последним; в результате создается копия всего списка.

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

foods.py

(1) my_foods = ['pizza', 'falafel', 'carrot cake']

(2)friend_foods = my_foods[:]

print("My favorite foods are:")

print(my_foods)

print("\nMy friend's favorite foods are:")

print(friend_foods)

В точке (1) создается список блюд с именем my_foods. В точке (2) создается другой список с именем 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']

(1) friend_foods = my_foods[:]

(2)my_foods.append('cannoli')

(3)friend_foods.append('ice cream')

print("My favorite foods are:")

print(my_foods)

print("\nMy friend's favorite foods are:")

print(friend_foods)

В точке (1) исходные элементы my_foods копируются в новый список friend_foods, как было сделано в предыдущем примере. Затем в (2) каждый список добавляется новый элемент: 'cannoli' в my_foods, и 'ice cream' в friend_foods. После этого вывод двух списков наглядно показывает, что каждое блюдо находится в соответствующем списке.

My favorite foods are:

(4)['pizza', 'falafel', 'carrot cake', 'cannoli']

My friend's favorite foods are:

(5)['pizza', 'falafel', 'carrot cake', 'ice cream']

Вывод в точке (1) показывает, что элемент 'cannoli' находится в списке my_foods, а элемент 'ice cream' в этот список не входит. В точке (5) видно, что 'ice cream' входит в список friend_foods, а элемент 'cannoli' в этот список не входит. Если бы эти два списка просто совпадали, то их содержимое уже не различалось бы. Например, вот что происходит при попытке копирования списка без использования среза:

my_foods = ['pizza', 'falafel', 'carrot cake']

# This doesn't work:

(1) 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 в точке (1) , мы задаем 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. Срезы: добавьте в конец одной из программ, написанных в этой главе, фрагмент, который делает следующее.

• Выводит сообщение «The first three items in the list are:», а затем использует срез для вывода первых трех элементов из списка.

• Выводит сообщение «Three items from the middle of the list are:», а затем использует срез для вывода первых трех элементов из середины списка.

• Выводит сообщение «The last three items in the list are:», а затем использует срез для вывода последних трех элементов из списка.

4-11. Моя пицца, твоя пицца: начните с программы из упражнения 4-1. Создайте копию списка с видами пиццы, присвойте ему имя friend_pizzas. Затем сделайте следующее.

• Добавьте новую пиццу в исходный список.

• Добавьте другую пиццу в список friend_pizzas.

• Докажите, что в программе существуют два разных списка. Выведите сообщение «My favorite pizzas are:», а затем первый список в цикле for. Выведите сообщение «My friend’s favorite pizzas are:», а затем второй список в цикле for. Убедитесь в том, что каждая новая пицца находится в соответствующем списке.

4-12. Больше циклов: во всех версиях foods.py из этого раздела мы избегали использования цикла for при выводе для экономии места. Выберите версию foods.py и напишите два цикла for для вывода каждого списка.

 

Кортежи

Списки хорошо подходят для хранения наборов элементов, которые могут изменяться на протяжении жизненного цикла программы. Например, возможность модификации списков жизненно необходима при работе со списками пользователей сайта или списками персонажей игры. Однако в некоторых ситуациях требуется создать список элементов, который не может изменяться. Кортежи (tuples) предоставляют именно такую возможность. В языке Python значения, которые не могут изменяться, называются неизменяемыми (immutable), а неизменяемый список называется кортежем.

 

Определение кортежа

Кортеж выглядит как список, не считая того, что вместо квадратных скобок используются круглые скобки. После определения кортежа вы можете обращаться к его отдельным элементам по индексам точно так же, как это делается при работе со списком.

Допустим, имеется прямоугольник, который в программе всегда должен иметь строго определенные размеры. Чтобы гарантировать неизменность размеров, можно объединить размеры в кортеж:

dimensions.py

(1) dimensions = (200, 50)

(2)print(dimensions[0])

print(dimensions[1])

В точке (1) определяется кортеж dimensions, при этом вместо квадратных скобок используются круглые. В точке (2) каждый элемент кортежа выводится по отдельности с использованием того же синтаксиса, который использовался для обращения к элементу списка:

200

50

Посмотрим, что произойдет при попытке изменения одного из элементов в кортеже dimensions:

dimensions = (200, 50)

(1) dimensions[0] = 250

Код в точке (1) пытается изменить первое значение, но Python возвращает ошибку типа. По сути, так как мы пытаемся изменить кортеж, а эта операция недопустима для объектов этого типа, Python сообщает о невозможности присваивания нового значения элементу в кортеже:

Traceback (most recent call last):

File "dimensions.py", line 3, in

. .dimensions[0] = 250

TypeError: 'tuple' object does not support item assignment

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

 

Перебор всех значений в кортеже

Для перебора всех значений в кортеже используется цикл for, как и при работе со списками:

dimensions = (200, 50)

for dimension in dimensions:

. .print(dimension)

Python возвращает все элементы кортежа по аналогии с тем, как это делается со списком:

200

50

 

Замена кортежа

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

(1) dimensions = (200, 50)

print("Original dimensions:")

for dimension in dimensions:

. .print(dimension)

. .

(2)dimensions = (400, 100)

(3)print("\nModified dimensions:")

for dimension in dimensions:

. .print(dimension)

Блок в точке (1) определяет исходный кортеж и выводит исходные размеры. В точке (2) в переменной dimensions сохраняется новый кортеж, после чего в точке (3) выводятся новые размеры. На этот раз Python не выдает сообщений об ошибках, потому что замена значения переменной является допустимой операцией:

Original dimensions:

200

50

Modified dimensions:

400

100

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

Упражнения

4-13. Шведский стол: меню «шведского стола» в ресторане состоит всего из пяти пунктов. Придумайте пять простых блюд и сохраните их в кортеже.

• Используйте цикл for для вывода всех блюд, предлагаемых рестораном.

• Попробуйте изменить один из элементов и убедитесь в том, что Python отказывается вносить изменения.

• Ресторан изменяет меню, заменяя два элемента другими блюдами. Добавьте блок кода, который заменяет кортеж, и используйте цикл for для вывода каждого элемента обновленного меню.

 

Стиль программирования

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

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

 

Рекомендации по стилю

Когда кто-нибудь хочет внести изменения в язык Python, он пишет документ PEP (Python Enhancement Proposal). Одним из самых старых PEP является документ PEP 8 с рекомендациями по стилевому оформлению кода. 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 при переборе элементов списка для выполнения действий с элементами, выбранными по некоторому условию.