*args **kwargs Python

Содержание
Введение
*args
**kwargs
*args, **kwargs
Фильтрация **kwargs
kwargs.get()
Порядок следования
Positional-Only Arguments
Extended Call
Переадресация аргументов
Аргументы из командной строки
Похожие статьи

Введение

В Python можно создавать функции, которые принимают заранее неизвестное число аргументов.

Это очень удобно, к тому же есть два варианта:

Вместо слов args, kwargs можно использовать другие, главное чтобы было правильное количество *

Тем не менее, если против этого нет особых причин, желательно следовать общей практике.

Перед изучением этой статьи советую ознакомится с главой Параметры и аргументы из статьи Функции

В англоязычной литературе используются термин Extended Formal Argument Syntax и Arbitraty Keyword Arguments

С помощью *args передаётся заранее неизвестное число позиционных аргументов .

С помощью **kwargs передаётся заранее неизвестное число именованых аргументов .

Начнём с *args

*args

Рассморим скрипт args_demo.py

def myfunc(*args): print(args) print(type(args)) myfunc(50, 70, 120, 3, 14) myfunc('Barcelona', 'Malaga', 'Riga')

python args_demo.py

python args.py (50, 70, 120, 3, 14) <class 'tuple'> ('Barcelona', 'Malaga', 'Riga') <class 'tuple'>

Как видно из примера: одна и та же функция смогла обработать сперва пять аргументов типа int а затем три аргумента типа str

Использовать * нужно только в объявлении функции.

Аргументы передаются как кортеж

Ничто не мешает перебрать полученные аргументы по одному.

Воспользуемся циклом for

def myfunc(*args): for item in args: print(item) myfunc(50, 70, 120, 3, 14) myfunc('a','a','a')

python args_demo.py

50
70
120
3
14
a
a
a

Пример функции, которая складывает неизвестное заранее число аргументов.

Про функции iter() и next() можно прочитать в статье итерация в Python

def my_sum(*args): i = iter(args) mysum = next(i) for ar in i: mysum += ar return mysum print(my_sum(50, 70, 120, 3, 14)) print(my_sum('a', 'b', 'c'))

257 abc

Пример функции, которая умножает заранее неизвестное число аргументов.

Для демонстрации используем вместо *args *length

def hypervolume(*lengths): i = iter(lengths) v = next(i) for length in i: v *= length return v print(hypervolume(2, 4)) # 8 print(hypervolume(2, 4, 6)) # 48 print(hypervolume(2, 4, 6, 8)) # 384 print(hypervolume(1)) # 384

python hypervolume.py

8 48 384 1

print(hypervolume())

Traceback (most recent call last): File "/home/andrei/python/hypervolume.py", line 14, in <module> print(hypervolume()) File "/home/andrei/python/hypervolume.py", line 3, in hypervolume v = next(i) StopIteration

Так как ни одного аргумента не передано итерацию произвести невозможно. Появляется ошибка StopIteration

Заменить эту ошибку на более понятную можно изменив код функции так, чтобы сперва принимался один позиционный аргумент. Так можно упростить жизнь пользователям, которые мало знакомы с итерацией в Python

def hypervolume(length, *lengths): v = length for item in lengths: v *= item return v print(hypervolume(2, 4)) # 8 print(hypervolume(2, 4, 6)) # 48 print(hypervolume(2, 4, 6, 8)) # 384 print(hypervolume(1)) # 1 print(hypervolume())

8 48 384 1 Traceback (most recent call last): File "/home/andrei/python/hypervolume.py", line 30, in <module> print(hypervolume()) TypeError: hypervolume() missing 1 required positional argument: 'length'

Показанный выше приём использования позиционного аргумента для обязательного аргумента и * аргументов для необязательных довольно популярен.

**kwargs

Использование **kwagrs позволяет передавать в функцию не простые аргументы, а аргументы в виде ключевых слов.

Это очень удобно, если вам нужно работать с разными пользовательскими сценариями - не нужно вводить какой-то определённый порядок аргументов, как в Bash скриптах

Каждый аргумент получает своё название и может быть обработан вне зависимости от порядка.

**kwargs передаются в виде словаря

Методы

Существуют встроенные методы для работы с kwargs

Начнём с методов keys() и values() которые возвращают имя ключа и значение.

Вызовем функцию filterkw() с тремя аргументами-ключами a=1, b=2, c=3 и выведем их в терминал отдельно друг от друга

def filterkw(**kwargs): for k in kwargs.keys(): print(f"key: {k}") for v in kwargs.values(): print(f"value: {v}") if __name__ == "__main__": filterkw(a=1, b=2, c=3)

key: a key: b key: c value: 1 value: 2 value: 3

Метод get() возвращает значение по ключу

def filterkw(**kwargs): filters = ["b", "c"] for f in filters: print(kwargs.get(f)) if __name__ == "__main__": filterkw(a=1, b=2, c=3)

2 3

Если такого ключа нет get() возвращает None

def filterkw(**kwargs): filters = ["b", "c", "z"] for f in filters: print(kwargs.get(f)) if __name__ == "__main__": filterkw(a=1, b=2, c=3)

2 3 None

Пример

Рассморим скрипт kwargs_demo.py

def myfunc(**kwargs): if 'website' in kwargs: print('Заходите на сайт {}'.format(kwargs['website'])) else: print('Посетите topbicycle.ru') myfunc(website='HeiHei.ru') myfunc(localsite='aredel.com') myfunc(website='eth1.ru', author='Andrey Olegovich')

python kwargs_demo.py

Заходите на сайт HeiHei.ru
Посетите topbicycle.ru
Заходите на сайт eth1.ru

Функция ожидает аргумент с ключом website

При первом вызове такой аргумент приходит один

Во втором вызове не приходит ключа website и срабатывает else

Во время третьего вызова приходи website и author, но author функция не ждёт и он просто игнорируется

Добавим ещё один вызов функции

myfunc(website='eth1.ru', author='Andrey Olegovich', 2)

python kwargs_demo.py

File "args_demo.py", line 29 myfunc3(website='eth1.ru', author='Andrey Olegovich', 2) SyntaxError: non-keyword arg after keyword arg

Как видите, принимать обычный аргумент функция не хочет. Чтобы узнать как решается эта задача - переходите к следующей главе.

*args **kwargs

Чтобы вызывать функции как с позиционными аргументами так и именованными нужно в объявлении функции указать и *args и **kwargs.

При вызове функции сперва нужно перечислить все позиционные (обычные) аргументы а затем именованные (ключевики)

Рассморим скрипт args_kwargs_demo.py

def myfunc(*args,**kwargs): if 'website' in kwargs: print('Заходите на сайт {}'.format(kwargs['website'])) else: print('Посетите topbicycle.ru') myfunc(2, website='eth1.ru', author='Andrey Olegovich')

python args_kwargs_demo.py

Заходите на сайт eth1.ru

Если вы получили ошибку

File "args_demo.py", line 21 SyntaxError: Non-ASCII character '\xd0' in file args_demo.py on line 21, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

Добавьте следующий код на первую строку

# coding=utf-8

Подробности здесь

Одно из самых распространённых применений *args, **kwargs - это переадресация аргументов (Argument Forwarding)

Фильтрация **kwargs

Иногда бывает нужно проводить действия только с определёнными именованными аргументами.

Остальные нужно отфильтровать. О том как это сделать с помощью генератора словарей (dict comprehension) вы узнаете в этом параграфе.

Предположим функция filterkw() принимает **kwargs.

Создадим список допустимых аргументов (filter) и будем выводить на экран только их.

Для этого, с помощью генератора словарей с условием, создадим словарь (params) во время создания которого каждый kwarg будем проверять на принадлежность к списку.

def filterkw(**kwargs): filters = ["limit", "offset", "sortkey", "sortdir"] params = {k: kwargs[k] for k in kwargs.keys() if k in filters} print(params) if __name__ == '__main__': filterkw(test=2, more=3, new=40) filterkw(limit=25, some=50, offset=40) filterkw(limit=1, offset=2, sortkey=90, sortdir=100)


{'limit': 25, 'offset': 40}
{'limit': 1, 'offset': 2, 'sortkey': 90, 'sortdir': 100}

Первый вызов ни к чему не привёл, так как ни test, ни more, ни new не являются нужными ключами.

Второй вызов содержал два нужных ключа limit и offset. some был проигнорирован

Третий вызов содержал все нужные ключи и они все были выведены на экран

Возможно для каких-то задач более эффективным будет перебирать элементы из фильтра и затем проверять на принадлежность к kwargs

def filterkw(**kwargs): filters = ["limit", "offset", "sortkey", "sortdir"] params = {k: kwargs[k] for k in filters if k in kwargs.keys()} print(params)

kwargs.get()

Пример фильтрации аргументов с использоанием get()

def filterkw(**kwargs): filters = ["a", "e", "i", "o"] params = {k: kwargs.get(k) for k in filters if kwargs.get(k)} print(params) if __name__ == "__main__": filterkw(a=1, b=2, c=3, d=4, e=5)

python list_comp_filter.py

{'a': 1, 'e': 5}

С помощью kwargs.get() можно задавать значения по умолчанию kw аргументов в зависимости от других аргументов.

def set_kw_arg(url, auth, **kwargs): pwd_prompt = kwargs.get( "expect_pwd_prompt", False if auth.lower() == "cert" else True ) print(pwd_prompt) if __name__ == "__main__": set_kw_arg("https://heihei.ru", "cert") # Will use default -> False set_kw_arg("https://heihei.ru", "cert", expect_pwd_prompt=True) # True set_kw_arg("https://heihei.ru", "pwd") # Will use default -> True set_kw_arg("https://heihei.ru", "pwd", expect_pwd_prompt=False) # False

python kw_get.py

False True True False

def tag(name, **kwargs): print(name) print(kwargs) print(type(kwargs)) m = tag('img', src="Malaga.jpg", alt="Malaga Fortress", border=1) print(m)

python tag_func.py

img {'src': 'Malaga.jpg', 'alt': 'Malaga Fortress', 'border': 1} <class 'dict'> None

def tag(name, **attributes): result = '<' + name for key, value in attributes.items(): result += ' {k}="{v}"'.format(k=key, v=str(value)) result += '>' return result m = tag('img', src="Malaga.jpg", alt="Malaga Fortress", border=1) print(m)

<img src="Malaga.jpg" alt="Malaga Fortress" border="1">

Порядок следования

Порядок передачи аргументов жёстко регламетирован.

Сперва идут обязательные позиционные аргументы, затем *args для необязательных, затем обязательные именованные и **kwargs для необязательных именованных, после необязательных именованных аргументов уже нельзя передавать обязательные.

Неправильно:

*args, arg1
arg1, arg2, *args, arg3
**kwargs, arg1
**kwargs, *args
arg1, *args, **kwargs, kwarg1="value"
arg1, *args, kwarg1="value", **kwargs, kwarg2="newValue"

Правильно:

arg1, arg2, *args, kwarg1="x", kwarg2=1961, **kwargs

def print_args(arg1, arg2, *args, kwarg1, kwarg2): print(arg1) print(arg2) print(args) print(kwarg1) print(kwarg2) print_args(1, 2, 3, 4, 5, kwarg1=6, kwarg2=7)

python order_arg_kwarg.py

1 2 (3, 4, 5) 6 7

Видно, что *args это кортеж. Извлечь из него отдельные значения можно выполнив

print(args[2])

5

Нельзя просто перечислить аргументы, обязательно нужно передать последние два как именованные

print_args(1, 2, 3, 4, 5, 6, 7)

Traceback (most recent call last): File "/home/andrei/python/order_arg_kwarg.py", line 10, in <module> print_args(1, 2, 3, 4, 5, 6, 7) TypeError: print_args() missing 2 required keyword-only arguments: 'kwarg1' and 'kwarg2'

Благодаря жёскому условию на порядок аргументов, с помощью * можно запретить передачу позиционных аргументов после обязательных.

Для этого нужно поставить * (без args, только *) после последнего нужного обязательного позиционного аргумента.

def name_tag(first_name, last_name, *, title=''): print(title, first_name, last_name) name_tag('Eugene', 'Kaspersky', title='Kaspersky founder')

Kaspersky founder Eugene Kaspersky

Теперь попробуем передать лишний позиционный аргумент:

name_tag('Geert', 'Jan', 'Bruinsma', title='Booking.com founder')

Traceback (most recent call last): File "/home/andrei/python/order_arg_kwarg.py", line 21, in <module> name_tag('Geert', 'Jan', 'Bruinsma', title='Booking.com founder') TypeError: name_tag() takes 2 positional arguments but 3 positional arguments (and 1 keyword-only argument) were given

Без * ошибка была бы следующей

Traceback (most recent call last): File "/home/andrei/python/order_arg_kwarg.py", line 21, in <module> name_tag('Geert', 'Jan', 'Bruinsma', title='Booking.com founder') TypeError: name_tag() got multiple values for argument 'title'

Positional-Only Arguments

Именованные аргументы нужно передавать как именованные, позиционные можно передавать как позиционные и как именованные

def add(term1, term2): return term1 + term2 print(add(1, 2)) # Именованный не должен быть перед # позиционным. Нельзя: # print(add(term1=1, 2)) # Можно: print(add(1, term2=2)) print(add(term1=1, term2=2))

python positional_only.py

3 3 3

Начиная с Python 3.8 можно использовать / чтобы ограничить использование позиционных аргументов.

Если / добавлен после позиционных аргументов - их больше нельзя передавать как именованные.

Официальная документаци в PEP 570

def add(term1, term2, /): return term1 + term2 print(add(1, 2)) # Теперь будет ошибка: print(add(1, term2=2))

3 Traceback (most recent call last): File "/home/andrei/python/positional_only.py", line 21, in <module> print(add(1, term2=2)) TypeError: add() got some positional-only arguments passed as keyword arguments: 'term2'

Эта фича нужна для повышения совместимости с модулями, написанными на Си и других языках.

Пример

range(start=1, stop=10))

Traceback (most recent call last): File "/home/andrei/python/positional_only.py", line 1, in <module> print(range(start=1, stop=10)) TypeError: range() takes no keyword arguments

Также запрет на использование именованных аргументов гарантирует зависимость API от имен.

Пользователи вашего API не смогут придумать свои имена и использовать их, а потом жаловаться, что что-то сломалось.

Extended Call

С помощью * можно делать так называемые расширенные вызовы (Extended Calls) набирая позиционные аргументы из итерируемых объектов, таких как кортежи.

def print_args(arg1, arg2, *args): print(arg1) print(arg2) print(args) t = (11, 12, 13, 14) print_args(*t)

python extended_call.py

11 12 (13, 14)

С помощью ** можно распаковать словарь и передать его в именованные аргументы

def color(red, green, blue, **kwargs): print("r =", red) print("g =", green) print("b =", blue) print(kwargs) k = {'red': 21, "green": 68, 'blue': 120, 'alpha': 52} # Или # k = dict(red=21, green=68, blue=120, alpha=52) color(**k)

r = 21 g = 68 b = 120 {'alpha': 52}

Переадресация аргументов

С помощью * и ** можно передавать аргументы одной функции в другую, не зная заранее, какой набор аргументов содержится в источнике.

В качестве примера проследим за передачей аргументов в функцию int()

Функция trace() ничего не знает про тип и количество аргументов у int()

# Argument Forwarding # Напрямую вызовем int() чтобы затем сравнить результат print(int("ff", 16)) def trace(f, *args, **kwargs): print("args =", args) print("kwargs =", kwargs) result = f(*args, **kwargs) print("result =", result) return result # Передадим int() в trace() чтобы промониторить аргументы trace(int, "ff", base=16)

255 args = ('ff',) kwargs = {'base': 16} result = 255

Аргументы из командной строки

Если вам интересна тема вызова скриптов с аргументами из командной строки - рекомендую:

Похожие статьи
Функции
Python
sys.argv: аргументы командной строки
Лямбда функции
Функции первого класса
Замыкания
Декораторы
Кэширование
if, elif, else
Циклы
Методы
Итерация
Генераторы
Генераторы: Maintaining State
Встроенные фукнции: all()zip()

Поиск по сайту

Подпишитесь на Telegram канал @aofeed чтобы следить за выходом новых статей и обновлением старых

Перейти на канал

@aofeed

Задать вопрос в Телеграм-группе

@aofeedchat

Контакты и сотрудничество:
Рекомендую наш хостинг beget.ru
Пишите на info@urn.su если Вы:
1. Хотите написать статью для нашего сайта или перевести статью на свой родной язык.
2. Хотите разместить на сайте рекламу, подходящую по тематике.
3. Реклама на моём сайте имеет максимальный уровень цензуры. Если Вы увидели рекламный блок недопустимый для просмотра детьми школьного возраста, вызывающий шок или вводящий в заблуждение - пожалуйста свяжитесь с нами по электронной почте
4. Нашли на сайте ошибку, неточности, баг и т.д. ... .......
5. Статьи можно расшарить в соцсетях, нажав на иконку сети: