Argumentos de longitud variable en Python con * args y ** kwargs

    Introducción

    Algunas funciones no tienen argumentos, otras tienen múltiples. Hay ocasiones en que tenemos funciones con argumentos que no conocemos de antemano. Es posible que tengamos un número variable de argumentos porque queremos ofrecer una API flexible a otros desarrolladores o no conocemos el tamaño de entrada. Con Python, podemos crear funciones para aceptar cualquier cantidad de argumentos.

    En este artículo, veremos cómo podemos definir y usar funciones con argumentos de longitud variable. Estas funciones pueden aceptar una cantidad desconocida de entrada, ya sea como entradas consecutivas o como argumentos con nombre.

    Usar muchos argumentos con * args

    Implementemos una función que encuentre el valor mínimo entre dos números. Se vería así:

    def my_min(num1, num2):
        if num1 < num2:
            return num1
        return num2
    
    my_min(23, 50)
    
    23
    

    Simplemente verifica si el primer número es menor que el segundo. Si es así, se devuelve el primer número. De lo contrario, se devuelve el segundo número.

    Si deseamos encontrar un mínimo de 3 números, podemos agregar otro argumento a my_min() y más declaraciones if. Si nuestra función mínima necesita encontrar el número más bajo de cualquier cantidad indeterminada, podemos usar una lista:

    def my_min(nums):
        result = nums[0]
        for num in nums:
            if num < result:
                result = num
        return result
    
    my_min(4, 5, 6, 7, 2)
    
    2
    

    Si bien esto funciona, nuestros codificadores ahora tienen que incluir sus números en una lista, lo que no es tan sencillo como lo era cuando teníamos dos o tres argumentos definidos. Obtengamos lo mejor de ambos mundos con argumentos de longitud variable.

    Los argumentos de longitud variable, varargs para abreviar, son argumentos que pueden requerir una cantidad de entrada no especificada. Cuando se utilizan, el programador no necesita envolver los datos en una lista o secuencia alternativa.

    En Python, los varargs se definen usando el *args sintaxis. Reimplementemos nuestro my_min() funcionar con *args:

    def my_min(*args):
        result = args[0]
        for num in args:
            if num < result:
                result = num
        return result
    
    my_min(4, 5, 6, 7, 2)
    
    2
    

    Nota: args es solo un nombre, puede nombrar ese vararg cualquier cosa siempre que esté precedido por un solo asterisco (*). Es una buena práctica seguir nombrándolo args para que sea inmediatamente reconocible.

    Cualquier argumento que venga después *args debe ser un argumento con nombre, un argumento al que se hace referencia por su nombre en lugar de su posición. Con *args ya no puede hacer referencia a otro argumento por su posición.

    Además, solo puedes tener en *args escriba vararg en una función.

    Puede estar pensando que la solución con *args es muy similar a la solución de lista. Eso es porque *args es internamente una Tupla, que es una secuencia iterable similar a las listas. Si desea verificar su tipo, puede ingresar el código en su intérprete de Python:

    $ python3
    >>> def arg_type_test(*args):
    ...     print(type(args))
    ...
    >>> arg_type_test(1, 2)
    <class 'tuple'>
    

    Con *args, podemos aceptar varios argumentos en secuencia como se hace en my_min(). Estos argumentos son procesados ​​por su posición. ¿Qué pasaría si quisiéramos tomar varios argumentos, pero hacer referencia a ellos por su nombre? Veremos cómo hacer esto en la siguiente sección.

    Uso de muchos argumentos con nombre con ** kwargs

    Python puede aceptar múltiples argumentos de palabras clave, mejor conocidos como **kwargs. Se comporta de manera similar a *args, pero almacena los argumentos en un diccionario en lugar de tuplas:

    def kwarg_type_test(**kwargs):
        print(kwargs)
    
    kwarg_type_test(a="hi")
    kwarg_type_test(roses="red", violets="blue")
    

    La salida será:

    {'a': 'hi'}
    {'roses': 'red', 'violets': 'blue'}
    

    Usando un diccionario, **kwargs puede conservar los nombres de los argumentos, pero no podría mantener su posición.

    Nota: Me gusta args, puede utilizar cualquier otro nombre que kwargs. Sin embargo, la mejor práctica dicta que debe usar constantemente kwargs.

    Ya que **kwargs es un diccionario, puede iterar sobre ellos como cualquier otro usando el .items() método:

    def kwargs_iterate(**kwargs):
        for i, k in kwargs.items():
            print(i, '=', k)
    
    kwargs_iterate(hello='world')
    

    Cuando se ejecuta, nuestra consola mostrará:

    hello = world
    

    Los argumentos de palabras clave son útiles cuando no está seguro de si un argumento estará disponible. Por ejemplo, si tuviéramos una función para guardar una publicación de blog en una base de datos, guardaríamos la información como el contenido y el autor. Una publicación de blog puede tener etiquetas y categorías, aunque no siempre se establecen.

    Podemos definir una función como esta:

    def save_blog_post(content, author, tags=[], categories=[]):
        pass
    

    Alternativamente, permitimos que la función que llama pase cualquier cantidad de argumentos y solo asocie tags y categories si están configurados:

    def save_blog_post(content, author, **kwargs):
        if kwargs.get('tags'):
            # Save tags with post
            pass
    
        if kwargs.get('categories'):
            # Save categories with post
            pass
    

    Ahora que tenemos una comprensión de ambos tipos de soporte para argumentos de longitud variable, veamos cómo podemos combinar los dos en una función.

    Combinando varargs y argumentos de palabras clave

    Muy a menudo queremos usar ambos *args y **kwargs juntos, especialmente al escribir bibliotecas Python o código reutilizable. Por suerte para nosotros, *args y **kwargs juegan bien juntos, y podemos usarlos de la siguiente manera:

    def combined_varargs(*args, **kwargs):
        print(args)
        print(kwargs)
    
    combined_varargs(1, 2, 3, a="hi")
    

    Si ejecuta ese fragmento de código, verá:

    (1, 2, 3)
    {'a': 'hi'}
    

    Al mezclar los argumentos posicionales y nombrados, los argumentos posicionales deben venir antes de argumentos nombrados. Además, los argumentos de longitud fija se anteponen a los argumentos de longitud variable. Por lo tanto, obtenemos un pedido como este:

    • Argumentos posicionales conocidos
    • *args
    • Argumentos con nombre conocidos
    • **kwargs

    Una función con todo tipo de argumentos puede verse así:

    def super_function(num1, num2, *args, callback=None, messages=[], **kwargs):
        pass
    

    Una vez que sigamos ese orden al definir y llamar a funciones con varargs y argumentos de palabras clave, obtendremos el comportamiento que esperamos de ellos.

    Hasta ahora hemos usado el *args y **kwargs sintaxis para las definiciones de funciones. Python nos permite usar la misma sintaxis cuando también llamamos funciones. ¡Veamos cómo!

    Desempaquetado de argumentos con * args y ** kwargs

    Consideremos una función add3(), que acepta 3 números e imprime su suma. Podemos crearlo así:

    def add3(num1, num2, num3):
        print("The grand total is", num1 + num2 + num3)
    

    Si tenía una lista de números, puede usar esta función especificando qué elemento de la lista se usa como argumento:

    magic_nums = [32, 1, 7]
    
    add3(magic_nums[0], magic_nums[1], magic_nums[2])
    

    Si ejecuta este código, verá:

    The grand total is 40
    

    Si bien esto funciona, podemos hacerlo más sucinto con *args sintaxis:

    add3(*magic_nums)
    

    La salida es The grand total is 40, justo como antes.

    Cuando usamos *args sintaxis en una llamada a función, estamos desempaquetando la variable. Al desempacar, queremos decir que estamos sacando los valores individuales de la lista. En este caso, sacamos cada elemento de la lista y los colocamos en los argumentos, donde la posición 0 corresponde al primer argumento.

    También puede descomprimir una tupla de manera similar:

    tuple_nums = (32, 1, 7)
    add3(*tuple_nums) # The grand total is 40
    

    Si desea descomprimir un diccionario, debe usar el **kwargs sintaxis.

    dict_nums = {
        'num1': 32,
        'num2': 1,
        'num3': 7,
    }
    
    add3(**dict_nums) # The grand total is 40
    

    En este caso, Python hace coincidir la clave del diccionario con el nombre del argumento y establece su valor.

    ¡Y eso es! Puede administrar más fácilmente sus llamadas a funciones desempaquetando valores en lugar de especificar cada argumento que necesita un valor de un objeto.

    Conclusión

    Con Python, podemos usar el *args o **kwargs sintaxis para capturar un número variable de argumentos en nuestras funciones. Utilizando *args, podemos procesar un número indefinido de argumentos en la posición de una función. Con **kwargs, podemos recuperar un número indefinido de argumentos por su nombre.

    Si bien una función solo puede tener un argumento de longitud variable de cada tipo, podemos combinar ambos tipos de funciones en un solo argumento. Si lo hacemos, debemos asegurarnos de que los argumentos posicionales vengan antes que los argumentos con nombre y que los argumentos fijos vengan antes que los de longitud variable.

    Python también nos permite usar la sintaxis para llamadas a funciones. Si tenemos una lista o una tupla y usamos el *args sintaxis, descomprimirá cada valor como argumentos posicionales. Si tenemos un diccionario y usamos **kwargs sintaxis, entonces hará coincidir los nombres de las claves del diccionario con los nombres de los argumentos de la función.

    ¿Estás trabajando en una función que pueda beneficiarse de este tipo de argumentos? ¿O quizás puede refactorizar una función y hacerla a prueba de futuro? ¡Háganos saber en qué está trabajando!

     

    Etiquetas:

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *