Перейти к содержимому
TelegramGitHub

Функции

Функция — это именованный кусок кода. Вы пишете его один раз и вызываете сколько угодно. Pipeline состоит из функций: прочитать, очистить, посчитать, записать.

Правило курса: решайте сами, без AI.

def add_vat(amount, rate=0.2):
return round(amount * (1 + rate), 2)
print(add_vat(100))
print(add_vat(100, 0.1))
print(add_vat(rate=0.5, amount=10))

Ответ:

120.0
110.0
15.0
ЧастьЧто значит
def add_vat(...)Объявление функции
amountОбязательный параметр
rate=0.2Параметр со значением по умолчанию
returnЧто функция отдаёт наружу
  • Аргументы можно передавать по позиции: add_vat(100, 0.1).
  • Или по имени: add_vat(rate=0.5, amount=10). Так код легче читать.
  • Параметры со значением по умолчанию ставьте после обязательных.

Функция без return отдаёт None.

def hello(name):
print("hi", name)
r = hello("x")
print(r)

Ответ:

hi x
None

print показывает значение. return отдаёт его вызывающему коду. В pipeline почти всегда нужен return.

Можно вернуть несколько значений. Это tuple, его можно распаковать:

def min_max(values):
return min(values), max(values)
lo, hi = min_max([49.9, 120.0, 15.5])
print(lo, hi)

Ответ:

15.5 120.0

Подсказки типов говорят читателю и редактору, что ждёт функция и что вернёт.

def total(amounts: list[float], skip_negative: bool = True) -> float:
if skip_negative:
return sum(a for a in amounts if a > 0)
return sum(amounts)
print(total([10, -5, 20]))
print(total([10, -5, 20], skip_negative=False))

Ответ:

30
25
  • amounts: list[float] — параметр. -> float — тип результата.
  • sum(a for a in amounts if a > 0) — это comprehension без квадратных скобок. Python не строит лишний список.
  • Для «или None» пишите str | None.
  • Python не проверяет типы при запуске. Проверяет их внешний инструмент, например mypy или редактор.

Проверьте сами:

def add(a: int, b: int) -> int:
return a + b
print(add("x", "y"))

Ответ:

xy

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

Коротко. Это способ принять «любое число аргументов».

def count_args(*args, **kwargs):
print(args, kwargs)
count_args(1, 2, sep=";", header=True)

Ответ:

(1, 2) {'sep': ';', 'header': True}
  • *args — все позиционные аргументы в tuple.
  • **kwargs — все именованные аргументы в dict.
  • Обратная операция: распаковка при вызове.
def add(a, b):
return a + b
nums = [3, 4]
opts = {"a": 1, "b": 2}
print(add(*nums))
print(add(**opts))

Ответ:

7
3

Вы часто увидите **kwargs в чужом коде и библиотеках. Писать их самому нужно редко.

def bad(items=[]):
items.append(1)
return items
print(bad(), bad())

Ответ:

[1, 1] [1, 1]

Оба вызова вернули один и тот же список. Он создаётся один раз при объявлении функции. Правильно так:

def good(items=None):
if items is None:
items = []
items.append(1)
return items
print(good(), good())

Ответ:

[1] [1]

Правило: не ставьте [] и {} как значение по умолчанию. Ставьте None.

Переменные внутри функции живут только в ней.

x = 10
def scope():
x = 5
return x
print(scope(), x)

Ответ:

5 10

Внутренний x — другая переменная. Передавайте данные через аргументы и return. Не меняйте глобальные переменные из функции.

Файл .py — это модуль. Из одного файла можно подключить функции другого.

Файл helpers.py:

def clean_amount(raw: str) -> float:
return float(raw.replace(",", ".").strip())
print("helpers loaded")
if __name__ == "__main__":
print("self-test:", clean_amount(" 12,5 "))

Файл main.py в той же папке:

from helpers import clean_amount
print(clean_amount("49,90"))

Запуск:

Окно терминала
python3 main.py
python3 helpers.py

Ответ:

helpers loaded
49.9
helpers loaded
self-test: 12.5
  • import выполняет весь код модуля. Поэтому helpers loaded появилось и при импорте.
  • Формы импорта: import helpers (вызов helpers.clean_amount(...)) или from helpers import clean_amount.
  • as даёт короткое имя: import polars as pl. Так подключают почти все библиотеки для данных.
  • В модуле Python есть переменная __name__. Если файл запущен напрямую, она равна "__main__". Если его импортировали — имени модуля.
  • Блок if __name__ == "__main__": выполняется только при прямом запуске. Там держат запуск скрипта или самопроверку.

Типичная структура скрипта:

def main() -> None:
... # прочитать, обработать, записать
if __name__ == "__main__":
main()
ЗадачаКак помогают функции
Очистка поля ("10,5" -> 10.5)Маленькая функция, которую легко проверить
ETL: extract, transform, loadТри функции и main()
Тесты pytestТестируют именно функции (урок 13)
Общий код между скриптамиМодуль helpers.py и import
Запуск из cron или Airflowif __name__ == "__main__":

Для заданий 2–4 возьмите этот список:

orders = [
{"order_id": 1001, "user": "anna", "amount": 49.9, "channel": "web"},
{"order_id": 1002, "user": "boris", "amount": 120.0, "channel": "app"},
{"order_id": 1003, "user": "anna", "amount": 15.5, "channel": "web"},
{"order_id": 1004, "user": "dina", "amount": 300.0, "channel": "app"},
{"order_id": 1005, "user": "boris", "amount": 80.0, "channel": "email"},
]

Напишите add_vat(amount, rate=0.2). Вызовите с 100, с 100, 0.1 и по именам rate=0.5, amount=10.

Проверка: 120.0, 110.0, 15.0.

Решение
def add_vat(amount, rate=0.2):
return round(amount * (1 + rate), 2)
print(add_vat(100))
print(add_vat(100, 0.1))
print(add_vat(rate=0.5, amount=10))

Напишите summarize(amounts: list[float]) -> dict. Она возвращает count, total и avg (среднее, округлить до 2 знаков). Вызовите для сумм всех заказов.

Проверка: {'count': 5, 'total': 565.4, 'avg': 113.08}

Решение
def summarize(amounts: list[float]) -> dict:
return {
"count": len(amounts),
"total": sum(amounts),
"avg": round(sum(amounts) / len(amounts), 2),
}
print(summarize([o["amount"] for o in orders]))

Напишите format_order(order: dict, currency: str = "RUB") -> str. Она возвращает строку вида 1001: 49.9 RUB. Вызовите для первого заказа и для второго с currency="USD".

Проверка:

1001: 49.9 RUB
1002: 120.0 USD
Решение
def format_order(order: dict, currency: str = "RUB") -> str:
return f"{order['order_id']}: {order['amount']} {currency}"
print(format_order(orders[0]))
print(format_order(orders[1], currency="USD"))

Внутри f-string с двойными кавычками снаружи используйте одинарные кавычки для ключей.

Напишите filter_orders(orders, min_amount=0.0, channel=None). Она возвращает список заказов с суммой не меньше min_amount. Если channel задан, то только этого канала. Выведите order_id для трёх вызовов: min_amount=50; channel="web"; 100, "app".

Проверка:

[1002, 1004, 1005]
[1001, 1003]
[1002, 1004]
Решение
def filter_orders(
orders: list[dict],
min_amount: float = 0.0,
channel: str | None = None,
) -> list[dict]:
result = []
for o in orders:
if o["amount"] < min_amount:
continue
if channel is not None and o["channel"] != channel:
continue
result.append(o)
return result
print([o["order_id"] for o in filter_orders(orders, min_amount=50)])
print([o["order_id"] for o in filter_orders(orders, channel="web")])
print([o["order_id"] for o in filter_orders(orders, 100, "app")])

Напишите sum_all(*args), которая суммирует любое число аргументов. Вызовите sum_all(1, 2, 3) и sum_all().

Проверка: 6 0

Решение
def sum_all(*args):
return sum(args)
print(sum_all(1, 2, 3), sum_all())

Создайте два файла в одной папке. В helpers.py — функция clean_amount(raw: str) -> float (меняет запятую на точку, убирает пробелы, делает float), строка print("helpers loaded") и блок if __name__ == "__main__": с самопроверкой clean_amount(" 12,5 "). В main.py импортируйте функцию и выведите clean_amount("49,90").

Проверка:

Окно терминала
python3 main.py # helpers loaded, потом 49.9
python3 helpers.py # helpers loaded, потом self-test: 12.5
Решение

helpers.py:

def clean_amount(raw: str) -> float:
return float(raw.replace(",", ".").strip())
print("helpers loaded")
if __name__ == "__main__":
print("self-test:", clean_amount(" 12,5 "))

main.py:

from helpers import clean_amount
print(clean_amount("49,90"))
  • def name(params): ... return value. Без return функция отдаёт None.
  • Аргументы бывают позиционные и именованные. Значения по умолчанию ставьте после обязательных.
  • Type hints помогают людям и инструментам. Python их не проверяет.
  • Не используйте [] и {} как значение по умолчанию. Берите None.
  • if __name__ == "__main__": запускает код только при прямом запуске файла.

Дальше: Python Crash Course, глава 8.

Следующий урок: Классы (минимум).