Перейти к содержимому
Поиск
Разработка

Python: полный мануал с примерами и справочником функций

Полный практический мануал Python: от установки и синтаксиса до файлов, сети, тестов и проектов. 25 глав, справочник функций и методов, упражнения с решениями и 195 проверенных примеров.

Александр Некрасов
Python: практический мануал с кодом, папкой и схемой работы

Python удобно изучать на задачах: посчитать расходы, разобрать текст, прочитать файл, собрать отчёт. В этом руководстве пройдём путь от установки и первых выражений до функций, классов, сети, тестов и самостоятельных проектов.

Выберите одну из 25 глав или найдите нужную функцию через поиск. В справочнике собраны все 73 позиции официальной таблицы встроенных функций и типов Python 3.15, а также 161 метод и 12 свойств строк, коллекций и путей. Для каждой позиции есть назначение, важный нюанс и ссылка на первичную документацию.

Код можно копировать и скачивать. 195 самостоятельных примеров проверены запуском на Python 3.15.0; у упражнений есть решения и ожидаемый результат. Новые возможности языка отмечены в тексте, а интерактивные фрагменты и команды установки объяснены отдельно.

Если начинаете с нуля, открывайте главы по порядку. Если решаете конкретную задачу, ищите по-русски или по имени Python: open, str.split, Path.read_text.

Python 3.15.025 главПримеры проверены запуском
Все главы руководства 25

Глава 8 · начальный

Свои функции, аргументы и области видимости

Отделить расчёт от ввода и оформить повторяемое действие.

Функция даёт имя законченной операции: рассчитать строку заказа, подготовить подпись, выбрать подходящие записи. Хорошая граница функции понятна без чтения каждой строки: какие данные она получает, что возвращает и какие ошибки означает недопустимый ввод. Начните с маленького расчёта, который не читает терминал и не меняет внешние данные. Такой код проще повторить, проверить и использовать в другой программе.

Def и return: вернуть результат, затем показать

def создаёт функцию и связывает её с именем. Тело выполняется при вызове, а не при объявлении. return завершает этот вызов и передаёт значение вызывающему коду. Если return отсутствует или записан без значения, результатом будет None. Print отправляет текст в вывод; он не заменяет возвращаемое значение, которое требуется следующему расчёту.

Отделяйте вычисление от способа общения с пользователем. Функция стоимости может возвращать число, а терминал, веб-страница или отчёт затем оформляют его по-своему. Если функция сама спрашивает input и печатает ответ, её труднее использовать в тесте или в другом интерфейсе. Это не запрет на print, а выбор понятной ответственности: ввод, расчёт и представление можно соединить внешним кодом.

При вызове параметр получает ссылку на переданный объект, но его имя является локальным для функции. Переназначение параметра другому объекту не переназначает имя вызывающего кода. Зато изменение общего списка через append видно снаружи. Не описывайте передачу данных одной фразой «всё копируется» или «всё передаётся по ссылке», если она скрывает эту разницу. Для функции, обещающей сохранить исходник, копию создают явно на подходящем уровне: внешнего списка достаточно не для каждой вложенной структуры.

Проверяйте такие обещания отдельным наблюдением. Сохраните исходный список, вызовите функцию и посмотрите на него после вызова, затем сравните возвращённый объект. Если задача намеренно изменяет список на месте, сообщите это в имени и docstring. Если возвращается новый результат, вызывающий код может безопасно сравнить варианты и оставить исходные данные для следующего действия.

def line_total(price: int, quantity: int, *, discount: int = 0) -> int:
    """Возвращает итог строки с целыми суммами."""
    if type(price) is not int or price < 0:
        raise ValueError("Цена должна быть неотрицательным целым")
    if type(quantity) is not int or quantity < 0:
        raise ValueError("Количество должно быть неотрицательным целым")
    subtotal = price * quantity
    if type(discount) is not int or not 0 <= discount <= subtotal:
        raise ValueError("Скидка должна быть в пределах суммы")
    return subtotal - discount

print(line_total(1500, 3, discount=500))
print(line_total(100, 0))
try:
    line_total(100, True)
except ValueError as error:
    print(type(error).__name__)
4000
0
ValueError

Здесь аннотации показывают намерение, а настоящая проверка записана явно. Bool специально не принимается как количество. Пустой заказ даёт нулевой итог при нулевой скидке; скидка сверх суммы отклоняется. Эти правила относятся к примеру. В вашем проекте сначала согласуйте договор данных, единицу суммы и политику округления, затем оформляйте их в коде функции.

Возвращать можно любой подходящий объект. Несколько значений обычно упаковывают в кортеж: return count, total, после чего вызывающий код может распаковать результат. Не возвращайте иногда строку ошибки, иногда число, если следующий шаг ожидает только число. Используйте согласованную структуру результата либо понятное исключение; это предотвращает отложенную ошибку на совсем другом действии.

Обрабатывайте ошибку там, где можно принять осмысленное решение. Функция расчёта может отклонить данные, а интерфейс — попросить исправить ввод. Если внутри расчёта перехватить любое Exception и вернуть ноль, вызывающий код уже не отличит корректный нулевой результат от сбоя. Проверяйте ожидаемые нарушения договора явно и сохраняйте неожиданную ошибку для диагностики. Это также помогает тестировать один расчёт без настоящего терминального ввода.

Параметры и способы передачи аргументов

Параметры записаны в определении функции, аргументы передаются при вызове. Позиционные аргументы сопоставляются по порядку, именованные — по имени. Одно значение нельзя одновременно передать одному параметру обоими способами: это TypeError. Именованные аргументы особенно полезны для нескольких флагов или чисел, смысл которых не очевиден по позиции. Сравните мысленно настройку resize(800, 600) с длинной цепочкой переключателей без названий.

Косая черта / отделяет позиционные-only параметры, а * делает последующие параметры именованными-only. Первый вариант полезен, когда имя внутреннего параметра не должно стать частью публичного интерфейса. Второй заставляет указать смысл настройки при вызове. Не усложняйте сигнатуру без задачи: для небольшой собственной функции достаточно привычного сочетания обязательных данных и нескольких именованных настроек.

def label(value, /, *, prefix="Тема"):
    return f"{prefix}: {value}"

print(label("Grid", prefix="Раздел"))
try:
    label(value="Grid")
except TypeError:
    print("value передаётся позиционно")

def add_all(*values):
    return sum(values)

print(add_all(2, 3, 4))
print(add_all())
Раздел: Grid
value передаётся позиционно
9
0

*args собирает дополнительные позиционные аргументы в кортеж, **kwargs — именованные в словарь. Имена args/kwargs являются соглашением; смысл задают звёздочки. При вызове звёздочки распаковывают iterable и отображение в аргументы. Следите за повтором ключей и числом ожидаемых значений. Свободные kwargs удобны для передачи настроек дальше, но опечатка в параметре может остаться незамеченной, если вы просто игнорируете неизвестные ключи.

def greeting(name, *, formal=False):
    prefix = "Здравствуйте" if formal else "Привет"
    return f"{prefix}, {name}"

options = {"name": "Анна", "formal": True}
print(greeting(**options))
try:
    greeting(**(options | {"unknown": 1}))
except TypeError:
    print("Неизвестный параметр отклонён")
Здравствуйте, Анна
Неизвестный параметр отклонён

Распаковка настроек не превращает данные в исполняемый исходник: здесь словарь лишь сопоставляется с объявленными параметрами. Но сам договор ключей всё ещё важен. В примере неизвестная настройка отклоняется обычной сигнатурой функции. Если принять свободные kwargs, проверку неизвестных имён придётся сделать самостоятельно либо явно передать их следующему известному обработчику. Такой выбор должен быть частью документации публичного API.

Передавать функцию как значение можно без круглых скобок: handler = label. Запись handler = label(...) вызывает её сейчас и сохраняет результат. Это различие встречается в callback, сортировке и декораторах. Если интерфейс ожидает действие на будущее, передавайте вызываемый объект либо специально подготовленное замыкание, а не случайный результат раннего вызова.

Default вычисляется один раз при определении

Выражение значения по умолчанию вычисляется, когда выполняется def. Оно не вычисляется заново для каждого вызова. С неизменяемым числом или строкой это обычно ожидаемо. Со списком или словарём один объект может накапливать состояние между вызовами. Такая ошибка часто появляется только после повторного использования функции, поэтому первая успешная проверка недостаточна.

def shared(label, bucket=[]):
    bucket.append(label)
    return bucket

print(shared("А"))
print(shared("Б"))

def independent(label, bucket=None):
    if bucket is None:
        bucket = []
    bucket.append(label)
    return bucket

print(independent("А"))
print(independent("Б"))
['А']
['А', 'Б']
['А']
['Б']

Во втором варианте новый список создаётся в теле функции. Однако если вызывающий код явно передаст собственный список, функция изменит именно его. Выберите и опишите этот договор: изменять предоставленную коллекцию или возвращать отдельную. Параметр по умолчанию None исправляет разделённый default, но не делает автоматически любую операцию чистой и не копирует все входные данные.

Тот же принцип касается вызова функции в default: текущее время, случайное значение или прочитанная настройка будут получены при определении. Если они должны меняться каждый раз, вычисляйте их внутри тела. Если значение намеренно фиксируется заранее, default может быть подходящим способом. Сначала определите нужный момент вычисления, затем выбирайте запись.

Когда None имеет собственный смысл, нужен отдельный маркер отсутствия. С Python 3.15 можно создать sentinel. В следующем примере отсутствие аргумента означает обычную настройку, явный None — отключённое ограничение, а ноль остаётся переданным значением. Проверка через or потеряла бы это различие.

UNSET = sentinel("UNSET")

def timeout_label(value=UNSET):
    if value is UNSET:
        return "По умолчанию"
    if value is None:
        return "Без ограничения"
    return f"Передано: {value}"

print(timeout_label())
print(timeout_label(None))
print(timeout_label(0))
По умолчанию
Без ограничения
Передано: 0

Область видимости: LEGB, global и nonlocal

Обычный поиск имени часто описывают правилом LEGB: локальная область, окружающие функции, область модуля, встроенные имена. Не путайте «область модуля» с единственным общим пространством всей программы. Каждый модуль имеет свои имена. Название вроде list или sum, которому вы присвоили собственное значение, может заслонить встроенную функцию и неожиданно изменить последующие вызовы.

Если в функции есть обычное присваивание имени, Python обычно считает его локальным во всём этом блоке. Поэтому чтение такого имени до присваивания может вызвать UnboundLocalError, даже если одноимённая переменная есть снаружи. Global направляет присваивание в область модуля. Nonlocal использует уже существующее имя в ближайшей подходящей окружающей функции. Это точные механизмы, а не способы сделать любую переменную доступной отовсюду.

limit = 10

def read_limit():
    return limit

def mistaken_change():
    print(limit)
    limit = 20

def change_limit(value):
    global limit
    limit = value

print(read_limit())
try:
    mistaken_change()
except UnboundLocalError:
    print("Имя признано локальным")
change_limit(20)
print(read_limit())
10
Имя признано локальным
20

Простое чтение read_limit не требует global. В mistaken_change присваивание заставляет трактовать limit как локальное имя, поэтому предыдущее print не успевает получить внешний предел. Исправленный пример показывает механизм, но не утверждает, что глобальное изменение является лучшим дизайном. Для большинства расчётов передать limit аргументом легче, чем помнить о скрытом изменении настройки между вызовами.

def make_counter():
    value = 0
    def increment():
        nonlocal value
        value += 1
        return value
    return increment

first = make_counter()
second = make_counter()
print(first(), first(), second())
1 2 1

Каждый вызов make_counter создаёт отдельное состояние. Внешняя функция уже завершилась, а внутреннее действие продолжает использовать её имя. Для такого локального счётчика nonlocal показывает намерение яснее, чем общая глобальная переменная. Тем не менее состояние усложняет повторяемость: если действие можно выразить функцией «получить старое значение, вернуть новое», этот вариант часто проще проверять.

Не используйте global как первую реакцию на любую ошибку имени. Проверьте, какие данные действительно нужны функции, и передайте их аргументами. Скрытые зависимости от переменной модуля связывают порядок запусков и затрудняют параллельное использование. Явные параметры делают необходимое окружение видимым в сигнатуре и помогают разделить независимые задачи.

Замыкание, lambda и позднее связывание

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

def bad_callbacks():
    return [lambda: number for number in range(3)]

def fixed_callbacks():
    return [lambda number=number: number for number in range(3)]

print([callback() for callback in bad_callbacks()])
print([callback() for callback in fixed_callbacks()])
[2, 2, 2]
[0, 1, 2]

Default во втором варианте вычисляется отдельно при создании каждой lambda и фиксирует текущее число. Для сложного действия можно написать обычную фабрику функции с аргументом. Если фиксируется изменяемый объект, default сохраняет его ссылку, а не глубокую копию. Опять сначала решите, нужно текущее состояние объекта на момент вызова или отдельный снимок.

Lambda содержит одно выражение и возвращает его результат. Она удобна для короткого ключа сортировки или небольшого преобразования рядом с местом использования. Для сложных условий, docstring и нескольких шагов обычный def читабельнее. Дайте функции имя, когда оно помогает объяснить намерение: это улучшает сообщения об ошибках и чтение вызывающего кода.

Декоратор и functools.wraps

Декоратор получает функцию и возвращает объект, который будет использоваться под её именем. Запись с @decorator применяет такое преобразование при определении. Это удобно для повторяющейся оболочки: учёта вызовов, проверки или оформления результата. Сначала проверьте простую функцию без оболочки, затем убедитесь, что декоратор не меняет её аргументы и результат неожиданным образом.

from functools import wraps

def announce_call(function):
    @wraps(function)
    def wrapper(*args, **kwargs):
        print("Вызов:", function.__name__)
        return function(*args, **kwargs)
    return wrapper

@announce_call
def double(value):
    """Удваивает значение."""
    return value * 2

print(double(3))
print(double.__name__)
print(double.__doc__)
print(double.__wrapped__(4))
Вызов: double
6
double
Удваивает значение.
8

Без return перед вызовом исходной функции wrapper вернул бы None, даже если исходный расчёт вернул число. functools.wraps переносит полезные метаданные и добавляет связь __wrapped__, чтобы инструменты могли найти оригинал. Он не переписывает фактическое тело или параметры wrapper; оболочка всё ещё должна правильно передавать args/kwargs и результат.

Несколько декораторов применяются снизу вверх. Порядок проверки и кеширования, например, может менять поведение. Не добавляйте оболочки только ради короткой записи: они должны выражать общую задачу. Подробности метаданных и других инструментов: functools.

Рекурсия, документация и проверка договора

Рекурсия вызывает функцию из самой себя. Нужны базовый случай и уменьшение задачи, иначе вызовы не завершатся. Глубина ограничена средой, а каждый вызов использует ресурсы. Для простого последовательного накопления обычный цикл часто понятнее и не создаёт глубокую цепочку вызовов. Не увеличивайте лимит рекурсии вместо проверки алгоритма.

def factorial_recursive(n):
    if type(n) is not int or n < 0:
        raise ValueError("Нужно неотрицательное целое")
    if n == 0:
        return 1
    return n * factorial_recursive(n - 1)

def factorial_loop(n):
    if type(n) is not int or n < 0:
        raise ValueError("Нужно неотрицательное целое")
    result = 1
    for value in range(2, n + 1):
        result *= value
    return result

for n in (0, 5):
    print(factorial_recursive(n), factorial_loop(n))
1 1
120 120

Docstring описывает смысл, параметры, результат и важные исключения. Он должен дополнять имя, а не пересказывать каждую строку. Аннотация типа помогает человеку и статическому анализатору, но автоматически не проверяет аргумент в runtime. Если функция обещает только строки, запись text: str не помешает вызывающему передать число. Для внешнего ввода нужны настоящие проверки и преобразование на определённой границе.

def repeat(text: str, count: int) -> str:
    return text * count

print(repeat("А", 3))
unexpected = repeat(2, 3)
print(unexpected)
print(type(unexpected).__name__)
ААА
6
int

Число здесь умножилось на число, хотя аннотация обещала строковый результат. Это воспроизводимый пример отсутствия автоматической проверки, а не рекомендуемый контракт. Статический анализатор может обнаружить несовместимый вызов до запуска, а runtime-валидация — отклонить его в самой программе. Эти проверки дополняют друг друга и решают разные задачи.

Начиная с Python 3.14 аннотации имеют отложенную модель вычисления; инструменты чтения описаны в annotationlib. Не стройте проверку данных только на наличии аннотации. Для первых функций достаточно ясного договора и тестов нормального случая, пустого ввода, границы и недопустимого типа.

Упражнение: расчёт с явной настройкой

Напишите summarize, которая получает последовательность целых, а minimum принимает только по имени. Возвратите количество и сумму значений не ниже minimum. Для пустого входа нужны ноль и ноль. Исходную последовательность не изменяйте. Отдельно отклоните bool и другие типы, если договор требует именно встроенный int. Проверьте количество и сумму как отдельные результаты, а не только напечатанную строку.

Решение упражнения
def summarize(values, /, *, minimum=0):
    """Возвращает количество подходящих целых и их сумму."""
    if type(minimum) is not int:
        raise TypeError("minimum должен быть int")
    accepted = []
    for value in values:
        if type(value) is not int:
            raise TypeError("Значения должны быть int")
        if value >= minimum:
            accepted.append(value)
    return len(accepted), sum(accepted)

print(summarize([0, -2, 4, 7], minimum=0))
print(summarize([], minimum=0))
(3, 11)
(0, 0)

Примеры и материалы с собой

Архив содержит наши самостоятельные примеры, тестовые данные, ожидаемый результат и README. Указанные команды установки и фрагменты REPL отделены от скриптов, которые проверены запуском.

Сверено с официальной документацией 2026-10-12. Стандартная библиотека и сторонние пакеты различаются; ссылки на источники есть в каждой главе.