Любая программа рано или поздно начинает повторяться: один и тот же кусок логики нужен в нескольких местах. Функции позволяют дать этому куску имя и переиспользовать его, а модули — разложить функции по файлам, чтобы проект не превращался в одну простыню. Разберёмся, как это устроено в 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__"отделяет код прямого запуска от кода для импорта.
Что почитать дальше
- Синтаксис и типы — базовые типы, переменные и выражения, на которых строятся функции.
- Объектно-ориентированное программирование — классы и методы как следующий шаг после функций.
- Инструментарий — виртуальные окружения, установка пакетов и запуск проектов.