*args **kwargs Python
Введение | |
*args | |
**kwargs | |
*args, **kwargs | |
Фильтрация **kwargs | |
kwargs.get() | |
Порядок следования | |
Positional-Only Arguments | |
Extended Call | |
Переадресация аргументов | |
Аргументы из командной строки | |
Похожие статьи |
Введение
В Python можно создавать функции, которые принимают заранее неизвестное число аргументов.
Это очень удобно, к тому же есть два варианта:
- *args - для позиционных аргументов
- **kwargs - для именованных аргументов
Вместо слов 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
Аргументы из командной строки
Если вам интересна тема вызова скриптов с аргументами из командной строки - рекомендую:
*args **kwargs | |
Лямбда функции | |
all() | |
any() | |
map() | |
zip() | |
sorted() | |
Функции первого класса | |
Замыкания | |
Декораторы | |
Кэширование | |
Python | |
if, elif, else | |
Циклы | |
Методы | |
enum |