← назад к разделу

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

Как объявить функцию

Функция объявляется ключевым словом def: имя, скобки с параметрами, двоеточие и тело с отступом. Результат возвращается через return.

def area(width, height):
    return width * height

print(area(3, 4))  # 12

Если return нет (или он без значения), функция возвращает None — специальный объект «ничего». Это не ошибка, а нормальное поведение для функций, которые что-то делают, но ничего не отдают (например, печатают).

def greet(name):
    print(f"Привет, {name}")

result = greet("Аня")  # печатает приветствие
print(result)          # None

Короткая формула: def объявляет функцию, return отдаёт значение, без return отдаётся None.

Позиционные и именованные аргументы

Аргументы можно передавать по позиции (в том порядке, в каком объявлены параметры) или по имени (явно указав, какому параметру что соответствует).

def connect(host, port):
    print(f"{host}:{port}")

connect("localhost", 5432)        # позиционно
connect(port=5432, host="localhost")  # по имени, порядок не важен

Именованные аргументы удобны, когда у функции много параметров и из вызова неочевидно, что есть что. Сравните create_user(True, False, True) и create_user(is_admin=True, is_active=False, send_email=True) — второй вариант читается сам.

Значения по умолчанию

Параметру можно задать значение по умолчанию — тогда при вызове его можно не передавать.

def connect(host, port=5432, timeout=30):
    print(f"{host}:{port}, таймаут {timeout}")

connect("localhost")              # localhost:5432, таймаут 30
connect("localhost", timeout=5)   # localhost:5432, таймаут 5

Параметры со значениями по умолчанию идут после обычных — иначе Python не поймёт, где заканчиваются обязательные.

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

def add_item(item, basket=[]):   # так НЕ надо
    basket.append(item)
    return basket

print(add_item("яблоко"))  # ['яблоко']
print(add_item("груша"))   # ['яблоко', 'груша'] — корзина не очистилась!

Правильный приём — значение по умолчанию None, а реальный объект создавать внутри:

def add_item(item, basket=None):
    if basket is None:
        basket = []
    basket.append(item)
    return basket

*args и **kwargs: переменное число аргументов

Иногда заранее неизвестно, сколько аргументов передадут. Тогда параметр с * собирает все лишние позиционные аргументы в кортеж, а ** — все лишние именованные в словарь.

def log(*args, **kwargs):
    print("позиционные:", args)    # кортеж
    print("именованные:", kwargs)  # dict

log(1, 2, 3, level="INFO", user="Аня")
# позиционные: (1, 2, 3)
# именованные: {'level': 'INFO', 'user': 'Аня'}

Имена args и kwargs — это просто соглашение, значимы именно * и **. Эти же звёздочки работают и в обратную сторону — чтобы распаковать коллекцию в аргументы:

def point(x, y):
    return f"({x}, {y})"

coords = [10, 20]
print(point(*coords))   # распаковали список → point(10, 20)

data = {"x": 1, "y": 2}
print(point(**data))    # распаковали словарь → point(x=1, y=2)

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

Область видимости (scope) — это то, где переменная «видна». В Python переменные ищутся по правилу LEGB: сначала Local (внутри функции), потом Enclosing (объемлющая функция), потом Global (на уровне модуля), потом Built-in (встроенные имена вроде len).

x = "глобальная"

def outer():
    x = "локальная"   # своя переменная, глобальную не трогает
    print(x)

outer()    # локальная
print(x)   # глобальная

Внутри функции можно читать глобальную переменную, но присваивание создаёт новую локальную. Чтобы изменить именно глобальную, нужен global; чтобы изменить переменную объемлющей функции — nonlocal. На практике оба используют редко: менять внешнее состояние из функции — частый источник трудноуловимых ошибок, лучше возвращать значение через return.

Функции — это объекты

В Python функция — обычный объект, как число или строка. Её можно положить в переменную, передать в другую функцию, вернуть из функции, сложить в список.

def shout(text):
    return text.upper()

f = shout            # без скобок — это сам объект-функция
print(f("привет"))   # ПРИВЕТ

# передаём функцию как аргумент
names = ["bob", "ann"]
print(list(map(shout, names)))  # ['BOB', 'ANN']

Это и есть фундамент для многих приёмов: коллбэков, сортировки по ключу (sorted(..., key=...)), декораторов.

Замыкания

Замыкание (closure) — это функция, которая «запоминает» переменные из того места, где она была создана, даже после того, как внешняя функция завершилась.

def make_multiplier(factor):
    def multiply(value):
        return value * factor   # factor взят из объемлющей функции
    return multiply

double = make_multiplier(2)
triple = make_multiplier(3)

print(double(10))  # 20
print(triple(10))  # 30

double помнит, что factor равен 2, хотя make_multiplier уже отработала. Замыкания — удобный способ настроить функцию параметрами и получить «готовую к работе» специализированную версию.

Модули: один файл — один модуль

Модуль — это просто файл .py. Всё, что в нём объявлено (функции, классы, переменные), можно подключить в другом файле через import.

Пусть есть файл geometry.py:

# geometry.py
PI = 3.14159

def circle_area(radius):
    return PI * radius ** 2

Подключаем его в другом файле:

import geometry
print(geometry.circle_area(5))   # обращаемся через имя модуля
print(geometry.PI)

# или импортируем отдельные имена
from geometry import circle_area, PI
print(circle_area(5))

# можно дать псевдоним
import geometry as geo
print(geo.circle_area(5))

Форма from module import * тянет всё подряд и засоряет пространство имён — на практике её избегают. Лучше импортировать явно то, что нужно.

Пакеты: папка с модулями

Пакет — это папка с модулями. Раньше требовался файл __init__.py, чтобы папка считалась пакетом; сейчас он не обязателен, но часто его всё равно добавляют (в нём можно собрать публичный интерфейс пакета). Внутри пакета модули адресуются через точку:

from shop.orders import create_order
import shop.payments as payments

Здесь shop — папка-пакет, orders и payments — модули внутри неё.

if __name__ == "__main__"

Когда Python запускает файл напрямую (python geometry.py), он присваивает специальной переменной __name__ значение "__main__". Когда же файл импортируют, в __name__ оказывается имя модуля ("geometry").

Это позволяет отделить код, который должен выполняться только при прямом запуске, от кода, который просто объявляет функции для импорта:

# geometry.py
def circle_area(radius):
    return PI * radius ** 2

if __name__ == "__main__":
    # выполнится только при `python geometry.py`,
    # но НЕ при `import geometry`
    print(circle_area(5))

Без этой проверки демонстрационный или отладочный код сработал бы при каждом импорте модуля — что почти никогда не нужно.

Коротко

  • Функция объявляется через def, возвращает значение через return; без return отдаёт None.
  • Аргументы передаются по позиции или по имени; параметрам можно задать значения по умолчанию — но не изменяемые объекты (используйте None и создавайте объект внутри).
  • *args собирает лишние позиционные аргументы в кортеж, **kwargs — именованные в словарь; те же */** распаковывают коллекции в аргументы.
  • Переменные ищутся по правилу LEGB; присваивание внутри функции создаёт локальную переменную.
  • Функция — это объект: её можно передавать, возвращать и хранить; на этом строятся замыкания, запоминающие переменные из места создания.
  • Модуль — это файл .py, пакет — папка с модулями; подключаются через import.
  • Проверка if __name__ == "__main__" отделяет код прямого запуска от кода для импорта.

Что почитать дальше

  • Синтаксис и типы — базовые типы, переменные и выражения, на которых строятся функции.
  • Объектно-ориентированное программирование — классы и методы как следующий шаг после функций.
  • Инструментарий — виртуальные окружения, установка пакетов и запуск проектов.