Python: Passando lista dinâmica de argumentos para um função.

Atualmente estou trabalhando em um projeto pessoal que consiste em fazer um bot para o Telegram utilizando Python. A API do Telegram é muito flexível e permite vários tipos de interação com o usuário do bot. O problema é que, dependendo do caso, vou precisa de um grupo X ou Y de argumentos para a função de enviar mensagem. Dependendo do caso, vou precisar disponibilizar para o usuário um  teclado personalizado, outra hora vou precisar enviar um  texto formatado.  Para resolver este problema, podemos utilizar a lógica abaixo para uma solução rápida e simples.

Antes de qualquer outra coisa, para fins de exemplo, vamos considerar a função abaixo como base para os próximos exemplos. Todos eles irão chamar esta função:

def get_food(quantity, food="Bacon", express = True, is_awesome = True):
    """get_food: Sample function."""
    express_msg = "I'm hungry, please hurry!\r\n" if express else ""
    is_awesome_msg = "This is awesome!" if is_awesome else "nah..."
    print("%sHere's %s portions of %s.\r\n%s" % (express_msg, quantity, food, is_awesome_msg))

 

Como relembrar é viver, vamos revisar rapidamente algumas formas de se chamar esta função:

Primeira forma, informando os argumentos posicionalmente:

print("Just informing the mandatory argument...")
get_food(15)    

print("\r\n\r\nInforming all arguments by position...")
get_food(2, "Carrtos", False, False)

No exemplo acima, primeiro chamados a função get_food passando apenas o argumento obrigatório. Na segunda chamada, passamos todos os argumentos, mas de forma posicional, ou seja, na mesma ordem em que foram declarados na função.

A segunda forma é passar os argumentos nominalmente:

print("\r\n\r\nInforming the mandatory argument using it's name...")
get_food(quantity = 42)

print("\r\n\r\nInforming all arguments by name...")
get_food(quantity = 2, food = "Carrtos", express = False, is_awesome = False)

Neste segundo exemplo, fizemos igual ao anterior: Primeiro passamos apenas o argumento obrigatório e depois utilizamos todos os argumentos. A diferença é que os argumentos foram passados com os respectivos nomes. Sendo assim, ao contrário do exemplo anterior, não importa a ordem em que você fornece os argumentos. Sempre vai funcionar. Pessoalmente, acho mais organizado fazer desta forma.

Ok. Revisão feita. Vamos entrar no assunto deste post.

Para este exemplo, vamos criar mais 2 funções, mas estas vão atuar como wrapper da get_food, ou seja, elas vão encapsular a lógica e as regras envolvidas em chamar função get_food. Esta primeira função wrapper foi feita meramente para fins didáticos. Pelos poderes de grayskull! Não imagine que eu faria algo assim na vida real…

def wrapper_func_hard(quantity = None, food = None, express = None, is_awesome = None):
    """
    wrapper_func_hard: Sample wrapper function. 
    """
    
    if quantity is None:
        quantity = 42
    
    if food is None and express is None and is_awesome is None:
        get_food(quantity = quantity)
        return
        
    if food is not None and express is None and is_awesome is None:
        get_food(quantity = quantity, food = food)
        return
        
    if food is not None and express is not None and is_awesome is None:
        get_food(quantity = quantity, food = food, express = express)
        return
        
    if food is not None and express is not None and is_awesome is not None:
        get_food(quantity = quantity, food = food, express = express, is_awesome = is_awesome)
        return
    
    print("something went wrong... :(")

O que esta função está fazendo? Ela não possui argumentos obrigatórios, ou seja, você pode chama-la sem passar qualquer argumento e tudo funcionará. O pulo do gato deste tipo de função é que você pode controlar o fluxo de informações e como a função alvo (no caso, a get_food) vai ser chamada.

Neste caso, estou considerando 4 cenários:

  1. Nenhum argumento é passado: Chamará a função get_food, passando 42 no argumento quantidade;
  2. Passando apenas o argumento food (ou quantidade e food);
  3. Passando os argumentos food e express (podendo ou não existir o argumento quantidade);
  4. Passando todos os argumentos (podendo ou não existir o argumento quantidade);

Qualquer cenário que não se encaixe nestes 4 irá produzir um erro e para cada cenário, preciso repetir a chamada da função get_food. Péssimo, certo? Seria um pesadelo ter que conviver com um sistema cheio de funções deste tipo.

Veja o resultado da chamada desta função:

Case 1: No args...
I'm hungry, please hurry!
Here's 42 portions of Bacon.
This is awesome!

Case 2: Only first arg...
I'm hungry, please hurry!
Here's 12 portions of Bacon.
This is awesome!

Case 3: Only first 2 args...
I'm hungry, please hurry!
Here's 12 portions of cheese.
This is awesome!

Case 4: Only first 3 args...
I'm hungry, please hurry!
Here's 12 portions of cheese.
This is awesome!

Case 5: All 4 args...
I'm hungry, please hurry!
Here's 12 portions of cheese.
This is awesome!

Case 6: Skipping one arg and breaking things...
something went wrong... :(

Se faltar um argumento, mesmo que opcional, ocorre um erro. Para nossa alegria, o Python provê uma maneira mais fácil de tratar este tipo de situação.

O segredo está em montar um dicionário com os argumentos que você quer passar e utiliza-lo na função, com o prefixo **. Veja abaixo o exemplo da segunda função wrapper, que utiliza este método:

def wrapper_func_easy(quantity = None, food = None, express = None, is_awesome = None):
    """
    wrapper_func_easy: Sample wrapper function. 
    """
    
    dict_args = {}
    
    dict_args['quantity'] = 42 if quantity is None else quantity
    
    if food is not None:
        dict_args['food'] = food
        
    if express is not None:
        dict_args['express'] = express

    if is_awesome is not None:
        dict_args['is_awesome'] = is_awesome
  
    get_food(**dict_args)

Ela é menor que a anterior, mais fácil de ler e de implementar possíveis tratamentos futuros nas informações que precisam ser passadas para a função get_food. Outro ponto positivo desta abordagem é que ela abrange todas as possibilidades que a própria função alvo oferece.

Abaixo está o log de execução desta função, para os mesmos casos de uso da anterior:

Case 1: No args...
I'm hungry, please hurry!
Here's 42 portions of Bacon.
This is awesome!

Case 2: Only first arg...
I'm hungry, please hurry!
Here's 12 portions of Bacon.
This is awesome!

Case 3: Only first 2 args...
I'm hungry, please hurry!
Here's 12 portions of cheese.
This is awesome!

Case 4: Only first 3 args...
I'm hungry, please hurry!
Here's 12 portions of cheese.
This is awesome!

Case 5: All 4 args...
I'm hungry, please hurry!
Here's 12 portions of cheese.
This is awesome!

Case 6: Skipping one arg and NOT breaking things...
I'm hungry, please hurry!
Here's 12 portions of cheese.
This is awesome!

 

Momento TL:DR: Ao invés de ficar agonizando na hora de definir como mandar X ou Y argumentos para uma função, crie um dicionário (nome_do_argumento:valor) e passe ele para a função, colocando ** antes do nome da variável. Exemplo: get_food(**dict_args)

Você também pode criar uma tuple ou um vetor e fazer a mesma coisa. A diferença é que, se os argumentos não estiverem com seus respectivos nomes, eles precisam estar na ordem em que foram declarados na função.

Fonte destes exemplos no GitHub: https://github.com/brenordv/python-snippets/tree/master/pass-dynamic-args-to-func

Espero ter ajudado.

The following two tabs change content below.
Arquiteto de Software e Desenvolvedor Backend (quase Fullstack), geralmente trabalho com C#, PowerShell, Python, Golang, bash e Unity (esse é mais por hobby). Estou sempre buscando algo novo para aprender, adicionando novas ferramentas ao meu cinto de utilidades.
Posted in Dev, Python and tagged , , , , , , , .