Урок 03

Функции (functions) и коллекции (collections)

Организация кода и структуры данных «ключ-значение»

Что прошли

Урок 2 - циклы (loops) и списки (lists)

  • while и for + range()
  • break и continue
  • Списки и индексация
  • Срезы и шаги
  • Методы списка
  • Перебор коллекций

Урок 1, напоминание

Переменные и типы

Каждое значение имеет тип. Python определяет его автоматически.

name = "Алиса" age = 30 price = 19.99 is_admin = True print(type(age)) print(type(is_admin))
<class 'int'> <class 'bool'>

Урок 1, напоминание

Ввод и преобразование

input() всегда возвращает строку. Для чисел нужно int() или float().

# пользователь вводит: 5 n = int(input("Число: ")) print(n * 2)
Число: 5 10

Урок 1, напоминание

Условия if / elif / else

Разные ветви для разных случаев. f-строка вставляет значения в текст.

n = 5 if n > 0: print(f"{n}: положительное") elif n < 0: print(f"{n}: отрицательное") else: print("ноль")
5: положительное

Урок 2, напоминание

Цикл for и range

Повторение известное число раз. range(1, 6) даёт числа 1..5 (без 6).

for i in range(1, 6): print(i, end=" ")
1 2 3 4 5

В реальном мире

Цикл for на практике

Не учебный пример: автоматизация в цикле проверяет, что сервис ожил после деплоя.

for-loop polling health endpoint

for i in 1 2 3 4 5; do ... done - на этом держится мониторинг, retry-логика, тесты миграций и сотни других вещей.

Урок 2, напоминание

Списки и срезы

Упорядоченная коллекция. Индексация с нуля. Срезы через двоеточие.

fruits = ["яблоко", "груша", "слива", "вишня"] print(fruits[0]) print(fruits[-1]) print(fruits[1:3])
яблоко вишня ['груша', 'слива']

Урок 2, напоминание

break и continue

continue: пропустить итерацию. break: выйти из цикла.

for x in range(10): if x == 3: continue if x == 7: break print(x, end=" ")
0 1 2 4 5 6

Сегодня

Функции (functions) и коллекции (collections)

кортежи (tuple)
множества (set)
словари (dict)
def · return
*args · **kwargs
scope · lambda

Что это?

Кортежи (tuple)

Упорядоченная коллекция, которая не меняется после создания. Используется для фиксированных наборов: координаты (x, y), цвет RGB, дата (год, месяц, день). Python возвращает кортеж, когда функция отдаёт несколько значений сразу.

0"Москва"
1"Ташкент"
2"Бишкек"
capitals = ("Москва", "Ташкент", "Бишкек") point = (10, 20) single = (42,) # запятая обязательна!

Зачем нужны?

Кортеж vs список

Зачем брать кортеж, если есть список? Неизменяемость - это не недостаток, а защита.

List · []

Изменяемый

  • • можно добавлять и удалять
  • • подходит для растущих данных
  • • чуть медленнее
Tuple · ()

Неизменяемый

  • • защита от случайных правок
  • • ключ словаря, элемент set
  • • чуть быстрее и легче
Правило выбора

Набор фиксирован (координаты, RGB, дата) → кортеж. Коллекция меняется → список.

Пример 1

Доступ по индексу

Предскажите результат. Нажмите → для ответа.

capitals = ("Москва", "Ташкент", "Бишкек") print(capitals[0])
Москва

Индексация начинается с нуля, как у списка. capitals[-1] - последний элемент.

Пример 2

Длина кортежа

Предскажите результат. Нажмите → для ответа.

capitals = ("Москва", "Ташкент", "Бишкек") print(len(capitals))
3

len() работает с любой коллекцией: tuple, list, set, dict, str.

Пример 3

Попытка изменить

Что произойдёт, если попробовать переписать элемент? Нажмите → для ответа.

capitals = ("Москва", "Ташкент", "Бишкек") capitals[0] = "X"
TypeError: 'tuple' object does not support item assignment

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

Что это?

Множества (set)

Структура из математики: коллекция уникальных элементов без определённого порядка. Дубликаты Python убирает автоматически. Главная суперспособность - проверка x in set за O(1), то есть мгновенно даже на миллионах элементов.

Set visualization
tags = {"python", "sql", "python", "git"} empty = set() # внимание: {} - это dict!

Мотивация

Зачем нужны множества

Скорость поиска · дедупликация · алгебра множеств

01

Дедупликация

Убрать повторы одной строкой: list(set(items)).

02

Быстрый поиск

x in set - O(1), а не O(n) как у списка.

03

Алгебра множеств

Пересечение, объединение, разность - одна строка вместо циклов.

# реально: A/B-тест - кто видел и старую, и новую версию? test_a = set(users_in_old_flow) # 50 000 ID test_b = set(users_in_new_flow) # 50 000 ID overlap = test_a & test_b # пересечение only_in_b = test_b - test_a # чистая разница print(f"Видели обе версии: {len(overlap)}") print(f"Только новая: {len(only_in_b)}")
Видели обе версии: 1247 Только новая: 48753

Пример 1

Автоматическая дедупликация

Что Python сделает с дубликатом "python"? Нажмите → для ответа.

tags = {"python", "sql", "python", "git"} print(tags)
{'python', 'sql', 'git'}

Второй "python" исчез - set молча убрал дубликат. Порядок не гарантирован.

Пример 2

Длина после дедупликации

Мы написали 4 элемента - сколько окажется? Нажмите → для ответа.

tags = {"python", "sql", "python", "git"} print(len(tags))
3

Не 4, а 3 - set считает только уникальные. Удобный способ узнать «сколько разных значений в списке».

Пример 3

Проверка принадлежности

Что вернёт x in set? Нажмите → для ответа.

tags = {"python", "sql", "git"} print("python" in tags) print("java" in tags)
True False

Поиск в set - это O(1). В списке тот же in был бы O(n) - для миллиона элементов разница огромная.

Операции

Операции над множествами

Операция Действие Пример
a | b Объединение {1,2,3} | {3,4} → {1,2,3,4}
a & b Пересечение {1,2,3} & {2,3,4} → {2,3}
a - b Разность {1,2,3} - {2,3} → {1}
a ^ b Симметричная разность {1,2,3} ^ {2,3,4} → {1,4}
Лайфхак

Удаление дубликатов из списка: list(set(items)) - но порядок теряется.

Что это?

Словари (dict)

Связь между ключом (имя, ID, дата) и значением (любой объект). Доступ по ключу - за O(1). Это главная коллекция Python: JSON-ответы, конфиги, кеши, параметры функций - всё словари под капотом.

ключзначение
"name"
"Алиса"
"age"
30
"city"
"Ташкент"
user = {"name": "Алиса", "age": 30, "city": "Ташкент"}

Мотивация

Зачем нужны словари

Словарь - главная коллекция Python. JSON, конфиги, API, кэши - всё под капотом словари.

01

Поиск по имени

Записная книжка: имя → телефон. Доступ O(1) вместо перебора.

02

JSON ↔ dict

Все API-ответы, конфиги, сохранения - это словари в обёртке JSON.

03

Замена if-elif

10 веток if-elif заменяются на одну строку d[key].

# реально: парсинг ответа от API response = requests.get("https://api.bank.uz/user/42").json() # response - это уже dict name = response["full_name"] # обязательное поле email = response.get("email", "-") # может не быть balance = response.get("balance", 0) # default 0 print(f"{name}: {balance} сум")
Алиша Каримова: 1250000 сум

Пример 1

Доступ по ключу

Предскажите результат. Нажмите → для ответа.

user = {"name": "Алиса", "age": 30} print(user["name"])
Алиса

Квадратные скобки с ключом, как у списка, но имя вместо номера: d["name"].

Пример 2

Сколько пар в словаре

Предскажите результат. Нажмите → для ответа.

user = {"name": "Алиса", "age": 30, "city": "Ташкент"} print(len(user))
3

len(d) - число пар (ключ, значение). Не число ключей или значений - это одно и то же.

Пример 3

Проверка наличия ключа

Что проверяет in - ключ или значение? Нажмите → для ответа.

user = {"name": "Алиса", "age": 30} print("age" in user) print("Алиса" in user)
True False

Только ключи! "Алиса" - значение, его поиск через in user.values().

Доступ

Чтение по ключу

user = {"name": "Алиса", "age": 30}

Кликните на выражение - увидите результат:

// клик по любому выражению
Внимание

d[k] на отсутствующем ключе вызовет KeyError. .get() вернёт None или значение по умолчанию.

Перебор

Итерация по словарю

scores = {"Аня": 85, "Боря": 72, "Вика": 91} for k in scores: print(k) # только ключи for v in scores.values(): print(v) # только значения for k, v in scores.items(): print(f"{k}: {v}")
Аня: 85 Боря: 72 Вика: 91

Вопрос

Какая коллекция подойдёт?

Хранить пары «город → население» с быстрым поиском по городу

Alist - упорядоченный список
Btuple - неизменяемый
Cset - уникальные элементы
Ddict - пары ключ-значение

Подумайте минуту →

Ответ

D - словарь (dict)

«Город → население» - это ровно тот сценарий, для которого создан словарь.

cities = { "Ташкент": 2_900_000, "Самарканд": 530_000, "Бухара": 280_000, } print(cities["Ташкент"]) # O(1) - мгновенно
2900000

list искал бы запись за O(n). set хранит только сами значения, без связи. tuple - неизменяемый. dict - единственный, кто реально создан для key → value.

Шпаргалка

Коллекции - когда что

Тип Изменяемый Упорядоченный Дубликаты Главный сценарий
list [] да да да растущая последовательность
tuple () нет да да фиксированный набор
set {} да нет нет уникальные + быстрый поиск
dict {k:v} да да (с 3.7) ключи уникальны поиск по имени/ключу
Шорткат для выбора

«Поиск по имени?» → dict. «Уникальные значения, быстрый in?» → set. «Меняется?» → list. «Не меняется?» → tuple.

Половина пути

Перерыв

5–10 минут

☕ Кофе, разминка, вопросы - встретимся снова с функциями

Мотивация

Зачем нужны функции

Без функций код на 10 000 строк превращается в ад. Функция - именованный блок кода с собственным миром.

01

Повтор (DRY)

Описали логику один раз - используем тысячу. Поправили в одном месте - везде.

02

Имя

Самодокументируемый код: calc_tax(...) понятнее 5 строк формулы.

03

Изоляция

Свой scope. Локальные переменные не «протекают» наружу. Снаружи нельзя сломать.

# реальный пример: расчёт цены со скидкой, # используется в корзине, инвойсе, отчёте, чекауте... def final_price(price, discount=0, tax=0.12): return (price * (1 - discount)) * (1 + tax) cart_total = final_price(100, discount=0.10) invoice_sum = final_price(250) vip_offer = final_price(500, discount=0.25)
cart = 100.80 invoice = 280.00 vip = 420.00

Без функции vs с функцией

Одно правило вместо 50 копий

Слева - ад дублирования. Справа - переиспользуемый код.

без функции

Каждый заказ - копипаст

# Заказ 1 p1 = 100 * 1.1 + 50 print(f"Заказ 1: {p1}") # Заказ 2 p2 = 250 * 1.1 + 50 print(f"Заказ 2: {p2}") # Заказ 3 p3 = 80 * 1.1 + 50 print(f"Заказ 3: {p3}") # ... ещё 47 заказов
с функцией

Описали раз, вызвали 50

def order_total(price): return price * 1.1 + 50 orders = [100, 250, 80, 500, 30] for i, p in enumerate(orders, 1): print(f"Заказ {i}: {order_total(p)}")
Что меняется при новом правиле

Изменился налог? В левом коде - 50 правок, легко пропустить. В правом - одна строка.

Синтаксис

Синтаксис: def

Объявление функции - описание, как её вызвать

def greet(name): """Приветствует пользователя.""" message = f"Привет, {name}!" return message # Вызов print(greet("Алиса")) # Привет, Алиса!
"Алиса"
greet()
"Привет, Алиса!"

Шаг за шагом

Как работает вызов

Нажмите «Запустить» - каждый шаг проявится по очереди

def square(n): result = n * n return result x = square(4)
Шаг Действие Значение
1вызов square(4)n = 4
2n * n16
3result = 16result = 16
4return result→ 16
5x = 16x = 16

return

Возврат значения

def divmod_safe(a, b): if b == 0: return None return a // b, a % b q, r = divmod_safe(17, 5) print(q, r)
3 2
Запомнить

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

Опционально

Параметры по умолчанию

Введите имя и приветствие - оставьте поле пустым, сработает значение по умолчанию

def greet(name, greeting="Привет"): return f"{greeting}, {name}!"
Привет, Аня!

Параметры со значением по умолчанию идут после обычных в сигнатуре функции.

Ловушка #1

Изменяемые значения по умолчанию

Default вычисляется один раз - при объявлении функции, а не при каждом вызове

опасно

Один список на всех вызовов

def add(item, cart=[]): cart.append(item) return cart print(add("яблоко")) # ["яблоко"] print(add("груша")) # ["яблоко", "груша"] ⚠
правильно

Новый список каждый раз

def add(item, cart=None): if cart is None: cart = [] cart.append(item) return cart
Аналогия

«Один блокнот на столе учителя» vs «новый чистый лист каждому ученику». Никогда не используйте [], {}, set() как значение по умолчанию.

Позиционные

*args - переменное число позиционных

Подвиньте ползунок - посмотрите, как аргументы упаковываются в кортеж

аргументов: 3
вызов
123
↓ args
(1, 2, 3)
def total(*args): return sum(args) print(total(1, 2, 3))
6

Именованные

**kwargs - переменное число именованных

Добавляйте/убирайте поля - kwargs пакует их в словарь

полей: 3
вызов
name="Аня" age=30 city="Ташкент"
↓ kwargs
{"name": "Аня", "age": 30, "city": "Ташкент"}
def profile(**kwargs): for k, v in kwargs.items(): print(f"{k}: {v}") profile(name="Аня", age=30, city="Ташкент")
name: Аня age: 30 city: Ташкент

Порядок параметров

Куда попадёт каждое значение?

Строгий порядок: обычные → defaults → *args → keyword-only → **kwargs

def f(a, b=10, *args, status="active", **kwargs): print(a, b, args, status, kwargs) f(1, 2, 3, 4, status="pending", user="John")
a = 1 b = 2 # перезаписал default args = (3, 4) status = "pending" kwargs = {"user": "John"}
Тонкий момент

После *args все аргументы - keyword-only. status можно передать только по имени - иначе значение попало бы в args.

Распаковка при вызове

f(*lst, **dct) - те же * и ** работают наоборот: разложить список и словарь как аргументы.

Прежде чем погружаться

Зачем вообще нужен scope?

Представьте проект на 10 000 строк. Если бы все переменные были глобальными, ад начался бы на 10-й строке.

без scope

Хаос имён

  • • Каждая функция знает обо всём
  • • Нельзя i, temp, result использовать дважды
  • • Изменил в одной - сломал в другой
  • • Никто не знает, кто меняет переменную
со scope

Изолированные миры

  • • У каждой функции свой namespace
  • i в f()i в g()
  • • Локальные изменения остаются локальными
  • • Легко переиспользовать и тестировать
Аналогия

Scope - окно с односторонним стеклом. Изнутри функции видно внешний мир (глобальные). Снаружи к локальным переменным доступа нет.

Видимость

Область видимости (scope)

Кликните на имя - Python ищет его по правилу LEGB

x = 100 # global
def demo():
    y = 5 # local
    print(x, y) # видит global x
demo() # 100 5
print(y) # попробуйте: NameError
Кликните на любую переменную - увидите путь поиска

Правила

LEGB - порядок поиска

Python ищет имя в этой последовательности

Уровень Где
L · Local Внутри текущей функции
E · Enclosing Во внешней функции (если есть вложенность)
G · Global На уровне модуля
B · Built-in Встроенные имена: print, len, range...
Важно

Присваивание x = 5 внутри функции создаёт локальную переменную, даже если есть глобальная с тем же именем.

Запись во внешний scope

global и nonlocal

Чтение видит внешний scope. Запись - только локальный. Чтобы переписать внешнюю переменную, нужны ключевые слова.

global

писать в глобальную

counter = 0 def inc(): global counter counter += 1 inc(); inc() print(counter) # 2
nonlocal

писать во внешнюю функцию

def outer(): x = 0 def inner(): nonlocal x x += 1 inner(); inner() return x print(outer()) # 2
Без global

Получите UnboundLocalError - Python считает переменную локальной, как только видит присваивание в теле функции. global явно говорит «не создавай локальную, пиши в глобальную».

Кратко

Лямбда-функции

Короткая безымянная функция в одну строку

через def

Многострочно

def double(x): return x * 2
через lambda

Одна строка

double = lambda x: x * 2 double(5) # 10

Синтаксис: lambda аргументы: выражение. Только одно выражение, return не пишется.

Граница хорошего вкуса

Когда lambda - это уже плохо

Lambda идеальна для коротких выражений. Если логика разветвлена - пишите def. Код пишется для людей.

ужасно

Тройные тернарные операторы

group = lambda x: "Взрослый" if x >= 18 else ("Подросток" if x >= 13 else "Ребёнок")
читаемо

Обычная функция

def age_group(age): if age >= 18: return "Взрослый" if age >= 13: return "Подросток" return "Ребёнок"
Правило большого пальца

Lambda больше 50–60 символов или с условиями - переписывайте в def. Имя функции = бесплатная документация.

Применение

lambda + sorted

Нажмите кнопку - карточки физически перестроятся

1Аня30
2Боря25
3Вика28
4Глеб22
5Дима35
people # исходный
[ ('Аня', 30), ('Боря', 25), ('Вика', 28), ('Глеб', 22), ('Дима', 35), ]

Ловушка #2

Late binding в lambda

Лямбда запоминает имя переменной, а не её значение в момент создания

funcs = [lambda: i for i in range(3)] for f in funcs: print(f())
2 2 2 ⚠ не 0, 1, 2 !

К моменту вызова всех трёх лямбд цикл уже закончился и i = 2. Каждая лямбда смотрит на одно и то же имя.

# фикс: «защёлкнуть» текущее i через default-аргумент funcs = [lambda i=i: i for i in range(3)] for f in funcs: print(f())
0 1 2

Вопрос

Что выведет код?

def f(x, y=10): return x + y print(f(5)) print(f(5, 20)) print(f(y=3, x=7))
A15 / 25 / 10
B15 / 25 / 21
C5 / 25 / Error
D15 / 20 / 10

Подумайте минуту →

Практика 1

Калькулятор статистики

15:00
  1. 1 Написать функцию stats(*nums)
  2. 2 Возвращать словарь: min, max, avg, count
  3. 3 Если аргументов нет - вернуть None
  4. 4 Проверить: stats(1, 2, 3, 4, 5)

Решение

Разбор решения 1

def stats(*nums): if not nums: return None return { "min": min(nums), "max": max(nums), "avg": sum(nums) / len(nums), "count": len(nums), } print(stats(1, 2, 3, 4, 5)) # {"min": 1, "max": 5, "avg": 3.0, "count": 5}
*args проверка dict-return

Практика 2

Топ-3 студента

15:00
scores = {"Аня": 85, "Боря": 72, "Вика": 91, "Глеб": 68, "Дима": 88}
  1. 1 Написать функцию top_n(scores, n=3)
  2. 2 Использовать sorted с lambda по значению
  3. 3 Вернуть список из n лучших пар (имя, балл)

Подсказка: scores.items() → список пар, обратная сортировка через reverse=True.

Решение

Разбор решения 2

def top_n(scores, n=3): pairs = sorted( scores.items(), key=lambda p: p[1], reverse=True, ) return pairs[:n] scores = {"Аня": 85, "Боря": 72, "Вика": 91, "Глеб": 68, "Дима": 88} print(top_n(scores)) # [("Вика", 91), ("Дима", 88), ("Аня", 85)]
items() sorted + lambda срез [:n]

Поиграйте сами

Python прямо в браузере

Измените код и нажмите «Запустить» - это настоящий Python через Pyodide

Pyodide ещё не загружен - нажмите «Запустить»
// Вывод появится здесь

Практические задания

Jupyter Notebook

10 задач по темам урока. Откройте в Jupyter или VS Code и выполните самостоятельно.

.ipynb
lesson3_tasks.ipynb
10 практических заданий
Скачать RU Yuklab olish UZ Download EN

Итоги

Итоги урока

  • Кортежи (tuple), множества (set), словари (dict)
  • def, return, параметры
  • Mutable defaults trap
  • *args и **kwargs
  • LEGB · global · nonlocal
  • lambda и sorted/map/filter
  • Late binding в lambda
  • Распаковка f(*lst, **dct)
Следующий урок

Модули, файлы и обработка ошибок - чтение/запись данных и try/except

Вопросы?

Telegram: @gokalqurt

Python · Урок 3
RU UZ EN
1 / 59