*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

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

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

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

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

Начнём с методов 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='urn.su', author='Andrey Olegovich')

python kwargs_demo.py

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

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

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

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

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

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

myfunc(website='urn.su', author='Andrey Olegovich', 2)

python kwargs_demo.py

File "args_demo.py", line 29 myfunc3(website='urn.su', 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='urn.su', author='Andrey Olegovich')

python args_kwargs_demo.py

Заходите на сайт urn.su

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

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

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

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

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

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

def filterkw(**kwargs): filters = ["limit", "offset", "sortkey", "sortdir"] params = {k: kwargs[k] for k in filters if k in kwargs.keys()} 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.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}

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

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

# Argument Forwarding def trace(f, *args, **kwargs): print("args =", args) print("kwargs =", kwargs) result = f(*args, **kwargs) print("result =", result) return result trace(int, "ff", base=16)

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

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

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