Imaginem uma função que recebe alguns parâmetros numéricos e no final imprime na tela o resultado de cada parâmetro multiplicado por 10:
>>> def func(a, b, c):
... print(a * 10)
... print(b * 10)
... print(c * 10)
...
>>> func(4, 6, 3)
40
60
30
À primeira vista já podemos notar um ponto no código que está estranho, com código repetitivo, que são as 3 linhas da função. As 3 linhas realizam a mesma operação, o que muda é a variável recebida pela função. Esse por si só já é um problema, pois repetições no código em programação não são uma boa ideia e são muitas vezes substituídos por laços.
Agora imaginem que eu preciso imprimir 4 valores na tela. Vou ter que refatorar minha função para receber um argumento a mais e só então conseguir chamar a função e obter o resultado desejado.
>>> func(5, 3, 2, 10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func() takes 3 positional arguments but 4 were given
>>> def func_refatorada(a, b, c, d):
... print(a * 10)
... print(b * 10)
... print(c * 10)
... print(d * 10)
...
>>> func_refatorada(5, 3, 2, 10)
50
30
20
100
Percebam que temos dois problemas: a repetição de código no corpo da função e a necessidade de sempre refatorar a função sempre que eu mude a quantidade de parâmetros, pois se eu precisar agora passar 5 argumentos ou 2 argumentos ou 3, a função irá retornar com um erro e não com o retorno esperado:
>>> func_refatorada(2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func_refatorada() missing 2 required positional arguments: 'c' and 'd'
>>> func_refatorada(2, 3, 5, 6, 8)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func_refatorada() takes 4 positional arguments but 5 were given
Podemos resolver esse problema usando *args. Na verdade, pode ser qualquer nome de variável com um asterisco na frente. *args querem dizer número de argumentos variável.
>>> def func_args(*args):
... print(args)
... for arg in args:
... print(arg)
...
>>> func_args(5, 3, 2, 10)
(5, 3, 2, 10)
5
3
2
10
Notem que agora minha função empacota todos os argumentos em uma tupla e a partir dela eu posso fazer o que quiser com meu número variável de argumentos.
>>> func_args(2, 3, 5, 1, 2, 9, 8)
(2, 3, 5, 1, 2, 9, 8)
2
3
5
1
2
9
8
>>> func_args(2, 3)
(2, 3)
2
3
E eu não preciso passar como parâmetro somente *args, posso passar parâmetros isolados e por fim adicionar *args aos parâmetros de minha função:
>>> def func_args_2(a, b, *variaveis):
... print(a)
... print(b)
... print(variaveis)
...
>>> func_args_2(2, 3, 5, 1, 4, 7)
2
3
(5, 1, 4, 7)
Percebam que mudei o nome do parâmetro, de *args para *variaveis, e o resultado foi o mesmo, então tanto faz o nome da variável, o que importa é o asterisco na frente do nome da variável.
Também posso passar como argumento um objeto chave-valor, ou seja, um dicionário, usando dois asteriscos antes do nome da variável. Aqui, usamos os **kwargs, mas que novamente pode ser qualquer nome de variável, contanto que tenha os dois asteriscos:
>>> def func_kwargs(**kwargs):
... print(kwargs)
>>> dic = {'one': 1, 'two': 2}
>>> func_kwargs(**dic)
{'one': 1, 'two': 2}
>>> func_kwargs(one=1)
{'one': 1}
Portanto, para usar um dicionário dentro de uma função, precisamos usar **kwargs. Com o dicionário sendo passado como argumento para dentro da função, podemos realizar tarefas típicas de dicionário como a obtenção do valor do valor associado a aquela chave.