Изучаем Python — страница 9 из 61


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

Списки идеально подходят для хранения наборов чисел, а 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 для вывода каждого списка.

Кортежи