Всё, что вам нужно знать о звёздочках в 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
теперь является итерируемым объектом. Действительно, теперь объект numbers
стал кортежем, вследствие чего мы можем использовать функцию с произвольным числом аргументов:
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] add(*my_list)
**kwargs
в объявлении функции
Распаковывать словари с помощью оператора **
можно точно так же, как и делали с *
.
def change_user_details(username, email, phone, date_of_birth, street_address): user = get_user(username) user.email = email user.phone = phone ...
Если вызывать функцию с именованными аргументами, вызов будет выглядеть следующим образом:
change_user_details('pythontalk', email='[email protected]', phone='...', ...)
В таком случае мы бы могли переписать функцию, чтобы она принимала произвольное количество именованных аргументов, которые затем представляются в виде словаря kwargs
.
def change_user_details(username, **kwargs): user = get_user(username) user.email = kwargs['email'] user.phone = kwargs['phone'] ...
Конечно, мы можем использовать словарь kwargs
, как и любой другой словарь в Python, так что функция может стать немного чище, если использовать его следующим образом:
def change_user_details(username, **kwargs): user = get_user(username) for attribute, value in kwargs.items(): setattr(user, attribute, value) ...
**
в вызове функции
Конечно, оператор **
работает и при вызове функции для распаковки аргументов:
details = { 'email': '[email protected]', ... } change_user_detail('pythontalk', **details)
Ограничение способов вызова функций
Только именованные аргументы
Одной из самых прикольных особенностей звёздочки в определениях функций является то, что её можно использовать автономно, то есть без названия переменной (параметра). И в Python такое определение функции тоже будет абсолютно правильным:
def my_function(*, keyword_arg_1): ...
Но что в этом случае делает самостоятельная звёздочка? Звёздочка упаковывает все аргументы (не являющиеся именованными), как мы видели выше. Но в нашем случае у неё нет названия (и ее нельзя использовать), а после *
идёт переменная 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, /): ...
Удивительно, но немногие разработчики знают об этом приёме, который был введён в 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] # [1, 2, 3, 42, 10, 20, 30]
Создание словарей
Опять же, принципы, актуальные для оператора *
и списков, также актуальны и для оператора **
со словарями.
social_media_details = { 'telegram': 'pythontalk' } contact_details = { 'email': '[email protected]' } user_dict = {'username': 'pythontalk', **social_media_details, **contact_details} # {'username': 'pythontalk', 'telegram': 'pythontalk', 'email': [email protected]'}
Распаковка объектов
Возможно, вы уже знаете, как разделить элементы последовательности на несколько переменных следующим образом:
my_list = [1, 2, 3] a, b, c = my_list # a -> 1 # b -> 2 # c -> 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 # a -> 1 # b -> [2, 3, 4, 5, 6, 7, 8, 9] # c -> 10
Источник: Bas Steins