Параметры и аргументы функций
Параметры – это переменные, которые функция использует для получения значений, переданных ей при вызове. Они позволяют функции работать с данными, переданными извне, делая её более гибкой и универсальной. Значения, которые передаются функции при её вызове, называются аргументами.
Например, встроенная функция sum() возвращает сумму всех элементов коллекции. При этом коллекция должна быть передана как первый аргумент функции, а значение параметра start, определяющее начальное значение суммы, может быть не указано вовсе, передано по имени или как второй аргумент функции:
numbers = [1, 2, 3]
print(sum(numbers))
# Вывод: 6
print(sum(numbers, start=10))
# Вывод: 16
print(sum(numbers, 10))
# Вывод: 16
Таким образом, значения параметров, то есть аргументы, могут быть переданы функции по позиции или по имени. А сами параметры функции делятся на обязательные и необязательные. Обязательные параметры должны быть переданы при каждом вызове функции, в то время как необязательные параметры могут быть опущены, и для них будет использовано значение по умолчанию.
Позиционные и именованные аргументы
Давайте напишем простую функцию create_superuser(), условно создающую пользователя по его имени и почте:
def create_user(username: str, email: str) -> None:
print(f"Создан пользователь: {username} с email: {email}")
Здесь у функции есть два обязательных параметра: username и email.
Если передать их значения в том же порядке, в котором они были определены в функции, то такие аргументы называются позиционными:
create_user("KingOfWesteros", "dragon777@example.ru")
# Вывод: Создан пользователь: KingOfWesteros с email: dragon777@example.ru
Python сопоставляет первое значение "KingOfWesteros" с первым параметром username, второе значение "dragon777@example.ru" со вторым параметром email.
Если аргументы передаются по имени соответствующего параметра, а не по его позиции в списке параметров, то такие аргументы называются именованными:
create_user(email="daenerys99@example.ru", username="QueenOfWesteros")
# Вывод: Создан пользователь: QueenOfWesteros с email: daenerys99@example.ru
При этом не имеет значения порядок следования, поэтому мы можем сначала передать значение параметра email, а затем – username. Использование именованных аргументов делает код более читабельным, особенно если у функции много параметров.
Позиционные и именованные аргументы можно комбинировать. Однако существует важное правило: все позиционные аргументы должны идти перед именованными.
create_user("PrinceOfDorne", email="snake69@example.ru")
# Вывод: Создан пользователь: PrinceOfDorne с email: snake69@example.ru
Здесь первый аргумент "PrinceOfDorne" передан по позиции, а второй аргумент "snake69@example.ru" – по имени. Но наоборот делать нельзя, так как это приведёт к синтаксической ошибке:
create_user(email="snake69@example.ru", "PrinceOfDorne")
# Ошибка: SyntaxError: positional argument follows keyword argument
Также можно указать, какие параметры будут приниматься только по имени. Для этого используется символ *, который отделяет позиционные аргументы от именованных.
Например, создадим функцию get_book(), которая принимает информацию о книги и выводит её на экран:
def get_book(author: str, title: str, *, year: int, price: int) -> None:
print(f"Автор: {author}. Название: {title}. Год: {year}. Цена: {price}")
Параметры year и price нельзя передать этой функции по позиции, так как после символа * параметры могут передаваться только по имени:
get_book("Алан Александр Милн", "Винни-Пух и все-все-все", year=1969, price=99)
# Вывод: Автор: Алан Александр Милн. Название: Винни-Пух и все-все-все. Год: 1969. Цена: 99
Необязательные параметры
Необязательным параметрам при создании функции присваивается значение по умолчанию, поэтому если при вызове функции для такого параметра не передано новое значение, то будет использовано значение по умолчанию.
При создании функции, параметры со значением по умолчанию должны следовать после обязательных параметров.
Например, создадим функцию filter_menu(), которая принимает статус наличия комплексных завтраков, обедов и ужинов в меню и выводит на экран список доступных из них:
def filter_menu(is_breakfast: bool, is_lunch: bool=True, is_dinner: bool=True) -> None:
menu = {"Завтрак": is_breakfast, "Обед": is_lunch, "Ужин": is_dinner}
meals = [pos for pos, is_available in menu.items() if is_available]
print(meals)
Здесь is_breakfast – обязательный параметр, и у него не задано значение по умолчанию, а параметры is_lunch и is_dinner – необязательные, поэтому у них есть значение по умолчанию и если мы не передадим им значение, то Python автоматически подставит True:
filter_menu(True)
# Вывод: ['Завтрак', 'Обед', 'Ужин']
Но мы можем изменить значение необязательного параметра, передав ему значение по позиции или по имени:
filter_menu(True, is_dinner=False)
# Вывод: ['Завтрак', 'Обед']
filter_menu(True, False)
# Вывод: ['Завтрак', 'Ужин']
filter_menu(True, True, False)
# Вывод: ['Завтрак', 'Ужин']
Изменяемый тип данных как значение по умолчанию
Параметры со значением по умолчанию очень полезны, но если это значение является изменяемым типом данных, то могут возникнуть проблемы.
Например, создадим функцию add_sterisk(), которая принимает один необязательный параметр lst, по умолчанию представленный пустым списком, и добавляет звездочку в конец этого списка:
def add_sterisk(lst: list[str]=[]) -> list[str]:
lst.append("*")
print(lst)
return lst
Если при вызове функции передавать ей какой-то список, то всё работает:
add_sterisk([1, 2, 3])
# Вывод: [1, 2, 3, '*']
add_sterisk(["?", "!", ":"])
# Вывод: ['?', '!', ':', '*']
Но при многократном вызове без переданных аргументов результат может показаться неожиданным:
add_sterisk()
# Вывод: ['*']
add_sterisk()
# Вывод: ['*', '*']
add_sterisk()
# Вывод: ['*', '*', '*']
Это связано с тем, что пустой список lst создаётся в памяти компьютера один раз при определении функции, и все следующие вызовы функции без переданных аргументов изменяют значение этого параметра, так как список являются изменяемым типом данных.
Чтобы функция работала так, как требуется, мы можем изменить значение по умолчанию с пустого списка на неизменяемый объект None:
def add_sterisk(lst: list[str] | None=None) -> list[str]:
if lst is None:
lst = []
lst.append("*")
print(lst)
return lst
Теперь, сколько бы раз вы ни вызывали функцию, проблем не возникнет:
add_sterisk()
# Вывод: ['*']
add_sterisk()
# Вывод: ['*']
add_sterisk()
# Вывод: ['*']
Поэтому при определении значений по умолчанию для параметров функции следует использовать неизменяемые объекты.
Произвольное количество аргументов
Иногда заранее неизвестно, сколько аргументов будет передано функции. Например, встроенная функция print() выводит на экран все переданные ей объекты. При создании собственной функции мы также можем указать, что она может принимать произвольное количество аргументов с помощью операторов * и **.
Произвольное количество позиционных аргументов
Оператор * перед именем параметра позволяет ему принимать любое количество позиционных аргументов, которые собираются в кортеж. Обычно такой параметр называют args (от англ. arguments – аргументы), но имя можно свободно изменять.
Например, напишем функцию sum_even_numbers(), которая принимает произвольное количество чисел и возвращает сумму только чётных из них. Внутри функции с параметром args можно работать как с обычным кортежем:
def sum_even_numbers(*args) -> int:
sum_even = 0
for number in args:
if number % 2 == 0:
sum_even += number
print(sum_even)
return sum_even
Мы можем передать этой функции любое количество чисел:
sum_even_numbers(10, 11)
# Вывод: 10
sum_even_numbers(1, 2, 3, 4, 5)
# Вывод: 6
Как мы уже говорили, символ * позволяет отделить позиционные аргументы от именованных, но если в функции уже указан параметр *args, то этот символ не указывается, а все следующие аргументы в любом случае передаются по имени.
Например, напишем функцию sum_odd_numbers(), которая вычисляет сумму нечётных чисел, но при этом принимает стартовое значение суммы start и число stop, после которого вычисления заканчиваются:
def sum_odd_numbers(start: int, *args, stop: int) -> int:
sum_odd = start
for number in args:
if number == stop:
break
if number % 2 == 1:
sum_odd += number
print(sum_odd)
return sum_odd
При этом параметр stop может быть передан только по имени:
sum_odd_numbers(0, 10, 11, 12, 13, 97, stop=13)
# Вывод: 11
Произвольное количество именованных аргументов
Оператор ** перед именем параметра позволяет функции принимать любое количество именованных аргументов, которые собираются в словарь. Обычно такой параметр называют kwargs (от англ. keyword arguments – именованные аргументы), но имя можно свободно изменять.
Например, напишем функцию print_profile(), которая принимает произвольное количество именованных аргументов с данными о пользователе и выводит их на экран. Внутри функции с параметром kwargs можно работать как с обычным словарём:
def print_profile(**kwargs) -> None:
username = kwargs.get("username", "Не определено")
print(f"Профиль пользователя: {username}:")
for key, value in kwargs.items():
if key != "username":
print(f"{key}: {value}")
Ключами в словаре kwargs являются имена аргументов (например, username), а значениями – значения этих аргументов (например, "железнобокий"):
print_profile(username="железнобокий", name="Бьёрн", age=30,
city="Каттегат")
# Вывод: Профиль пользователя: железнобокий:
# Вывод: name: Бьёрн
# Вывод: age: 30
# Вывод: city: Каттегат
Комбинирование параметров в функции
В одной функции могут использоваться разные типы параметров. Как мы знаем, при вызове функции сначала передаются позиционные аргументы, а затем – именованные. Поэтому при создании функции параметры должны следовать в определённом порядке:
- Параметры, значения которых передаются по позиции.
*args.- Параметры, значения которых передаются по имени.
**kwargs.
Давайте создадим функцию create_message() для создания сообщения:
def create_message(
recipient: str,
text: str,
sender: str="Система",
*tags,
**details
) -> None:
print(f"От: {sender}")
print(f"Кому: {recipient}")
print(f"Текст: {text}")
if tags:
print(f"Теги: {', '.join(tags)}")
if details:
print("Детали:")
for key, value in details.items():
print(f"- {key}: {value}")
Здесь у нас есть обязательные параметры с получателем recipient и текстом сообщения text, а также необязательный параметр с отправителем sender, так как по умолчанию сообщение приходит от системы. Кроме этого, функция принимает произвольное количество тегов и деталей, собираемых параметрами *tags и **details.
В этой функции представлены все виды параметров, однако для её вызова достаточно передать значения только обязательных параметров recipient и text:
create_message("Михаил Голицын", "Привет!")
# Вывод: От: Система
# Вывод: Кому: Михаил Голицын
# Вывод: Текст: Привет!
Но также мы можем изменять значение по умолчанию и передавать произвольное количество позиционных и ключевых аргументов:
create_message("Екатерина Романова", "Срочно!", "Григорий Орлов", "срочно", "внимание", due_date="завтра", priority="высокий")
# Вывод: От: Григорий Орлов
# Вывод: Кому: Екатерина Романова
# Вывод: Текст: Срочно!
# Вывод: Теги: срочно, внимание
# Вывод: Детали:
# Вывод: - due_date: завтра
# Вывод: - priority: высокий
create_message("Анна Иоановна", "Отчет готов", "Эрнст Бирон", "финансы", "отчет")
# Вывод: От: Эрнст Бирон
# Вывод: Кому: Анна Иоановна
# Вывод: Текст: Отчет готов
# Вывод: Теги: финансы, отчет
Примеры
Пример 1. Регистрация нового пользователя
В системе администратор регистрирует новых пользователей с помощью функции register_user(), настраивая их права (администратор или обычный пользователь) и статус активности аккаунта:
def register_user(
username: str,
password: str,
*,
is_admin: bool=False,
is_active: bool=True
) -> None:
"""Регистрирует нового пользователя в системе.
Параметры:
username: Имя пользователя.
password: Пароль.
is_admin: Указывает, является ли пользователь администратором.
По умолчанию False.
is_active: Указывает, активен ли аккаунт.
По умолчанию True.
"""
print(f"Регистрация пользователя \"{username}\"...")
print(f"Статус: активен = {is_active}, администратор = {is_admin}")
# Здесь могла бы быть логика сохранения данных в базу
register_user("mr_potter", "pass1234") # Обычный пользователь
register_user("voldemort777", "admin_pass", is_admin=True) # Администратор
register_user("prof_snape", "12345678", is_active=False) # Неактивный пользователь
Эта функция принимает обязательные позиционные параметры username с именем и password с паролем пользователя. После символа * определяются два необязательных параметра со статусом пользователя: является ли он администратором (is_admin) или активным пользователем (is_active) которые могут быть переданы только по имени. Они имеют значения по умолчанию False и True соответственно.
Вывод:
Регистрация пользователя "mr_potter"...
Статус: активен = True, администратор = False
Регистрация пользователя "voldemort777"...
Статус: активен = True, администратор = True
Регистрация пользователя "prof_snape"...
Статус: активен = False, администратор = False
Пример 2. Форматирование заголовка отчета
Функция format_report_title() генерирует форматированный заголовок для отчета, добавляя к нему имя автора (если оно известно) и произвольное количество тегов, чтобы облегчить поиск и категоризацию:
def format_report_title(title: str, author: str="Неизвестен", *tags) -> None:
"""Форматирует заголовок отчета с автором и тегами.
Параметры:
title: Основной заголовок.
author: Автор отчета. По умолчанию "Неизвестен".
*tags: Произвольное количество позиционных тегов.
"""
formatted_title = f"{title.upper()} от {author}"
if tags:
formatted_title += f" [Теги: {', '.join(tags)}]"
print("-" * len(formatted_title))
print(formatted_title)
print("-" * len(formatted_title))
format_report_title("Отчет по продажам", "Третьяков П. М.")
format_report_title("Маркетинговая кампания", "Потёмкин Г. А.", "срочно", "бюджет", "2025")
Эта функция принимает обязательный позиционный параметр title с заголовком отчёта, необязательный позиционный параметр author с автором отчёта и произвольное количество тегов *tags.
Вывод:
------------------------------------
ОТЧЕТ ПО ПРОДАЖАМ от Третьяков П. М.
------------------------------------
---------------------------------------------------------------------
МАРКЕТИНГОВАЯ КАМПАНИЯ от Потёмкин Г. А. [Теги: срочно, бюджет, 2025]
---------------------------------------------------------------------
Пример 3. Запись данных в журнал событий
Функция логирования событий log_event() может принимать не только основное сообщение, но и любое количество дополнительных деталей о событии, например, идентификатор пользователя или код ошибки.
def log_event(message: str, **details) -> None:
"""Записывает событие в журнал с дополнительными деталями.
Параметры:
message: Обязательное сообщение о событии.
**details: Произвольное количество именованных аргументов с дета-лями.
"""
log_entry = f"Журнал событий: {message}"
if details:
details_list = [f"{key}={value}" for key, value in de-tails.items()]
log_entry += f" ({', '.join(details_list)})"
print(log_entry)
log_event("Пользователь вошел в систему.")
log_event("Ошибка загрузки файла", user="admin", file_id=123, error_code=404)
log_event("Операция успешно выполнена", module="API", duration_ms=150)
Эта функция принимает обязательный позиционный параметр message с сообщением и произвольное количество деталей об этом сообщении **details.
Вывод:
Журнал событий: Пользователь вошел в систему.
Журнал событий: Ошибка загрузки файла (user=admin, file_id=123, error_code=404)
Журнал событий: Операция успешно выполнена (module=API, duration_ms=150)
Итоги
- Позиционные аргументы передаются в функцию в порядке их объявления, а именованные аргументы передаются по имени.
- Позиционные аргументы должны быть переданы до именованных.
- Необязательные параметры – это параметры, которые имеют значение по умолчанию, назначенное им при определении функции и которое используется, если при вызове функции это значение не было передано явно.
- Параметры
*argsи**kwargsпозволяют передать в функцию произвольное количество аргументов.*argsсобирает позиционные аргументы в виде кортежа, а**kwargsсобирает именованные аргументы в виде словаря. - Порядок параметров при создании функции: позиционные,
*args, именованные,**kwargs.
Задания для самопроверки
1. Чем позиционные аргументы отличаются от именованных?
Позиционные аргументы передаются в функцию в порядке их объявления, а именованные аргументы передаются по имени. При этом позиционные аргументы передаются до именованных.
2. Напишите функцию calculate_total(price, quantity, discount=0), которая принимает два обязательных позиционных аргумента: цена товара price и количество товара quantity, а также один необязательный именованный аргумент со скидкой discount (в процентах, значение по умолчанию равно нулю). Функция должна возвращать итоговую стоимость с учётом скидки. Вызовите эту функцию для price = 85, quantity = 2 и discount = 10, и выведите результат на экран.
def calculate_total(price: float, quantity: int, discount: float = 0) -> float:
total_cost = price * quantity
final_cost = total_cost * (1 - discount / 100)
return final_cost
print(calculate_total(85, 2, discount=10))
# Вывод: 153.0
3. Объясните, почему при использовании изменяемых объектов (например, списков) в качестве значений по умолчанию для параметров могут возникнуть неожиданные результаты при многократном вызове функции.
Значения по умолчанию создаются в момент определения функции, поэтому если вызвать эту функцию несколько раз и изменить внутри неё какой-то элемент, то все последующие вызовы будут использовать и изменять ту же самую, уже изменённую, копию объекта.
4. Для чего используются параметры *args и **kwargs? В каком порядке они должны следовать в определении функции?
Параметры *args и **kwargs позволяют передать в функцию произвольное количество аргументов. *args собирает позиционные аргументы в виде кортежа, а **kwargs собирает именованные аргументы в виде словаря. Параметры *args передаются до параметров **kwargs.
5. Напишите функцию show_profile(username, **details), которая принимает обязательный позиционный аргумент с именем username и произвольное количество именованных аргументов **details. Функция должна выводить на экран имя пользователя и все значения словаря details через запятую. Вызовите функцию для username = "karen", occupation = "Журналист" и age=27.
def show_profile(username: str, **details) -> None:
# Оператор * распаковывает словарь details
print(username, *details.values(), sep=", ")
show_profile(username="karen", occupation="Журналист", age="27")
# Вывод: karen, Журналист, 27
0 комментариев