Модуль 06 — Функции и модули

Разбить программу на части, которые можно назвать, проверить и использовать снова

Прогресс курса Модуль 6 из 9

Что вы освоите в этом модуле

01

Объявление и вызов

Функция — именованный блок кода. def объявляет её: имя, в скобках параметры, после двоеточия — тело с отступом. Объявление ничего не выполняет; тело работает при каждом вызове имя(аргументы). return завершает функцию и отдаёт значение туда, откуда её вызвали.

>>> def greet(name):
...     return f"Здравствуйте, {name}!"
... 
>>> greet("Анна")
'Здравствуйте, Анна!'
>>> message = greet("Борис")
>>> message.upper()
'ЗДРАВСТВУЙТЕ, БОРИС!'
>>> def say(name):
...     print(f"Здравствуйте, {name}!")
... 
>>> result = say("Вера")
Здравствуйте, Вера!
>>> print(result)
None

У greet и say один и тот же видимый эффект в интерактивном режиме, но разное поведение. greet возвращает строку — её можно сохранить, передать дальше, преобразовать. say только печатает; функция без return возвращает None. Правило: функция, которая считает, возвращает результат, а печатает вызывающий код. Тогда её можно использовать и в программе с окнами, и в веб-сервисе, и в тесте.

Зачем выделять функцию

  • Один и тот же код встречается дважды — исправлять ошибку придётся в двух местах.
  • Кусок кода можно назвать одной фразой: «нормализовать телефон», «посчитать средний балл».
  • Кусок кода нужно проверить отдельно от остальной программы.
02

Параметры и аргументы

Параметр может иметь значение по умолчанию — тогда аргумент можно не передавать. Аргументы передаются по позиции или по имени: rate=0. Именованные аргументы делают вызов понятным без заглядывания в объявление и позволяют менять порядок.

>>> def price_with_vat(amount, rate=20):
...     return round(amount * (1 + rate / 100), 2)
... 
>>> price_with_vat(1000)
1200.0
>>> price_with_vat(1000, 10)
1100.0
>>> price_with_vat(rate=0, amount=1000)
1000.0
>>> price_with_vat()
Traceback (most recent call last):
  File "<python-input-4>", line 1, in <module>
    price_with_vat()
    ~~~~~~~~~~~~~~^^
TypeError: price_with_vat() missing 1 required positional argument: 'amount'
>>> def total(*numbers):
...     return sum(numbers)
... 
>>> total(1, 2, 3, 4)
10
>>> def min_max(values):
...     return min(values), max(values)
... 
>>> low, high = min_max([7, 2, 9, 4])
>>> low, high
(2, 9)

Параметры без умолчания пишутся первыми. *numbers собирает любое число позиционных аргументов в кортеж. Чтобы вернуть несколько значений, функция возвращает кортеж, а вызывающий распаковывает его.

03

Ловушка изменяемого умолчания

Значение по умолчанию вычисляется один раз — при объявлении функции, а не при каждом вызове. Если это список, все вызовы делят один и тот же список:

>>> def add_item(item, items=[]):
...     items.append(item)
...     return items
... 
>>> add_item("хлеб")
['хлеб']
>>> add_item("молоко")
['хлеб', 'молоко']
>>> def add_item(item, items=None):
...     if items is None:
...         items = []
...     items.append(item)
...     return items
... 
>>> add_item("хлеб")
['хлеб']
>>> add_item("молоко")
['молоко']

Второй вызов вернул список с «хлебом» из первого. Правильный приём — умолчание None и создание нового списка внутри функции. is None — проверка «это именно None», так её принято записывать. С числами, строками и None в умолчаниях проблем нет: они неизменяемы.

04

Области видимости

Переменные, созданные внутри функции (и её параметры), — локальные: они существуют, пока функция выполняется, и не видны снаружи. Глобальные переменные (объявленные в файле вне функций) функция читать может, а присваивание внутри функции создаёт локальную переменную с тем же именем:

>>> count = 0
>>> def show():
...     print("Вижу count =", count)
... 
>>> show()
Вижу count = 0
>>> def increment():
...     count = count + 1
... 
>>> increment()
Traceback (most recent call last):
  File "<python-input-4>", line 1, in <module>
    increment()
    ~~~~~~~~~^^
  File "<python-input-3>", line 2, in increment
    count = count + 1
            ^^^^^
UnboundLocalError: cannot access local variable 'count' where it is not associated with a value
>>> def increment():
...     global count
...     count += 1
... 
>>> increment()
>>> count
1

В increment строка count = count + 1 делает count локальным для всей функции, а прочитать локальную переменную до присваивания нельзя. global count разрешает менять глобальную — но так почти не пишут: функция, которая тайком меняет внешние переменные, непредсказуема. Нормальный путь — передать значение параметром и вернуть результат: count = increment(count).

05

Документация и аннотации типов

Строка в тройных кавычках сразу после def — строка документации (docstring): что делает функция и что означают параметры. Её показывают help и подсказки редактора. Аннотации типов weight: float и -> float сообщают, каких типов ждут параметры и что вернётся.

>>> def bmi(weight: float, height: float) -> float:
...     """Индекс массы тела: вес в кг, рост в метрах."""
...     return weight / height ** 2
... 
>>> round(bmi(70, 1.75), 1)
22.9
>>> help(bmi)
Help on function bmi in module __main__:

bmi(weight: float, height: float) -> float
    Индекс массы тела: вес в кг, рост в метрах.

Интерпретатор аннотации не проверяет: bmi("70", 1.75) всё равно упадёт с TypeError только при вычислении. Их читают человек, редактор (VS Code подчеркнёт неверный тип) и программы проверки вроде mypy. В учебных программах аннотации необязательны, в рабочем коде — общепринятая практика.

06

Свой модуль

Модуль — любой файл .py. Функции, вынесенные в отдельный файл, подключаются в другие программы командой import. Модуль с функциями для текста:

"""Функции для обработки текста."""


def normalize_spaces(text: str) -> str:
    """Убирает лишние пробелы: по краям и повторы внутри."""
    return " ".join(text.split())


def count_words(text: str) -> int:
    """Число слов, разделённых пробелами."""
    return len(text.split())


def truncate(text: str, limit: int = 20, tail: str = "…") -> str:
    """Обрезает текст до limit символов, добавляя tail."""
    if len(text) <= limit:
        return text
    return text[:limit - len(tail)] + tail


if __name__ == "__main__":
    # Самопроверка: выполняется только при запуске файла, не при импорте
    assert normalize_spaces("  а   б  ") == "а б"
    assert count_words("раз два  три") == 3
    assert truncate("короткий") == "короткий"
    assert len(truncate("очень длинная строка для проверки")) == 20
    print("textutils: все проверки пройдены")

Блок if __name__ == "__main__": выполняется, только когда файл запущен как программа. При импорте переменная __name__ равна имени модуля, и блок пропускается. Здесь в нём самопроверка: assert условие молча проходит, если условие истинно, и останавливает программу с AssertionError, если ложно.

$ python3 textutils.py
textutils: все проверки пройдены

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

import textutils
from textutils import truncate

titles = [
    "  Основы   Python  ",
    "Как   устроены     словари и множества в Python",
    "Функции",
]

for t in titles:
    clean = textutils.normalize_spaces(t)
    print(f"{truncate(clean, 30):30} | слов: {textutils.count_words(clean)}")
$ python3 report.py
Основы Python                  | слов: 2
Как устроены словари и множес… | слов: 7
Функции                        | слов: 1

import textutils — подключить модуль целиком, обращаться через точку: textutils.count_words. from textutils import truncate — взять одно имя, обращаться без префикса. Первая форма нагляднее показывает, откуда функция; вторую используют для часто вызываемых имён. Импорт from модуль import * не пишут: непонятно, какие имена появились в программе.

Не называйте свои файлы именами модулей стандартной библиотеки: random.py, math.py, string.py, json.py. Python ищет модули сначала в каталоге программы, и import random подключит ваш файл вместо настоящего — с непонятной ошибкой AttributeError.
07

Стандартная библиотека

Вместе с Python ставятся сотни модулей — «батарейки в комплекте». Импорт можно сократить псевдонимом: import datetime as dt.

>>> import math
>>> math.sqrt(2)
1.4142135623730951
>>> math.ceil(7 / 2), math.floor(7 / 2)
(4, 3)
>>> math.pi
3.141592653589793
>>> from math import hypot
>>> hypot(3, 4)
5.0
>>> import random
>>> random.choice(["орёл", "решка"])
'орёл'
>>> deck = list(range(1, 11))
>>> random.shuffle(deck)
>>> deck
[1, 7, 3, 4, 10, 5, 8, 2, 9, 6]
>>> from datetime import date
>>> start = date(2026, 9, 1)
>>> end = date(2026, 12, 31)
>>> (end - start).days
121
>>> start.strftime("%d.%m.%Y")
'01.09.2026'
>>> start.weekday()
1
МодульДля чего
mathКорни, округление вверх и вниз, тригонометрия, константы
randomСлучайные числа для игр, моделирования, перемешивания
secretsСлучайные значения для паролей и токенов
datetimeДаты и время: разница, формат, день недели (weekday(): 0 — понедельник)
stringГотовые наборы символов: буквы, цифры, знаки
pathlib, os, shutilФайлы и каталоги (модули 07, 08)
csv, jsonТабличные и структурированные данные (модуль 07)

Результаты random в вашем запуске будут другими. Генератор random предсказуем: зная несколько выданных чисел, можно вычислить следующие. Для паролей, кодов подтверждения и токенов используют secrets:

import secrets
import string

ALPHABET = string.ascii_letters + string.digits


def make_password(length: int = 12) -> str:
    """Случайный пароль из латинских букв и цифр."""
    return "".join(secrets.choice(ALPHABET) for _ in range(length))


for _ in range(3):
    print(make_password())
print(make_password(20))
$ python3 password.py
jNl304IKjrw7
YQom48rFqv7Q
ntPa87GfN4kb
2DL4aaU7R63eswD2gQV5

Пароли в вашем запуске будут другими; пароли из примера не используйте — они опубликованы. secrets.choice(ALPHABET) for _ in range(length) — выражение-генератор из модуля 05; _ — принятое имя для переменной, значение которой не нужно.

08

Как устроить программу из функций

Программа больше одного экрана строится так: вверху импорты и константы, затем функции, внизу — функция main, которая их вызывает, и запуск под if __name__ == "__main__". Каждая функция делает одно дело и получает всё нужное через параметры.

"""Учёт оценок: ввод, расчёт, отчёт."""
import statistics

PASS_SCORE = 55


def read_scores() -> list[int]:
    """Читает баллы до пустой строки."""
    scores = []
    while line := input("Баллы (Enter — конец): "):
        scores.append(int(line))
    return scores


def summary(scores: list[int]) -> dict:
    """Среднее, медиана и число не сдавших."""
    return {
        "среднее": statistics.mean(scores),
        "медиана": statistics.median(scores),
        "не сдали": sum(1 for s in scores if s < PASS_SCORE),
    }


def main():
    scores = read_scores()
    if not scores:
        print("Нет данных")
        return
    for name, value in summary(scores).items():
        print(f"{name}: {value}")


if __name__ == "__main__":
    main()
$ python3 scores_app.py
Баллы (Enter — конец): 78
Баллы (Enter — конец): 92
Баллы (Enter — конец): 45
Баллы (Enter — конец): 61
Баллы (Enter — конец): 88
Баллы (Enter — конец): 50
Баллы (Enter — конец): 
среднее: 69
медиана: 69.5
не сдали: 2

Такую программу легко проверять: summary можно вызвать из теста со списком чисел, не вводя их руками. := («морж») присваивает и сразу возвращает значение — цикл читает строку и проверяет, что она не пустая, в одном условии. Две пустые строки между функциями — требование PEP 8.

Ключевые выводы модуля

Запомните главное

  • Функция, которая считает, возвращает результат через return; без return возвращается None
  • Именованные аргументы делают вызов понятным; изменяемое умолчание — только через None
  • Присваивание внутри функции создаёт локальную переменную; данные — через параметры, а не через global
  • Docstring и аннотации типов — для человека и редактора, интерпретатор их не проверяет
  • Модуль — файл .py; код запуска — под if __name__ == "__main__"
  • Пароли и токены — secrets, не random; свои файлы не называют именами стандартных модулей
Практические задания — на учебном портале. Задания, критерии оценивания, сдача работ и оценки преподавателя — в курсе на portal.nevabit.ru. Учётную запись выдаёт преподаватель.
Модуль 05: Коллекции Все модули Модуль 07: Файлы и исключения