Всё, что нужно знать о звёздочках в Python
Все знают, что в 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)
) вызовет ошибку:
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
.
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
теперь является списком.
Посмотрим на распаковку списка с бОльшим количеством элементов:
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
👉🏻Подписывайтесь на PythonTalk в Telegram 👈🏻