Операторы * и ** для упаковки и распаковки коллекций
На этом занятии я хочу немного отступить от темы функций и рассказать об операторах * и **, которые мы затронули на предыдущем уроке.
Мы знаем, что они позволяют упаковывать аргументы в кортеж и словарь. Но их можно использовать не только в объявлении функций, но и при работе с разными коллекциями. Например, если взять кортеж из двух значений:
то его можно распаковать в две переменные. Но, если мы пропишем там больше значений, например, четыре:
то уже получим ошибку, так как элементов четыре, а переменных всего две. Но, используя оператор *, мы можем упаковать оставшиеся значения во вторую переменную:
То же самое можно проделывать и со списками:
И вообще с любыми итерируемыми объектами. То есть, оператор * упаковывает оставшиеся значения в список. Правда, мы не можем упаковывать уже упакованные данные, например, так:
произойдет ошибка, но вот так:
Этот же оператор может выполнять и обратную операцию – распаковывать коллекции в набор данных. Пусть у нас имеется список:
И на его основе мы хотим сформировать кортеж. Если просто записать переменную в круглых скобках:
то увидим кортеж со списком внутри. Но, если прописать оператор * перед списком:
то произойдет распаковка его элементов и список превратится в кортеж. То же самое можно сделать и при вызове функций. Допустим, определим кортеж из двух значений:
и вызовем с этими значениями функцию:
Возникнет ошибка, так как функция ожидает числа в качестве аргументов, а не коллекции. Но, мы можем распаковать кортеж d в два числа, поставив перед ним оператор *:
и теперь никаких ошибок нет. Давайте посмотрим, что вернет эта функция, преобразуем все к списку:
Да, получаем ожидаемые значения от -5 до 5. И, теперь, это же преобразование генератора range() в список мы можем сделать и так:
Здорово, да?! Мы оператором * распаковали итерируемый объект и составили из его значений список.
Мало того, мы таким образом можем делать объединение разных коллекций в одну коллекцию, например, список:
Как видите, оператор * — невероятно удобный инструмент. И те же самые действия можно делать и со словарем. Зададим, следующий словарь с расшифровкой оценок:
d = {0: "безнадежно", 1: "убого", 2: "неуд.", 3: "удовл.", 4: "хорошо", 5: "отлично"}
Распаковать его можно двумя способами. Если прописать один оператор *:
То получим множество, состоящее из ключей этого словаря. Или, можно сформировать список из ключей:
То есть, оператор * перебирает словарь как обычный итерируемый объект и по умолчанию, перебираются именно ключи. Если нам нужно перебрать значения, то следует вызвать дополнительно метод:
и получим список из значений. Соответственно, метод:
вернет кортежи из пары ключ-значение.
Если же требуется распаковать словарь как словарь, то перед ним следует прописать две звездочки:
Теперь вместо множества мы получаем словарь. Где это нам может пригодиться? Например, для объединения нескольких словарей в один. Создадим еще один словарь:
d2 = {6: "превосходно", 7: "элитарно", 8: "божественно"}
И соединим их через распаковку данных:
На выходе получаем новый словарь с объединенными данными. Такой прием часто используется на практике, когда нужно объединить сразу несколько словарей. А вот для упаковки оператор ** не используется, если прописать, что то вроде:
то получим синтаксическую ошибку. Можно указывать только одну звездочку. Исключение только параметр **kwargs при определении функций.
Надеюсь, теперь вы знаете, как работают операторы * и ** для упаковки и распаковки коллекций. Закрепляйте все практическими заданиями и переходите к следующему уроку.
Видео по теме
#1. Первое знакомство с Python Установка на компьютер
#2. Варианты исполнения команд. Переходим в PyCharm
#3. Переменные, оператор присваивания, функции type и id
#4. Числовые типы, арифметические операции
#5. Математические функции и работа с модулем math
#6. Функции print() и input(). Преобразование строк в числа int() и float()
#7. Логический тип bool. Операторы сравнения и операторы and, or, not
#8. Введение в строки. Базовые операции над строками
#9. Знакомство с индексами и срезами строк
#11. Спецсимволы, экранирование символов, row-строки
#12. Форматирование строк: метод format и F-строки
#13. Списки — операторы и функции работы с ними
#14. Срезы списков и сравнение списков
#15. Основные методы списков
#16. Вложенные списки, многомерные списки
#17. Условный оператор if. Конструкция if-else
#18. Вложенные условия и множественный выбор. Конструкция if-elif-else
#19. Тернарный условный оператор. Вложенное тернарное условие
#21. Операторы циклов break, continue и else
#22. Оператор цикла for. Функция range()
#23. Примеры работы оператора цикла for. Функция enumerate()
#24. Итератор и итерируемые объекты. Функции iter() и next()
#25. Вложенные циклы. Примеры задач с вложенными циклами
#26. Треугольник Паскаля как пример работы вложенных циклов
#27. Генераторы списков (List comprehensions)
#28. Вложенные генераторы списков
#29. Введение в словари (dict). Базовые операции над словарями
#30. Методы словаря, перебор элементов словаря в цикле
#31. Кортежи (tuple) и их методы
#32. Множества (set) и их методы
#33. Операции над множествами, сравнение множеств
#34. Генераторы множеств и генераторы словарей
#35. Функции: первое знакомство, определение def и их вызов
#36. Оператор return в функциях. Функциональное программирование
#37. Алгоритм Евклида для нахождения НОД
#38. Именованные аргументы. Фактические и формальные параметры
#39. Функции с произвольным числом параметров *args и **kwargs
#40. Операторы * и ** для упаковки и распаковки коллекций
#42. Анонимные (lambda) функции
#43. Области видимости переменных. Ключевые слова global и nonlocal
#45. Введение в декораторы функций
#46. Декораторы с параметрами. Сохранение свойств декорируемых функций
#47. Импорт стандартных модулей. Команды import и from
#48. Импорт собственных модулей
#49. Установка сторонних модулей (pip install). Пакетная установка
#50. Пакеты (package) в Python. Вложенные пакеты
#51. Функция open. Чтение данных из файла
#52. Исключение FileNotFoundError и менеджер контекста (with) для файлов
#53. Запись данных в файл в текстовом и бинарном режимах
#55. Функция-генератор. Оператор yield
#56. Функция map. Примеры ее использования
#57. Функция filter для отбора значений итерируемых объектов
#58. Функция zip. Примеры использования
#59. Сортировка с помощью метода sort и функции sorted
#60. Аргумент key для сортировки коллекций по ключу
#61. Функции isinstance и type для проверки типов данных
#62. Функции all и any. Примеры их использования
#63. Расширенное представление чисел. Системы счисления
#64. Битовые операции И, ИЛИ, НЕ, XOR. Сдвиговые операторы
#65. Модуль random стандартной библиотеки
#66. Аннотация базовыми типами
#67. Аннотации типов коллекций
#68. Аннотации типов на уровне классов
#69. Конструкция match/case. Первое знакомство
#70. Конструкция match/case с кортежами и списками
#71. Конструкция match/case со словарями и множествами
#72. Конструкция match/case. Примеры и особенности использования
© 2023 Частичное или полное копирование информации с данного сайта для распространения на других ресурсах, в том числе и бумажных, строго запрещено. Все тексты и изображения являются собственностью сайта
Что такое *args и **kwargs в Python?
Функции — это жизнь. Правда? Если вы только начали осваивать Python, неважно — первый ли это ваш язык программирования, или вы пришли в Python из другого языка, то вы уже знаете о том, что количество параметров в объявлении функции соответствует количеству аргументов, которые передают функции при вызове.
Это — основы. Это то, что помогает людям понимать окружающий мир. Но утверждение «количество параметров равно количеству аргументов» закладывает в голову новичка бомбу замедленного действия, которая срабатывает после того, как он увидит в объявлении функции таинственные конструкции *args или **kwargs .
Не позволяйте всяким значкам загонять себя в ступор. Тут нет ничего архисложного. В общем-то, если эти конструкции вам незнакомы — предлагаю с ними разобраться.
Позиционные и именованные аргументы
Для того чтобы разобраться с *args и **kwargs , нам нужно освоить концепции позиционных (positional) и именованных (keyword) аргументов.
Сначала поговорим о том, чем они отличаются. В простейшей функции мы просто сопоставляем позиции аргументов и параметров. Аргумент №1 соответствует параметру №1, аргумент №2 — параметру №2 и так далее.
def printThese(a,b,c): print(a, "is stored in a") print(b, "is stored in b") print(c, "is stored in c") printThese(1,2,3) """ 1 is stored in a 2 is stored in b 3 is stored in c """
Для вызова функции необходимы все три аргумента. Если пропустить хотя бы один из них — будет выдано сообщение об ошибке.
def printThese(a,b,c): print(a, "is stored in a") print(b, "is stored in b") print(c, "is stored in c") printThese(1,2) """ TypeError: printThese() missing 1 required positional argument: 'c' """
Если при объявлении функции назначить параметру значение по умолчанию — указывать соответствующий аргумент при вызове функции уже необязательно. Параметр становится опциональным.
def printThese(a,b,c=None): print(a, "is stored in a") print(b, "is stored in b") print(c, "is stored in c") printThese(1,2) """ 1 is stored in a 2 is stored in b None is stored in c """
Опциональные параметры, кроме того, можно задавать при вызове функции, используя их имена.
В следующем примере установим три параметра в значение по умолчанию None и взглянем на то, как их можно назначать, используя их имена и не обращая внимания на порядок следования аргументов, применяемых при вызове функции.
def printThese(a=None,b=None,c=None): print(a, "is stored in a") print(b, "is stored in b") print(c, "is stored in c") printThese(c=3, a=1) """ 1 is stored in a None is stored in b 3 is stored in c """
Оператор «звёздочка»
Оператор * чаще всего ассоциируется у людей с операцией умножения, но в Python он имеет и другой смысл.
Этот оператор позволяет «распаковывать» объекты, внутри которых хранятся некие элементы. Вот пример:
a = [1,2,3] b = [*a,4,5,6] print(b) # [1,2,3,4,5,6]
Тут берётся содержимое списка a , распаковывается, и помещается в список b .
Как пользоваться *args и **kwargs
Итак, мы знаем о том, что оператор «звёздочка» в Python способен «вытаскивать» из объектов составляющие их элементы. Знаем мы и о том, что существует два вида параметров функций. Вполне возможно, что вы уже додумались до этого сами, но я, на всякий случай, скажу об этом. А именно, *args — это сокращение от «arguments» (аргументы), а **kwargs — сокращение от «keyword arguments» (именованные аргументы).
Каждая из этих конструкций используется для распаковки аргументов соответствующего типа, позволяя вызывать функции со списком аргументов переменной длины. Например — создадим функцию, которая умеет выводить результаты, набранные учеником в тесте:
def printScores(student, *scores): print(f"Student Name: ") for score in scores: print(score) printScores("Jonathan",100, 95, 88, 92, 99) """ Student Name: Jonathan 100 95 88 92 99 """
Я не использовал при объявлении функции конструкцию *args . Вместо неё у меня — *scores . Нет ли тут ошибки? Ошибки здесь нет. Дело в том, что «args» — это всего лишь набор символов, которым принято обозначать аргументы. Самое главное тут — это оператор * . А то, что именно идёт после него, особой роли не играет. Благодаря использованию * мы создали список позиционных аргументов на основе того, что было передано функции при вызове.
После того, как мы разобрались с *args , с пониманием **kwargs проблем быть уже не должно. Имя, опять же, значения не имеет. Главное — это два символа ** . Благодаря им создаётся словарь, в котором содержатся именованные аргументы, переданные функции при её вызове.
def printPetNames(owner, **pets): print(f"Owner Name: ") for pet,name in pets.items(): print(f": ") printPetNames("Jonathan", dog="Brock", fish=["Larry", "Curly", "Moe"], turtle="Shelldon") """ Owner Name: Jonathan dog: Brock fish: ['Larry', 'Curly', 'Moe'] turtle: Shelldon """
Итоги
Вот несколько советов, которые помогут вам избежать распространённых проблем, возникающих при работе с функциями, и расширить свои знания:
- Используйте общепринятые конструкции *args и **kwargs для захвата позиционных и именованных аргументов.
- Конструкцию **kwarg s нельзя располагать до *args . Если это сделать — будет выдано сообщение об ошибке.
- Остерегайтесь конфликтов между именованными параметрами и **kwargs , в случаях, когда значение планируется передать как **kwarg -аргумент, но имя ключа этого значения совпадает с именем именованного параметра.
- Оператор * можно использовать не только в объявлениях функций, но и при их вызове.