Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Операторы упаковки Python: Функции

Давайте попробуем реализовать очень простую функцию, суммирующую числа. Для начала определим функцию sum(), принимающую на вход два числа и возвращающую их сумму:

def sum(a, b):
    return a + b

sum(1, 2)   # 3
sum(-3, 10) # 7

Пока всё просто и понятно. Сложности возникают при дополнительных требованиях: что, если захотим суммировать не два, а три числа? Или пять, или даже десять? Писать для обработки каждого случая отдельную функцию — очевидно плохой вариант:

def sum_of_tree(a, b, c):
    return a + b + c
def sum_of_ten(a, b, c, d, e, f, g, h, i, j):
    return a + b + c + d + e + f + g + h + i + j # фух...
# def sum_of_thousand ???
# def sum_of_million ???

Надо, чтобы единая функция могла работать с разным количеством аргументов. Как это сделать?

Можно заметить, что в стандартной библиотеке Python существуют функции, которые могут принимать разное количество аргументов. Например, сигнатура функции max() определяется так:

max(arg1, arg2, *args, key=None)

Она говорит нам о том, что в max() передается 2 обязательных аргумента (arg1 и arg2) и еще любое количество аргументов (*args):

max(10, 20)             # 20
max(10, 20, 30)         # 30
max(10, 20, 30, 40, 50) # 50
max(-10, -20, -30)      # -10

С точки зрения вызова — ничего необычного, просто разное число аргументов. А вот определение функции с переменным числом аргументов выглядит необычно:

def func(*args):
  # args — это кортеж, содержащий все
  # переданные при вызове функции аргументы
  print(args)

func()            # => ()
func(9)           # => (9,)
func(9, 4)        # => (9, 4)
func(9, 4, 1)     # => (9, 4, 1)
func(9, 4, 1, -3) # => (9, 4, 1, -3)

Символ астериска * перед именем позиционного параметра в определении функции обозначает оператор упаковки (строго говоря, у *-оператора нет формального имени, но в документации *args называют "arguments packing"). Запись *args в определении func() из примера выше означает буквально следующее: "все переданные при вызове функции аргументы поместить в кортеж args".

Если вовсе не передать аргументов, то кортеж args будет пустым:

func() # => ()

В функцию можно передать любое количество аргументов — все они попадут в кортеж args:

func(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
# => (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

Аргументы могут быть любого типа — числа, строки, списки:

func(1, 2, 'hello', [3, 4, 5], True)
# => (1, 2, 'hello', [3, 4, 5], True)

Теперь у нас достаточно знаний, чтобы с помощью оператора упаковки переписать нашу функцию sum() так, чтобы она умела суммировать любое количество чисел:

def sum(*numbers):
  # По умолчанию 0, так как сумма ничего это 0
  result = 0
  for num in numbers:
    result += num
  return result

sum()         # 0
sum(10)       # 10
sum(10, 4)    # 14
sum(8, 10, 4) # 22

В таком контексте кортеж можно считать как "необязательные аргументы", которые можно либо вовсе не передавать, либо передавать столько, сколько хочешь. А что, если мы захотим, чтобы функция имела два обыкновенных ("обязательных") параметра, а остальные были необязательными? Всё просто: при определении функции сначала указываем стандартные параметры (например, a и b) и в конце добавляем *args:

def func(a, b, *args):
  # параметр 'a' содержит первый аргумент
  print(f'a -> {a}')
  # параметр 'b' содержит второй аргумент
  print(f'b -> {b}')
  # args содержит все остальные аргументы
  print(f'args -> {args}')

func(9, 4)
# => a -> 9
# => b -> 4
# => args -> ()
func(9, 4, 1)
# => a -> 9
# => b -> 4
# => args -> (1,)
func(9, 4, 1, -3)
# => a -> 9
# => b -> 4
# => (1, -3)
func(9, 4, 1, -3, -5)
# => a -> 9
# => b -> 4
# => args -> (1, -3, -5)

То же можно сделать и для одного аргумента:

def func(a, *args):
  # ...

и для трёх:

def func(a, b, c, *args):
  # ...

Эту идею можно продолжать и дальше, делая обязательными то количество аргументов, которое требуется. Единственное ограничение: rest-оператор может быть использован только для последнего параметра. То есть такой код синтаксически неверен:

def func(*args, a):
  # ...

# и такой тоже
def func(a, *args, b):
  # ...

Упаковка именованных аргументов

Для упаковки именованных аргументов существует свой оператор - **, "keyword argument packing". По аналогии с упаковкой позиционных аргументов, этот оператор упаковывает именованные аргументы, но, как вы могли догадаться, не в кортеж, а словарь.

def func(**kwargs):
  # kwargs — это словарь, содержащий все
  # переданные при вызове функции аргументы
  print(kwargs)

func() # => {}
func(a=2, b=4) # => {'a': 2, 'b': 4}
func(a='foo', b=[1, 2, 3]) # => {'a': 'foo', 'b': [1, 2, 3]}

Так как **kwargs будут преобразованы в словарь, то мы можем работать с ними как с любыми словарями:

def func(**kwargs):
    print(kwargs.keys())
    print(kwargs.values())
    print(kwargs | {'key': 'value'})

# => dict_keys(['a', 'b'])
# => dict_values([2, 5])
# => {'a': 2, 'b': 5, 'key': 'value'}

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

def func(a, b, *args, f='bar', k=42, **kwargs):
    # параметр 'a' содержит первый аргумент
    print(f'a -> {a}')
    # параметр 'b' содержит второй аргумент
    print(f'b -> {b}')
    # args содержит все остальные позиционные аргументы
    print(f'args -> {args}')
    # f содержит именованный аргумент и равен bar по умолчанию
    print(f'f -> {f}')
    # k содержит именованный аргумент и равен 42 по умолчанию
    print(f'k -> {k}')
    # kwargs содержит все остальные именованные аргументы
    print(f'kwargs -> {kwargs}')

func(1, 2)
# => a -> 1
# => b -> 2
# => args -> ()
# => f -> bar
# => k -> 42
# => kwargs -> {}

func(1, 2, 3)
# => a -> 1
# => b -> 2
# => args -> (3,)
# => f -> bar
# => k -> 42
# => kwargs -> {}

func(1, 2, 3, 4, 5, f='hello', k=24)
# => a -> 1
# => b -> 2
# => args -> (3, 4, 5)
# => f -> hello
# => k -> 24
# => kwargs -> {}

func(1, 2, 3, 4, 5, f='hello', k=24, l=[1, 2], ll={'key':'value'})
# => a -> 1
# => b -> 2
# => args -> (3, 4, 5)
# => f -> hello
# => k -> 24
# => kwargs -> {'l': [1, 2], 'll': {'key': 'value'}}

Выводы

В этом уроке мы научились создавать функции с переменным числом параметров, используя оператор упаковки. Операторы * и ** упаковывают в себя переданные аргументы и позволяют работать с ними как с коллекциями: кортежами или словарями.


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Об обучении на Хекслете

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
Обучитесь разработке бэкенда сайтов и веб-приложений — серверной части, которая отвечает за логику и базы данных
10 месяцев
с нуля
Старт 7 ноября

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»
Изображение Тото

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