Распаковка аргументов#
В Python выражения *args и **kwargs позволяют выполнять ещё одну задачу — распаковку аргументов.
До сих пор мы вызывали все функции вручную. И соответственно передавали все нужные аргументы.
В реальности, как правило, данные необходимо передавать в функцию программно. И часто данные идут в виде какого-то объекта Python.
Распаковка позиционных аргументов#
Например, при форматировании строк часто надо передать методу format несколько аргументов. И часто эти аргументы уже находятся в списке или кортеже. Чтобы их передать методу format, приходится использовать индексы таким образом:
In [1]: items = [1,2,3] In [2]: print('One: <>, Two: <>, Three: <>'.format(items[0], items[1], items[2])) One: 1, Two: 2, Three: 3
Вместо этого, можно воспользоваться распаковкой аргументов и сделать так:
In [4]: items = [1,2,3] In [5]: print('One: <>, Two: <>, Three: <>'.format(*items)) One: 1, Two: 2, Three: 3
Еще один пример — функция config_interface (файл func_config_interface_unpacking.py):
In [8]: def config_interface(intf_name, ip_address, mask): ..: interface = f'interface intf_name>' ..: no_shut = 'no shutdown' ..: ip_addr = f'ip address ip_address> mask>' ..: result = [interface, no_shut, ip_addr] ..: return result ..:
Функция ожидает такие аргументы:
Функция возвращает список строк для настройки интерфейса:
In [9]: config_interface('Fa0/1', '10.0.1.1', '255.255.255.0') Out[9]: ['interface Fa0/1', 'no shutdown', 'ip address 10.0.1.1 255.255.255.0'] In [11]: config_interface('Fa0/10', '10.255.4.1', '255.255.255.0') Out[11]: ['interface Fa0/10', 'no shutdown', 'ip address 10.255.4.1 255.255.255.0']
Допустим, нужно вызвать функцию и передать ей информацию, которая была получена из другого источника, к примеру, из БД.
Например, список interfaces_info, в котором находятся параметры для настройки интерфейсов:
In [14]: interfaces_info = [['Fa0/1', '10.0.1.1', '255.255.255.0'], . : ['Fa0/2', '10.0.2.1', '255.255.255.0'], . : ['Fa0/3', '10.0.3.1', '255.255.255.0'], . : ['Fa0/4', '10.0.4.1', '255.255.255.0'], . : ['Lo0', '10.0.0.1', '255.255.255.255']] . :
Если пройтись по списку в цикле и передавать вложенный список как аргумент функции, возникнет ошибка:
In [15]: for info in interfaces_info: . : print(config_interface(info)) . : --------------------------------------------------------------------------- TypeError Traceback (most recent call last) ipython-input-15-d34ced60c796> in module> 1 for info in interfaces_info: ----> 2 print(config_interface(info)) 3 TypeError: config_interface() missing 2 required positional arguments: 'ip_address' and 'mask'
Ошибка вполне логичная: функция ожидает три аргумента, а ей передан 1 аргумент — список.
В такой ситуации пригодится распаковка аргументов. Достаточно добавить * перед передачей списка как аргумента, и ошибки уже не будет:
In [16]: for info in interfaces_info: . : print(config_interface(*info)) . : ['interface Fa0/1', 'no shutdown', 'ip address 10.0.1.1 255.255.255.0'] ['interface Fa0/2', 'no shutdown', 'ip address 10.0.2.1 255.255.255.0'] ['interface Fa0/3', 'no shutdown', 'ip address 10.0.3.1 255.255.255.0'] ['interface Fa0/4', 'no shutdown', 'ip address 10.0.4.1 255.255.255.0'] ['interface Lo0', 'no shutdown', 'ip address 10.0.0.1 255.255.255.255']
Python сам „распакует“ список info и передаст в функцию элементы списка как аргументы.
Таким же образом можно распаковывать и кортеж.
Распаковка ключевых аргументов#
Аналогичным образом можно распаковывать словарь, чтобы передать его как ключевые аргументы.
Функция check_passwd (файл func_check_passwd_optional_param_2.py):
In [19]: def check_passwd(username, password, min_length=8, check_username=True): . : if len(password) min_length: . : print('Пароль слишком короткий') . : return False . : elif check_username and username in password: . : print('Пароль содержит имя пользователя') . : return False . : else: . : print(f'Пароль для пользователя username> прошел все проверки') . : return True . :
Список словарей username_passwd , в которых указано имя пользователя и пароль:
In [20]: username_passwd = ['username': 'cisco', 'password': 'cisco'>, . : 'username': 'nata', 'password': 'natapass'>, . : 'username': 'user', 'password': '123456789'>]
Если передать словарь функции check_passwd, возникнет ошибка:
In [21]: for data in username_passwd: . : check_passwd(data) . : --------------------------------------------------------------------------- TypeError Traceback (most recent call last) ipython-input-21-ad848f85c77f> in module> 1 for data in username_passwd: ----> 2 check_passwd(data) 3 TypeError: check_passwd() missing 1 required positional argument: 'password'
Ошибка такая, так как функция восприняла словарь как один аргумент и считает что ей не хватает только аргумента password.
Если добавить ** перед передачей словаря функции, функция нормально отработает:
In [22]: for data in username_passwd: . : check_passwd(**data) . : Пароль слишком короткий Пароль содержит имя пользователя Пароль для пользователя user прошел все проверки In [23]: for data in username_passwd: . : print(data) . : check_passwd(**data) . : 'username': 'cisco', 'password': 'cisco'> Пароль слишком короткий 'username': 'nata', 'password': 'natapass'> Пароль содержит имя пользователя 'username': 'user', 'password': '123456789'> Пароль для пользователя user прошел все проверки
Python распаковывает словарь и передает его в функцию как ключевые аргументы. Запись check_passwd(**data) превращается в вызов вида check_passwd(username=’cisco’, password=’cisco’) .
Аргументы функции python словарь
Одной из распространенных сфер, где применяются упаковка и распаковка — это параметры функций. Так, в определениях различных функций нередко можно увидеть, что они принимают такие параметры как *args и **kwargs .
Термины args и kwargs — это соглашения по программированию на Python, в реальности вместо них можно использовать любые именования. *args представляет параметры, которые передаются по позиции. А **kwargs означает параметры, которые передаются по имени. обозначает аргументы ключевого слова.
Оператор * применяется с любым итерируемым объектом (например, кортежем, списком и строками). Тогда как оператор ** можно использовать только со словарями.
*args
Оператор * позволяет передать в функцию несколько значений, и все они будут упакованы в кортеж:
def fun(*args): # обращаемся к первому элементу кортежа print(args[0]) # выводим весь кортеж print(args) fun("Python", "C++", "Java", "C#")
Здесь функция fun принимает кортеж значений. При вызове мы можем передать ей различное количество значений. Так, в примере выше передается четыре строки, которые образуют кортеж. Консольный вывод программы:
Благодаря такой возможности мы можем передавать в функцию переменное количество значений:
def sum(*args): result = 0 for arg in args: result += arg return result print(sum(1, 2, 3)) # 6 print(sum(1, 2, 3, 4)) # 10 print(sum(1, 2, 3, 4, 5)) # 15
Оператор **
Оператор ** упаковывает аргументы, переданные по имени, в словарь. Имена параметров служат ключами. Например, определим функцию, которая просто будет выводить все переданные параметры
def fun(**kwargs): print(kwargs) # выводим словарь на консоль fun(name="Tom", age="38", company="Google") fun(language="Python", version="3.11")
Консольный вывод программы:
Поскольку аргументы передаются в функцию в виде словаря, то внутри функции через ключи мы можем получить их значения:
def fun(**kwargs): for key in kwargs: print(f" = ") fun(name="Tom", age="38", company="Google")
Консольный вывод программы:
name = Tom age = 38 company = Google
Распаковка аргументов
Выше было описано, как операторы * и ** применяются для упаковки аругментов в кортеж и словарь соответственно. Но эти же операторы могут использоваться для распаковки.
Оператор * и распаковка
Сначала рассмотрим ситуацию, где это может пригодиться.Пусть мы передаем в функцию кортеж:
def sum(*args): result = 0 for arg in args: result += arg return result numbers = (1, 2, 3, 4, 5) print(sum(numbers))
Здесь в вызов функции sum передается кортеж. Параметр *args по сути тоже представляет кортеж, и кажется, все должно работать. Тем не менее мы столкнемся с ошибкой
TypeError: unsupported operand type(s) for +=: 'int' and 'tuple'
То есть в данном случае кортеж numbers передается как элемент кортежа *args .
И чтобы элементы кортежа были переданы в кортеж *args как отдельные значения, необходимо выполнить их распаковку:
def sum(*args): result = 0 for arg in args: result += arg return result numbers = (1, 2, 3, 4, 5) # применяем распаковку - *numbers print(sum(*numbers)) # 15
Здесь при передачи кортежа numbers в функцию sym применяется распаковка: *numbers
Другим случаем распаковки может быть ситуация, когда функция принимает несколько параметров, а мы передаем один кортеж или список:
def print_person(name, age, company): print(f"Name:, Age: , Company: ") person =("Tom", 38, "Google") # выполняем распаковку кортежа person print_person(*person) # Name:Tom, Age: 38, Company: Google
В данном случае выражение *person раскладывает кортеж person на отдельные значения, которые передаются параметрам name, age и company.
Оператор ** и распаковка
Оператор ** применяется для распаковки словарей:
def print_person(name, age, company): print(f"Name:, Age: , Company: ") tom = # выполняем распаковку словаря tom print_person(**tom) # Name:Tom, Age: 38, Company: Google
Здесь выражение **tom раскладывает словарь на отдельные значения, которые передаются параметрам name, age и company по названию ключей.
Сочетание параметров
Параметры *args и *kwargs могут использоваться в функции вместе с другими параметрами. Например:
def sum(num1, num2, *nums): result=num1+num2 for n in nums: result += n return result print(sum(1,2,3)) # 6 print(sum(1,2,3,4)) # 10