Python: от данных до своей сводки Урок 5 из 56
Список и кортеж: чек, который умеет считать себя сам
Пятый урок курса по Python. Цены в списке: индексы, срезы, изменение, сумма и максимум — без единого цикла. Измерена разница, на которой спотыкаются все: sorted() возвращает новый список, .sort() меняет исходный и отдаёт None. И кортеж, который меняться отказывается: TypeError.
Зачем это нужно
Одна цена — это переменная. Чек — это уже набор цен, и держать их в отдельных переменных price_1, price_2 невозможно: в реальном файле строк тысячи.
Для набора в Python есть список. Он умеет больше, чем кажется: сложить всё, найти наибольшее, отсортировать — и всё это до того, как вы научитесь писать циклы.
Сразу целиком
Файл tizim.py. Запуск: python tizim.py из окружения.
Программа длинная — шестьдесят строк. Обязательное в ней — список, срезы, sum, max и сортировка. itemgetter и кортежи идут сверх того: их достаточно узнать в лицо, а в задании они не понадобятся.
"""Урок 5: список и кортеж — на ценах из одного чека.
Циклов ещё не было, и они здесь не нужны: встроенные функции обрабатывают
список целиком, а сортировать он умеет и сам.
"""
from operator import itemgetter
# Список — упорядоченный набор значений. Порядок держится тот, в котором
# сложили: это не «множество», где порядка нет.
prices = [260, 620, 1890, 90]
names = ["хлеб", "молоко", "масло", "соль"]
print("== список целиком и по частям")
print("все цены: ", prices)
print("сколько штук: ", len(prices))
print("первая: ", prices[0])
print("последняя: ", prices[-1])
print("первые три: ", prices[:3])
print("со второй: ", prices[1:])
print("в обратном: ", prices[::-1])
print()
print("== список можно менять")
prices.append(310)
print("после append: ", prices)
prices[0] = 280
print("подняли первую:", prices)
last = prices.pop()
print("pop вернул: ", last, "| осталось:", prices)
print()
print("== считаем, не написав ни одного цикла")
print("сумма чека: ", sum(prices))
print("самое дорогое: ", max(prices))
print("самое дешёвое: ", min(prices))
print("средняя цена: ", sum(prices) / len(prices))
print()
print("== две сортировки, и разница между ними важна")
print("до сортировки: ", prices)
print("sorted() дал: ", sorted(prices), "| исходный:", prices)
prices.sort()
print("после .sort(): ", prices, "← изменился сам список")
print("по длине слова:", sorted(names, key=len))
print("по алфавиту: ", sorted(names, key=str.lower))
print()
print("== кортеж: то, что не меняют")
item = ("хлеб", 280)
print("запись:", item, type(item))
try:
item[1] = 300
except TypeError as err:
print("item[1] = 300 →", type(err).__name__, "—", err)
print()
print("== распаковка")
name, price = item
print("имя:", name, "| цена:", price)
a, b = 1, 2
a, b = b, a
print("обмен без третьей переменной: a =", a, ", b =", b)
print()
print("== список кортежей — это уже таблица")
check = [("хлеб", 280), ("молоко", 620), ("масло", 1890), ("соль", 90)]
print("по цене: ", sorted(check, key=itemgetter(1)))
print("по имени: ", sorted(check, key=itemgetter(0)))
print("самая дорогая строка:", max(check, key=itemgetter(1)))
Вывод:
== список целиком и по частям
все цены: [260, 620, 1890, 90]
сколько штук: 4
первая: 260
последняя: 90
первые три: [260, 620, 1890]
со второй: [620, 1890, 90]
в обратном: [90, 1890, 620, 260]
== список можно менять
после append: [260, 620, 1890, 90, 310]
подняли первую: [280, 620, 1890, 90, 310]
pop вернул: 310 | осталось: [280, 620, 1890, 90]
== считаем, не написав ни одного цикла
сумма чека: 2880
самое дорогое: 1890
самое дешёвое: 90
средняя цена: 720.0
== две сортировки, и разница между ними важна
до сортировки: [280, 620, 1890, 90]
sorted() дал: [90, 280, 620, 1890] | исходный: [280, 620, 1890, 90]
после .sort(): [90, 280, 620, 1890] ← изменился сам список
по длине слова: ['хлеб', 'соль', 'масло', 'молоко']
по алфавиту: ['масло', 'молоко', 'соль', 'хлеб']
== кортеж: то, что не меняют
запись: ('хлеб', 280) <class 'tuple'>
item[1] = 300 → TypeError — 'tuple' object does not support item assignment
== распаковка
имя: хлеб | цена: 280
обмен без третьей переменной: a = 2 , b = 1
== список кортежей — это уже таблица
по цене: [('соль', 90), ('хлеб', 280), ('молоко', 620), ('масло', 1890)]
по имени: [('масло', 1890), ('молоко', 620), ('соль', 90), ('хлеб', 280)]
самая дорогая строка: ('масло', 1890)
Разбор
Список: порядок и номера
prices = [260, 620, 1890, 90]
Квадратные скобки, значения через запятую. Порядок сохраняется тот, в котором положили, и у каждого значения есть номер — индекс.
Считают с нуля: prices[0] — первое. Отрицательный индекс считает с конца: prices[-1] — последнее, и это удобнее, чем prices[len(prices) - 1].
Срез: кусок списка
prices[:3] — первые три. prices[1:] — начиная со второго. prices[::-1] — в обратном порядке.
Правило одно: левая граница включается, правая — нет. prices[1:3] даёт элементы с номерами 1 и 2, но не 3. Так сделано, чтобы prices[:3] и prices[3:] вместе давали целый список без перекрытия и без дырки.
Срез возвращает новый список; исходный остаётся как был.
Образ. Отрезать кусок ленты по меткам. Метка «3» — это черта между третьим и четвёртым, а не сам третий. Поэтому от 1 до 3 — два деления, а не три.
Список можно менять
append добавляет в конец, pop снимает с конца и возвращает снятое, а присваивание по индексу заменяет значение:
после append: [260, 620, 1890, 90, 310]
подняли первую: [280, 620, 1890, 90, 310]
pop вернул: 310 | осталось: [280, 620, 1890, 90]
Это отличает список от строки из прошлого урока: строка неизменяема, список — наоборот.
Считаем без циклов
сумма чека: 2880
самое дорогое: 1890
самое дешёвое: 90
средняя цена: 720.0
sum, max, min и len — встроенные функции, они принимают список целиком. Средняя — это sum(prices) / len(prices), и в четвёртом уроке мы уже видели, почему здесь получается 720.0, а не 720.
Запомните это место: когда в модуле про pandas мы будем считать то же самое по таблице в миллион строк, команда будет выглядеть почти так же.
sorted() и .sort() — разные вещи
Самая частая ошибка новичка:
sorted() дал: [90, 280, 620, 1890] | исходный: [280, 620, 1890, 90]
после .sort(): [90, 280, 620, 1890] ← изменился сам список
sorted(prices) возвращает новый отсортированный список, а исходный не трогает. prices.sort() сортирует на месте и возвращает None — поэтому prices = prices.sort() уничтожит ваши данные и оставит пустоту. Это классическая ловушка, и она стоит того, чтобы прочитать абзац дважды.
Сортировать можно не только по значению. key= принимает функцию, которой сортировщик пропускает каждый элемент:
sorted(names, key=len)— по длине слова;sorted(names, key=str.lower)— по алфавиту, не различая заглавные;sorted(check, key=itemgetter(1))— по второму элементу пары, то есть по цене.
itemgetter из модуля operator — это «взять элемент номер такой-то». Свою функцию для key= мы напишем в уроке про функции.
Кортеж: набор, который не меняют
item = ("хлеб", 280)
Круглые скобки вместо квадратных — и получается кортеж. Он почти как список, но изменить его нельзя:
item[1] = 300 → TypeError — 'tuple' object does not support item assignment
Кортеж на самом деле делают не скобки, а запятая. Скобки только группируют, и в этом легко убедиться:
type((280)) → int — просто число в скобках
type((280,)) → tuple — запятая делает кортеж
pair = 280, 1 → tuple — скобки вообще не нужны
Отсюда правило, на котором спотыкаются все: кортеж из одного элемента пишут с висящей запятой — one = ("хлеб",). Без неё это просто строка в скобках.
Зачем такое нужно? Кортеж — это запись, а не набор однотипных значений. ("хлеб", 280) — строка чека: название и цена, у каждой позиции свой смысл. Список из четырёх цен — четыре одинаковых по смыслу числа. «Одинаковое по смыслу — список, разное — кортеж» — это не правило языка, а ориентир: язык разрешает и список из разного, и кортеж из одинакового.
Распаковка
name, price = item
Слева столько имён, сколько элементов справа, — и каждое получает своё. Это читается лучше, чем item[0] и item[1], и ошибиться номером негде.
Отсюда же знаменитый обмен значениями без третьей переменной:
a, b = b, a
Справа сначала собирается кортеж (b, a), потом он распаковывается в левую часть. Ни временных переменных, ни путаницы.
Список кортежей — это уже таблица
по цене: [('соль', 90), ('хлеб', 280), ('молоко', 620), ('масло', 1890)]
Каждый элемент — строка с полями, весь список — таблица. Так выглядят данные в половине задач до того, как появится pandas: строки в списке, поля в кортежах.
Карта урока
Скажите своими словами
Не подглядывая, ответьте вслух или на бумаге. Ответы — в конце урока.
- Почему
prices[1:3]даёт два элемента, а не три? - Чем
sorted(prices)отличается отprices.sort()и что вернёт второй? - Когда данные кладут в кортеж, а не в список?
Разминка
Три коротких шага перед заданием: предсказать, дописать, починить. Ответы — в конце урока, но сначала ответьте сами.
1. Предскажите. Что напечатает эта строка?
prices = [260, 620, 1890]
print(prices[0], prices[-1], len(prices), prices[:2])
2. Заполните пропуск. Вместо ... посчитайте среднюю цену — без цикла, циклов ещё не было.
prices = [260, 620, 1890, 90]
print("средняя:", ...)
3. Почините. Программа падает. Прочитайте ошибку и достаньте последнюю цену.
prices = [260, 620, 1890]
print(prices[3])
Задание
Обязательное. Дано:
prices = [260, 620, 1890, 90, 310]
names = ["хлеб", "молоко", "масло", "соль", "яйца"]
Напечатайте: сколько позиций, первую и последнюю цену; сумму чека; самое дорогое и самое дешёвое; среднюю цену с двумя знаками; первые три цены срезом; список по возрастанию — и следом исходный, чтобы было видно, что он не изменился; первые два названия срезом. Циклов не пишите: всё это делают встроенные функции.
Ожидаемый вывод:
позиций: 5 | первая: 260 | последняя: 310
сумма чека: 3170
самое дорогое: 1890 | самое дешёвое: 90
средняя цена: 634.00
три первые: [260, 620, 1890]
по возрастанию: [90, 260, 310, 620, 1890]
исходный не тронут: [260, 620, 1890, 90, 310]
названия: ['хлеб', 'молоко'] …
Готово, когда: вывод совпадает построчно; порядок в prices после всей работы прежний — значит, взяли sorted(), а не .sort(); последняя цена взята через -1, а не через len(prices) - 1.
На своих данных. Соберите свой чек: список цен и список названий. Напечатайте сумму, самую дорогую и самую дешёвую цену, среднюю цену с двумя знаками после запятой и список цен, отсортированный по возрастанию, — не меняя исходный список.
По желанию.
- Сделайте список кортежей
(название, цена)и отсортируйте его по цене, а потом по названию. - Проверьте, что делает
prices = prices.sort(), и объясните результат. - Возьмите срез
prices[::2]— каждый второй элемент. Догадайтесь, что означает третье число в срезе, и проверьте догадку.
Куда это встанет в проекте
В первом уроке мы получили от Всемирного банка ряд значений по годам и напечатали его как есть. Теперь понятно, чем он был: списком. Дальше мы научимся связывать год и значение — это словарь, следующий урок.
Долги. Сумму чека по списку кортежей мы посчитать пока не можем: для этого нужно пройти по списку, а циклы — через три урока. И мы всё ещё держим названия и цены в двух разных списках, надеясь, что порядок совпадёт; словарь эту надежду заменит связью.
Ответы
Показать ответы
На вопросы
- Потому что правая граница среза не включается: берутся элементы с номерами 1 и 2. Так
prices[:3]иprices[3:]вместе дают целый список без перекрытия. sorted()возвращает новый список и не трогает исходный;.sort()сортирует исходный на месте и возвращаетNone. Поэтомуprices = prices.sort()оставит вместо данных пустоту.- Когда значения разные по смыслу и вместе образуют одну запись — например, название и цена. Список берут для набора однотипных значений, которые можно добавлять и удалять.
К разминке
260 1890 3 [260, 620]. Счёт с нуля,-1— последний элемент, срез[:2]берёт два первых и не включает второй индекс.
260 1890 3 [260, 620]
sum(prices) / len(prices). Обе функции берут список целиком, поэтому средняя цена считается одной строкой.
prices = [260, 620, 1890, 90]
print("средняя:", sum(prices) / len(prices))
средняя: 715.0
IndexError: list index out of range. В списке три элемента, значит индексы0,1,2, а3— уже за краем. Последний берут через-1, и тогда номер не придётся пересчитывать при каждом изменении списка:
prices = [260, 620, 1890]
print(prices[-1])
1890
Источники
Если вы нашли ошибку или опечатку в тексте статьи, то сообщите нам об этом
Комментарии (0)
Войдите, чтобы оставить комментарий →
Пока нет комментариев. Будьте первым.