Практика · Спискові включення¶
Лекція: lecture.html · Тест: quiz.html · ДЗ: homework.md
Наскрізний приклад той самий, що в лекції, — кошик покупок. Головне правило цього
зошита: кожне включення ми звіряємо з еквівалентним циклом через assert. Не «схоже»,
не «майже те саме», а буквально той самий результат.
Що зробимо:
- перепишемо цикл із
appendна включення — і доведемо, що це те саме; - перетворимо список: подвоєння,
len, великі букви; - відфільтруємо через
ifу кінці — і побачимо, що список коротшає; - порівняємо фільтр із тернаром на початку, який довжину зберігає;
- зберемо словник із двох списків через
zipі перевернемо наявний словник; - розплющимо матрицю вкладеним включенням;
- розберемо
[[0] * 3 for _ in range(3)]і чому[[0] * 3] * 3зламане; - подивимось на включення з побічним ефектом і на область видимості;
- напишемо свідомо погане включення й перепишемо його циклом;
- заміряємо швидкість і познайомимось із генераторним виразом.
1 · Дані, з якими працюємо¶
Ті самі чотири структури, що й у лекції. цінник — це просто ціни списком, у тому самому
порядку, що й покупки: так із ними зручніше показувати включення.
покупки = ["хліб", "молоко", "яблука", "мед", "сіль"]
ціни = {"хліб": 28.5, "молоко": 32.0, "яблука": 19.9, "мед": 145.0, "сіль": 12.0}
кількості = [2, 1, 3, 1, 1]
цінник = [28.5, 32.0, 19.9, 145.0, 12.0]
print("покупки :", покупки)
print("цінник :", цінник)
print("кількості:", кількості)
print("позицій у кошику:", len(покупки))
покупки : ['хліб', 'молоко', 'яблука', 'мед', 'сіль'] цінник : [28.5, 32.0, 19.9, 145.0, 12.0] кількості: [2, 1, 3, 1, 1] позицій у кошику: 5
2 · Той самий алгоритм двома записами¶
Спершу пишемо звичайний цикл із append — рівно так, як робили в темі 12. Потім те саме
включенням. І одразу перевіряємо assert-ом, що результати збігаються до останнього
елемента: це головна перевірка всього зошита.
# спосіб 1 — цикл: створюємо порожній список і доповнюємо його на кожному кроці
подвоєні_циклом = []
for ціна in цінник:
подвоєні_циклом.append(ціна * 2)
# спосіб 2 — включення: описуємо результат одним виразом
подвоєні_включенням = [ціна * 2 for ціна in цінник]
print("циклом :", подвоєні_циклом)
print("включенням :", подвоєні_включенням)
# найважливіший рядок зошита: це не «схожі» списки, а буквально однакові
assert подвоєні_циклом == подвоєні_включенням, "включення дало не те, що цикл!"
print("✅ збігається: включення = той самий цикл")
циклом : [57.0, 64.0, 39.8, 290.0, 24.0] включенням : [57.0, 64.0, 39.8, 290.0, 24.0] ✅ збігається: включення = той самий цикл
Порівняймо ще й кількість рядків. Цикл витратив три (порожній список, заголовок, тіло), включення — один. Але економія рядків тут не головне: у включенні вираз стоїть першим, тому воно читається як відповідь, а не як інструкція.
цикл_рядків = 3 # подвоєні = [] · for ... · append(...)
включення_рядків = 1
print("рядків циклом :", цикл_рядків)
print("рядків включенням :", включення_рядків)
print("довжина результату однакова:", len(подвоєні_циклом), "=", len(подвоєні_включенням))
рядків циклом : 3 рядків включенням : 1 довжина результату однакова: 5 = 5
3 · Перетворення: вираз може бути будь-яким¶
У виразі не обовʼязково арифметика. Годиться виклик методу, виклик функції, звертання за індексом — усе, що повертає значення. Зверни увагу на третій приклад: на вході рядки, на виході числа. Тип результату не зобовʼязаний збігатися з типом джерела.
гучні = [назва.upper() for назва in покупки]
довжини = [len(назва) for назва in покупки]
перші_букви = [назва[0] for назва in покупки]
print("великими :", гучні)
print("довжини :", довжини)
print("перші букви:", перші_букви)
# джерело не постраждало: рядки незмінні, .upper() повертає НОВИЙ рядок
print("покупки лишились такими ж:", покупки)
великими : ['ХЛІБ', 'МОЛОКО', 'ЯБЛУКА', 'МЕД', 'СІЛЬ'] довжини : [4, 6, 6, 3, 4] перші букви: ['х', 'м', 'я', 'м', 'с'] покупки лишились такими ж: ['хліб', 'молоко', 'яблука', 'мед', 'сіль']
Перевіримо довжини циклом — і заразом переконаємось, що довжина результату дорівнює
довжині джерела. Це властивість включення без умови: один елемент на вході — один на виході.
довжини_циклом = []
for назва in покупки:
довжини_циклом.append(len(назва))
assert довжини == довжини_циклом
assert len(довжини) == len(покупки), "без умови довжина мусить зберігатись"
print("✅ довжини збігаються, елементів однаково:", len(довжини))
✅ довжини збігаються, елементів однаково: 5
4 · Фільтр: if у кінці¶
Умова в кінці вирішує, чи потрапить елемент у результат. Це прямий аналог continue
у звичайному циклі. Зверни увагу: вираз тут тривіальний (ціна), бо ми нічого не
перетворюємо — ми відбираємо.
# цикл із вкладеним if
дешеві_циклом = []
for ціна in цінник:
if ціна < 100:
дешеві_циклом.append(ціна)
# те саме включенням: умова переїхала в хвіст того самого рядка
дешеві = [ціна for ціна in цінник if ціна < 100]
print("усі ціни :", цінник, "— елементів:", len(цінник))
print("дешеві :", дешеві, "— елементів:", len(дешеві))
assert дешеві == дешеві_циклом
print("✅ фільтр включенням = фільтр циклом")
print("список СКОРОТИВСЯ на", len(цінник) - len(дешеві), "позицію — мед за 145.0 не пройшов")
усі ціни : [28.5, 32.0, 19.9, 145.0, 12.0] — елементів: 5 дешеві : [28.5, 32.0, 19.9, 12.0] — елементів: 4 ✅ фільтр включенням = фільтр циклом список СКОРОТИВСЯ на 1 позицію — мед за 145.0 не пройшов
Умова може бути якою завгодно — усі правила істинності з теми 11 працюють без змін. Крайній випадок теж корисний: якщо умову не проходить ніхто, виходить порожній список. Це не помилка.
довгі_назви = [назва for назва in покупки if len(назва) > 4]
з_буквою_м = [назва for назва in покупки if "м" in назва]
неможливе = [ціна for ціна in цінник if ціна > 1000]
print("довші за 4 букви :", довгі_назви)
print("з буквою «м» :", з_буквою_м)
print("дорожчі за 1000 :", неможливе, "— порожній список, і це законно")
довші за 4 букви : ['молоко', 'яблука'] з буквою «м» : ['молоко', 'мед'] дорожчі за 1000 : [] — порожній список, і це законно
5 · Перетворення проти фільтра: тернар на початку¶
А тепер інша задача: не «залиш дешеві», а «обріж дорогі до 100». Кількість позицій має
лишитись тією самою — ми нічого не викидаємо. Для цього потрібен тернарний вираз
a if умова else b, і стоїть він на місці виразу, тобто перед for.
зі_стелею = [ціна if ціна < 100 else 100 for ціна in цінник]
print("джерело :", цінник, "— довжина", len(цінник))
print("фільтр :", дешеві, "— довжина", len(дешеві))
print("зі стелею :", зі_стелею, "— довжина", len(зі_стелею))
# ключова різниця в одному рядку
assert len(дешеві) < len(цінник), "фільтр мусить скорочувати"
assert len(зі_стелею) == len(цінник), "тернар мусить зберігати довжину"
print("✅ фільтр скоротив, тернар зберіг довжину")
джерело : [28.5, 32.0, 19.9, 145.0, 12.0] — довжина 5 фільтр : [28.5, 32.0, 19.9, 12.0] — довжина 4 зі стелею : [28.5, 32.0, 19.9, 100, 12.0] — довжина 5 ✅ фільтр скоротив, тернар зберіг довжину
Запамʼятати легко: else буває тільки в тернарі. Якщо в тебе є else, він мусить
стояти перед for. Спроба поставити else у хвіст — це SyntaxError ще до запуску,
тому такий код ми показуємо рядком, а не виконуємо.
поганий_запис = '[ціна for ціна in цінник if ціна < 100 else 0]'
добрий_запис = '[ціна if ціна < 100 else 0 for ціна in цінник]'
print("так НЕ можна:", поганий_запис)
print(" ", "SyntaxError: expected 'else' after 'if' expression")
print("так можна :", добрий_запис)
print("результат :", [ціна if ціна < 100 else 0 for ціна in цінник])
так НЕ можна: [ціна for ціна in цінник if ціна < 100 else 0]
SyntaxError: expected 'else' after 'if' expression
так можна : [ціна if ціна < 100 else 0 for ціна in цінник]
результат : [28.5, 32.0, 19.9, 0, 12.0]
6 · Три дужки — три типи¶
Той самий вираз len(назва) дає три різні речі залежно від дужок. Візьмемо список
із повторами, щоб різниця була видима.
з_повторами = ["хліб", "мед", "сіль", "мед", "хліб"]
як_список = [len(назва) for назва in з_повторами]
як_множина = {len(назва) for назва in з_повторами}
як_словник = {назва: len(назва) for назва in з_повторами}
print("[...] ->", як_список, type(як_список).__name__, "-", len(як_список), "елементів")
print("{...} ->", як_множина, type(як_множина).__name__, "-", len(як_множина), "елементи")
print("{k: v} ->", як_словник, type(як_словник).__name__, "-", len(як_словник), "пари")
[...] -> [4, 3, 4, 3, 4] list - 5 елементів
{...} -> {3, 4} set - 2 елементи
{k: v} -> {'хліб': 4, 'мед': 3, 'сіль': 4} dict - 3 пари
Дублікати ми ніде не прибирали — це зробила сама природа типу: множина не вміє тримати два однакові елементи, а словник не вміє тримати два однакові ключі.
assert len(як_список) == 5, "список бере все підряд"
assert len(як_множина) == 2, "множина лишає лише різні значення"
assert len(як_словник) == 3, "у словнику стільки записів, скільки різних ключів"
assert як_множина == set(як_список), "множина — це ті самі значення без повторів"
print("✅ 5 -> 2 -> 3: дублікати злиплись самі, без жодного коду")
✅ 5 -> 2 -> 3: дублікати злиплись самі, без жодного коду
7 · Словник із двох списків через zip¶
Найкорисніший практичний рецепт цієї теми. Назви лежать в одному списку, кількості —
в другому; словниковим включенням вони склеюються в один запис. Після for тут стоять
два імені, бо zip віддає пари (розпакування з теми 07).
# цикл
склад_циклом = {}
for назва, скільки in zip(покупки, кількості):
склад_циклом[назва] = скільки
# включення
склад = {назва: скільки for назва, скільки in zip(покупки, кількості)}
print("склад:", склад)
assert склад == склад_циклом
assert склад == dict(zip(покупки, кількості)), "це те саме, що вбудований dict(zip(...))"
print("✅ словникове включення = цикл = dict(zip(...))")
склад: {'хліб': 2, 'молоко': 1, 'яблука': 3, 'мед': 1, 'сіль': 1}
✅ словникове включення = цикл = dict(zip(...))
Словникове включення вміє й перевертати наявний словник — міняти ключі зі значеннями. Обережно: якщо значення повторюються, після перевертання частина записів злипнеться, бо ключі мусять бути унікальні.
навпаки = {ціна: назва for назва, ціна in ціни.items()}
print("ціни :", ціни)
print("навпаки :", навпаки)
print("записів було", len(ціни), "- стало", len(навпаки), "(усі ціни різні, тому нічого не злиплось)")
# порахуємо вартість кожної позиції — теж словниковим включенням
вартість = {назва: round(ціни[назва] * скільки, 2)
for назва, скільки in склад.items()}
print("вартість:", вартість)
print("разом до сплати:", round(sum(вартість.values()), 2))
ціни : {'хліб': 28.5, 'молоко': 32.0, 'яблука': 19.9, 'мед': 145.0, 'сіль': 12.0}
навпаки : {28.5: 'хліб', 32.0: 'молоко', 19.9: 'яблука', 145.0: 'мед', 12.0: 'сіль'}
записів було 5 - стало 5 (усі ціни різні, тому нічого не злиплось)
вартість: {'хліб': 57.0, 'молоко': 32.0, 'яблука': 59.7, 'мед': 145.0, 'сіль': 12.0}
разом до сплати: 305.7
8 · Вкладене включення: розплющуємо матрицю¶
Два for в одному включенні — це рівно вкладені цикли з теми 12, у тому самому порядку:
зліва направо = зовні всередину. Поклади обидва записи поруч і прочитай — вони збігаються
слово в слово.
матриця = [[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]]
# вкладені цикли
пласкі_циклом = []
for рядок in матриця:
for значення in рядок:
пласкі_циклом.append(значення)
# те саме включенням — for-и в тому самому порядку
пласкі = [значення for рядок in матриця for значення in рядок]
print("матриця :", матриця)
print("пласкі :", пласкі)
assert пласкі == пласкі_циклом
assert len(пласкі) == 3 * 4, "три рядки по чотири значення — рівно 12 кроків"
print("✅ розплющення включенням = вкладені цикли, елементів:", len(пласкі))
матриця : [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]] пласкі : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] ✅ розплющення включенням = вкладені цикли, елементів: 12
Переставити for-и не можна: другий бере джерело з першого. Якщо поміняти їх місцями,
імʼя рядок згадається раніше, ніж зʼявиться. Спіймаємо цю помилку й надрукуємо її текст —
traceback теж навчальний матеріал.
Одна тонкість: після звичайного циклу вище імʼя рядок лишилось живим (це та сама
властивість циклу з теми 12). Воно замаскувало б помилку, тому спершу приберемо його
через del — і тоді побачимо чисту поведінку включення.
# після циклу вище імʼя «рядок» лишилось живим — прибираємо його,
# інакше воно замаскує помилку, і зламане включення випадково спрацює
del рядок
try:
# свідомо неправильний порядок: перший for просить джерело, якого ще немає
зламане = [значення for значення in рядок for рядок in матриця]
except NameError as помилка:
print("NameError:", помилка)
print("причина: перший for уже просить «рядок», а створює його лише другий for")
print("порядок for-ів у включенні той самий, що й у вкладених циклах: зовні всередину")
NameError: name 'рядок' is not defined причина: перший for уже просить «рядок», а створює його лише другий for порядок for-ів у включенні той самий, що й у вкладених циклах: зовні всередину
9 · Розгадка [[0] * 3 for _ in range(3)]¶
Це замовляння з теми 06, яке ми двічі просили просто запамʼятати. Тепер його можна
прочитати: «для кожного з трьох кроків поклади в список новий рядок [0] * 3».
Ключове слово — новий: вираз обчислюється заново на кожному кроці.
А поруч — зламаний варіант [[0] * 3] * 3, де множення копіює посилання на один
і той самий список.
правильна = [[0] * 3 for _ in range(3)]
зламана = [[0] * 3] * 3
# змінюємо одну клітинку в кожній таблиці й дивимось, що сталося
правильна[0][0] = 9
зламана[0][0] = 9
print("правильна:", правильна)
print("зламана :", зламана, "<- девʼятка зʼявилась у ВСІХ трьох рядках")
# доказ: у зламаній усі три рядки — це один і той самий обʼєкт
print("правильна — різні обʼєкти:", id(правильна[0]) != id(правильна[1]))
print("зламана — один обʼєкт :", id(зламана[0]) == id(зламана[1]))
assert правильна == [[9, 0, 0], [0, 0, 0], [0, 0, 0]]
assert зламана == [[9, 0, 0], [9, 0, 0], [9, 0, 0]]
print("✅ ось навіщо тут включення: воно щоразу створює НОВИЙ рядок")
правильна: [[9, 0, 0], [0, 0, 0], [0, 0, 0]] зламана : [[9, 0, 0], [9, 0, 0], [9, 0, 0]] <- девʼятка зʼявилась у ВСІХ трьох рядках правильна — різні обʼєкти: True зламана — один обʼєкт : True ✅ ось навіщо тут включення: воно щоразу створює НОВИЙ рядок
Підкреслення _ — це звичайне імʼя змінної, а не синтаксис. Домовленість така: якщо
значення нам не потрібне, а потрібна лише кількість повторень, називаємо змінну _.
три_привітання = ["привіт" for _ in range(3)]
номери_рядків = [номер for номер in range(3)]
print("значення не потрібне:", три_привітання)
print("значення потрібне :", номери_рядків)
значення не потрібне: ['привіт', 'привіт', 'привіт'] значення потрібне : [0, 1, 2]
10 · Включення з побічним ефектом — типова помилка¶
Класична помилка того, хто щойно вивчив включення: використати його заради дії, а не заради результату. Подивимось, що насправді збирається в такому «списку».
сміття = [print("у кошику:", назва) for назва in покупки]
print()
print("а що ж повернуло включення?", сміття)
print("тип елементів:", type(сміття[0]).__name__)
assert сміття == [None] * len(покупки), "print нічого не повертає, тобто повертає None"
print("✅ ми виділили памʼять під 5 None і одразу їх викинули")
у кошику: хліб у кошику: молоко у кошику: яблука у кошику: мед у кошику: сіль а що ж повернуло включення? [None, None, None, None, None] тип елементів: NoneType ✅ ми виділили памʼять під 5 None і одразу їх викинули
Правильно так: потрібна дія — пиши звичайний for. Дужки [...] обіцяють читачеві,
що тут будують список, і цю обіцянку не можна порушувати.
for назва in покупки:
print("у кошику:", назва)
print()
print("той самий вивід, але без списку з пʼятьох None")
у кошику: хліб у кошику: молоко у кошику: яблука у кошику: мед у кошику: сіль той самий вивід, але без списку з пʼятьох None
11 · Область видимості: змінна не тече назовні¶
У звичайному циклі змінна після завершення лишається живою — ми бачили це в темі 12. У включенні вона зникає разом із дужками. Перевіримо обидва випадки поспіль.
# цикл: змінна лишається живою й тримає ОСТАННЄ значення
for перевірка in range(3):
pass
print("після циклу перевірка =", перевірка)
# включення: власна область видимості
квадрати = [ч ** 2 for ч in range(3)]
print("квадрати =", квадрати)
try:
print(ч)
except NameError as помилка:
print("NameError:", помилка)
print("імʼя «ч» жило тільки всередині квадратних дужок")
після циклу перевірка = 2 квадрати = [0, 1, 4] NameError: name 'ч' is not defined імʼя «ч» жило тільки всередині квадратних дужок
Практична користь від цього — включення не затирає змінну, яка вже була в тебе під тим самим іменем. У Python 2 було інакше: змінна витікала назовні, і це вважали помилкою дизайну.
ціна = "моя важлива змінна"
дрібниця = [ціна * 2 for ціна in цінник] # усередині ціна — число
print("усередині включення ціна була числом:", дрібниця)
print("а зовні вона лишилась недоторканою :", ціна)
assert ціна == "моя важлива змінна", "включення не мусить затирати зовнішнє імʼя"
print("✅ зовнішнє імʼя вціліло")
усередині включення ціна була числом: [57.0, 64.0, 39.8, 290.0, 24.0] а зовні вона лишилась недоторканою : моя важлива змінна ✅ зовнішнє імʼя вціліло
12 · Свідомо погане включення — і як його переписати¶
Тепер найважливіша вправа теми. Напишемо включення, яке технічно правильне, а читати його
неможливо: два for, дві умови, виклик функції й розпакування всередині. Порахуємо
«рухомі частини» — for-и, if-и, виклики й розпакування — і подивимось на довжину рядка.
чеки = [[(28.5, 2), (145.0, 1)],
[(19.9, 3), (12.0, 1)],
[(32.0, 2), (99.0, 4)]]
# ⚠️ так писати не треба — це приклад того, чого варто уникати
монстр = [round(ц * к * 1.2, 2) for чек in чеки for ц, к in чек if ц < 100 if к > 1]
рядок = '[round(ц * к * 1.2, 2) for чек in чеки for ц, к in чек if ц < 100 if к > 1]'
print("результат:", монстр)
print("довжина рядка:", len(рядок), "символів (межа читабельності — близько 72)")
print("рухомих частин: 2 for + 2 if + 1 виклик + 1 розпакування = 6")
результат: [68.4, 71.64, 76.8, 475.2] довжина рядка: 75 символів (межа читабельності — близько 72) рухомих частин: 2 for + 2 if + 1 виклик + 1 розпакування = 6
А тепер той самий алгоритм звичайними циклами. Він довший у рядках — шість замість одного, — але жоден із цих рядків не потребує паузи на розуміння. Саме це й означає правило «не читається вголос за один подих — пиши цикл».
результат = []
for чек in чеки:
for ціна, скільки in чек:
if ціна < 100 and скільки > 1:
# націнка 20%, округлення до копійок — рахуємо окремим зрозумілим рядком
результат.append(round(ціна * скільки * 1.2, 2))
print("циклом :", результат, "— рядків коду: 6")
print("включенням:", монстр, "— рядків коду: 1")
assert результат == монстр, "переписування циклом мусить дати той самий результат"
print("✅ результат ідентичний — різниця лише в тому, що з цього можна прочитати")
циклом : [68.4, 71.64, 76.8, 475.2] — рядків коду: 6 включенням: [68.4, 71.64, 76.8, 475.2] — рядків коду: 1 ✅ результат ідентичний — різниця лише в тому, що з цього можна прочитати
Найкращий компроміс — не «все в один рядок» і не «все циклом», а проміжна змінна. Розбиваємо одне складне включення на два простих, кожне з яких має власне імʼя.
# крок 1: витягли всі пари з усіх чеків — одна проста думка
усі_пари = [пара for чек in чеки for пара in чек]
# крок 2: відібрали потрібні — друга проста думка
потрібні = [(ціна, скільки) for ціна, скільки in усі_пари if ціна < 100 and скільки > 1]
# крок 3: порахували — третя проста думка
з_націнкою = [round(ціна * скільки * 1.2, 2) for ціна, скільки in потрібні]
print("усіх пар :", len(усі_пари))
print("потрібних :", потрібні)
print("з націнкою :", з_націнкою)
assert з_націнкою == монстр
print("✅ три читабельних включення = один монстр, і кожне має імʼя, що пояснює його сенс")
усіх пар : 6 потрібних : [(28.5, 2), (19.9, 3), (32.0, 2), (99.0, 4)] з націнкою : [68.4, 71.64, 76.8, 475.2] ✅ три читабельних включення = один монстр, і кожне має імʼя, що пояснює його сенс
13 · Чому включення трохи швидше¶
Включення справді швидше за цикл із append, але йдеться про десятки відсотків, а не про
рази. Причина конкретна: у циклі Python на кожному елементі шукає атрибут append
і викликає його як звичайну функцію, а у включенні для цього є окрема інструкція
LIST_APPEND — без пошуку імені й без виклику.
Заміряємо самі. Числа на твоїй машині будуть іншими, а співвідношення — приблизно таким же.
import timeit
підготовка = "дані = list(range(50_000))"
час_включення = timeit.timeit("[x * 2 for x in дані]", setup=підготовка, number=100)
час_циклу = timeit.timeit(
"результат = []\nfor x in дані:\n результат.append(x * 2)",
setup=підготовка, number=100)
print("включення:", round(час_включення, 3), "с на 100 повторень")
print("цикл :", round(час_циклу, 3), "с на 100 повторень")
print("включення швидше приблизно в", round(час_циклу / час_включення, 2), "раза")
print()
print("висновок: різниця є, але вона НЕ причина переписувати цикли —")
print("читабельність важливіша за десятки відсотків на коротких списках")
включення: 0.274 с на 100 повторень цикл : 0.315 с на 100 повторень включення швидше приблизно в 1.15 раза висновок: різниця є, але вона НЕ причина переписувати цикли — читабельність важливіша за десятки відсотків на коротких списках
14 · Круглі дужки: генераторний вираз¶
Заміни квадратні дужки на круглі — і замість списку отримаєш генераторний вираз.
Він нічого не будує одразу, а віддає значення по одному на вимогу — рівно як range
із теми 12. Повна історія буде в темі 30, а зараз подивимось на витрати памʼяті.
import sys
список = [x * 2 for x in range(1_000_000)]
генератор = (x * 2 for x in range(1_000_000))
print("тип із квадратними дужками:", type(список).__name__)
print("тип із круглими дужками :", type(генератор).__name__)
print("список :", sys.getsizeof(список), "байтів")
print("генератор:", sys.getsizeof(генератор), "байтів")
print()
# найчастіше генератор пишуть просто всередині виклику функції — і дужки не дублюють
разом = sum(ціна * скільки for ціна, скільки in zip(цінник, кількості))
print("сума без проміжного списку:", round(разом, 2))
assert round(разом, 2) == round(sum([ц * к for ц, к in zip(цінник, кількості)]), 2)
print("✅ результат той самий, а проміжного списку не існувало")
тип із квадратними дужками: list тип із круглими дужками : generator список : 8448728 байтів генератор: 200 байтів сума без проміжного списку: 305.7 ✅ результат той самий, а проміжного списку не існувало
Що далі — три рівні¶
🟢 Рівень 1¶
Візьми свій список покупок (щонайменше шість позицій) і словник цін до нього. Одним
включенням побудуй список назв дорожчих за середню ціну, другим — словник
«назва → ціна з націнкою 15%».
Зроблено, якщо обидва результати проходять assert проти написаних тобою ж циклів,
а перше включення на порожньому списку дає [] без помилки.
🟡 Рівень 2¶
Дано список слів. Одним включенням побудуй словник «слово → його довжина», другим —
множину різних довжин. Поясни письмово, чому в словнику може виявитись менше записів,
ніж слів у списку.
Зроблено, якщо виконується assert set(словник.values()) == множина_довжин і ти
навів приклад списку, на якому довжина словника менша за довжину списку.
🔴 Рівень 3¶
Візьми монстра з розділу 12 і напиши три різні коректні версії того самого
обчислення: (1) одним включенням, (2) звичайними циклами, (3) двома-трьома простими
включеннями з проміжними змінними. Заміряй timeit-ом усі три.
Зроблено, якщо всі три дають однаковий результат за assert, ти навів довжину рядка
кожної версії в символах і письмово обґрунтував, яку з них узяв би в реальний проєкт — і чому.