Статьи
October 10, 2022

Всё, что вам нужно знать о звёздочках в 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

PythonTalk в Telegram

Чат PythonTalk в Telegram

Предложить материал | Поддержать канал