Все знают, что в Python символ звёздочки используется как оператор умножения:
product = 4 * 2 # 8
Однако, звёздочка имеет особое значение для сложных структур данных, например списков или словарей.
*args и **kwargs
*args в определении функции
В Python оператор распаковки (*) наиболее широко используется в функциях.
Допустим, у нас есть функция, которая может складывать значения и возвращать их сумму:
def add(number_1, number_2):
return number_1 + number_2
print(add(1, 2)) # 3
Но что делать, если мы хотим сложить произвольное количество значений? Мы можем просто добавить звёздочку перед именем параметра:
def add(*numbers):
sum_ = 0
for number in numbers:
sum_ += number
return sum_
В теле функции мы ожидаем, что объект numbers будет итерируемым. И действительно, теперь он стал кортежем, что позволяет нам использовать функцию с любым количеством аргументов:
print(add(1, 2, 3, 4)) # 10
* при вызове функции
Мы уже видели, как можно определить функцию, которая будет принимать любое количество аргументов. Однако что делать, если у нас уже есть функция с фиксированным набором параметров, и мы хотим передать ей список значений? Давайте рассмотрим следующий пример:
def add(number_1, number_2, number_3):
return number_1 + number_2 + number_3
У этой функции 3 параметра. Давайте предположим, что у нас есть список, содержащий ровно три элемента. Конечно, мы могли бы вызвать функцию следующим образом:
my_list = [1, 2, 3]
add(my_list[0], my_list[1], my_list[2])
К счастью, оператор распаковки (*) работает в обоих направлениях. Мы уже видели его использование в определении функции, но и при вызове функции это тоже сработает:
my_list = [1, 2, 3]
print(add(*my_list)) # 6
**kwargs в определении функции
Распаковывать словари с помощью оператора ** можно точно так же, как мы делали с *.
Рассмотрим следующую функцию:
def change_user_details(username, email, phone, date_of_birth, street_address):
user = get_user(username)
user.email = email
user.phone = phone
user.date_of_birth = date_of_birth
user.street_address = street_address
Если вызывать функцию с именованными аргументами, вызов будет выглядеть следующим образом:
change_user_details('Олег',
email='myemail@ya.ru',
phone='+77777777777',
date_of_birth='30.08.1991',
street_address='Какая-то улица')
В таком случае мы бы могли переписать функцию, чтобы она принимала произвольное количество именованных аргументов, которые затем представляются в виде словаря kwargs.
def change_user_details(username, **kwargs):
user = get_user(username)
user.email = kwargs['email']
user.phone = kwargs['phone']
user.date_of_birth = kwargs['date_of_birth']
user.street_address = kwargs['street_address']
Конечно, мы можем использовать словарь kwargs, как и любой другой словарь в Python, так что функция может стать немного чище, если использовать его следующим образом:
def change_user_details(username, **kwargs):
user = get_user(username)
for attribute, value in kwargs.items():
setattr(user, attribute, value)
** при вызове функции
Конечно, оператор ** работает и при вызове функции для распаковки аргументов:
details = {
'email': 'myemail@ya.ru',
'phone': '+77777777777',
'date_of_birth': '30.08.1991',
'street_address': 'Какая-то улица'
}
change_user_detail('Олег', **details)
Ограничение способов вызова функций
Только именованные аргументы
Интересной особенностью звёздочки в определении функций является то, что её можно использовать автономно, то есть без названия переменной (параметра). И в Python такое определение функции тоже будет абсолютно правильным:
def my_function(*, keyword_arg_1):
pass
Но что в этом случае делает самостоятельная звёздочка? Звёздочка упаковывает все аргументы (не являющиеся именованными), как мы видели выше. Но в нашем случае у неё нет названия (и ее нельзя использовать), а после * идёт переменная keyword_arg_1. Поскольку * уже соответствует всем позиционным аргументам, у нас остается keyword_arg_1, который обязан использоваться в качестве именованного аргумента.
Вызов функции с позиционными аргументами (my_function(1)) вызовет ошибку:
Вызов функции с позиционными аргументами (my_function(1)) вызовет ошибку:
TypeError: my_function() takes 0 positional arguments but 1 was given
Только позиционные аргументы
Что, если мы хотим заставить пользователей использовать только позиционные аргументы, в отличие от именованных аргументов в предыдущем примере?
Есть очень простой способ. Мы используем знак /:
def only_positional_arguments(arg1, arg2, /):
pass
Удивительно, но немногие разработчики знают об этом приёме, который был введён ещё в Python 3.8 с PEP 570.
Если попытаться вызвать функцию с именованными аргументами only_positional_arguments(arg1=1, arg2=2), это приведёт к ошибке TypeError.
Если попытаться вызвать функцию с именованными аргументами only_positional_arguments(arg1=1, arg2=2), это приведёт к ошибке TypeError.
TypeError: only_positional_arguments() got some positional-only arguments passed as keyword arguments: 'arg1, arg2'
Использование * и ** при создании объектов
Операторы * и ** могут использоваться не только для определения и вызова функций, но ещё и для создания списков и словарей.
Создание списков
Допустим, у нас есть два отдельных списка и мы хотим их объединить.
my_list_1 = [1, 2, 3]
my_list_2 = [10, 20, 30]
Конечно, мы можем сделать это с помощью оператора +:
merged_list = my_list_1 + my_list_2
Однако, оператор * предоставляет нам больше гибкости. Если мы хотим включить единичное значение между списками, мы можем сделать так:
some_value = 42
merged_list = [*my_list_1, some_value, *my_list_2]
print(merged_list)
# [1, 2, 3, 42, 10, 20, 30]
Создание словарей
Опять же, принципы, актуальные для оператора * и списков, также актуальны и для оператора ** со словарями.
social_media_details = {
'telegram': 'pythontalk_ru'
}
contact_details = {
'email': 'myemail@ya.ru'
}
user_dict = {'username': 'pythontalk', **social_media_details, **contact_details}
# {'username': 'pythontalk', 'telegram': 'pythontalk_ru', 'email': 'myemail@ya.ru'}
Распаковка объектов
Возможно, вы уже знаете, как разделить элементы последовательности на несколько переменных следующим образом:
my_list = [1, 2, 3]
a, b, c = my_list
print(a, b, c) # 1 2 3
Но знаете ли вы, что звездочки (*) можно использовать для назначения переменных, когда у вас есть последовательность произвольной длины?
Скажем, вам нужен первый элемент и последний элемент списка в определенных переменных.
Вы можете сделать это, используя звездочку следующим образом:
Скажем, вам нужен первый элемент и последний элемент списка в определенных переменных.
Вы можете сделать это, используя звездочку следующим образом:
my_list = [1, 2, 3]
a, *b, c = my_list
В этом примере a теперь содержит первый элемент, а c — последний элемент my_list. А что же в b?
В b находится весь список, исключая первый и последний элемент, то есть [2]. Обратите внимание, что b теперь является списком.
Посмотрим на распаковку списка с бОльшим количеством элементов:
В b находится весь список, исключая первый и последний элемент, то есть [2]. Обратите внимание, что b теперь является списком.
Посмотрим на распаковку списка с бОльшим количеством элементов:
my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a, *b, c = my_list
print(a) # 1
print(b) # [2, 3, 4, 5, 6, 7, 8, 9]
print(c) # 10
Источник: Bas Steins