Siempre me sorprendió que para trabajar con los argumentos de las funciones de Python, solo necesitas comprender
*args
y **kwargs
. Y me sorprendió no en vano. Resulta que los argumentos no son nada fáciles. En esta publicación, quiero dar una descripción general de todo lo relacionado con los argumentos de función en Python. Espero que al final pueda mostrar el panorama general del trabajo con argumentos y que este artículo no se convierta en una publicación más en la que el lector no pueda encontrar nada nuevo. Y ahora, al grano.
La mayoría de los lectores de este artículo, creo, comprenden la esencia de los argumentos de función. Para los principiantes, permítanme explicarles que estos son objetos enviados a una función por el iniciador de su llamada. Al pasar argumentos a una función, se realizan muchas acciones, dependiendo del tipo de objetos que se envían a la función (objetos mutables o inmutables). Un iniciador de llamada a función es una entidad que llama a una función y le pasa argumentos. Hablando de funciones de llamada, hay algunas cosas a considerar que ahora discutiremos.
Los argumentos, cuyos nombres se especifican cuando se declara la función, almacenan los objetos pasados a la función cuando se llama. Además, si se asigna algo a las correspondientes variables locales de funciones, sus parámetros, esta operación no afecta los objetos inmutables pasados a las funciones. Por ejemplo:
def foo(a):
a = a+5
print(a) # 15
a = 10
foo(a)
print(a) # 10
Como puede ver, la llamada a la función no afectó a la variable de ninguna manera
a
. Esto es exactamente lo que sucede cuando se pasa un objeto inmutable a una función.
Y si se pasan objetos mutables a funciones, es posible que encuentre un comportamiento del sistema diferente al anterior.
def foo(lst):
lst = lst + ['new entry']
print(lst) # ['Book', 'Pen', 'new entry']
lst = ['Book', 'Pen']
print(lst) # ['Book', 'Pen']
foo(lst)
print(lst) # ['Book', 'Pen']
¿Has notado algo nuevo aquí? Si responde "No", tiene razón. Pero si de alguna manera influimos en los elementos del objeto mutable pasado a la función, seremos testigos de algo diferente.
def foo(lst):
lst[1] = 'new entry'
print(lst) # ['Book', 'new entry']
lst = ['Book', 'Pen']
print(lst) # ['Book', 'Pen']
foo(lst)
print(lst) # ['Book', 'new entry']
Como puede ver, el objeto del parámetro
lst
se cambió después de la llamada a la función. Esto sucedió debido al hecho de que estamos trabajando con una referencia a un objeto almacenado en un parámetro lst
. Como resultado, cambiar el contenido de este objeto está fuera del alcance de la función. Puede evitar esto simplemente haciendo copias profundas de dichos objetos y escribiéndolos en las variables locales de la función.
def foo(lst):
lst = lst[:]
lst[1] = 'new entry'
print(lst) # ['Book', 'new entry']
lst = ['Book', 'Pen']
print(lst) # ['Book', 'Pen']
foo(lst)
print(lst) # ['Book', 'Pen']
¿No te sorprendió eso todavía? Si no es así, me gustaría asegurarme de que omita lo que sabe e inmediatamente pase a material nuevo para usted. Y si es así, entonces, marque mis palabras, usted, al familiarizarse con los argumentos, aprenderá cosas mucho más interesantes.
Entonces, esto es lo que debe saber sobre los argumentos de función:
- El orden en el que se pasan los argumentos posicionales a las funciones.
- El orden en el que se pasan los argumentos con nombre a las funciones.
- Asignar valores de argumento predeterminados.
- Organización del procesamiento de conjuntos de argumentos de longitud variable.
- Desempaquetando argumentos.
- Usar argumentos que solo se pueden pasar por nombre (solo palabras clave).
Echemos un vistazo a cada uno de estos puntos.
1. Orden de paso de argumentos posicionales a funciones
Los argumentos posicionales se procesan de izquierda a derecha. Es decir, resulta que la posición del argumento pasado a la función está en correspondencia directa con la posición del parámetro utilizado en el encabezado de la función cuando se declaró.
def foo(d, e, f):
print(d, e, f)
a, b, c = 1, 2, 3
foo(a, b, c) # 1, 2, 3
foo(b, a, c) # 2, 1, 3
foo(c, b, a) # 3, 2, 1
Las variables
a
, b
y c
tienen los valores 1, 2 y 3. Estas variables juegan el papel de los argumentos con los que se llama a la función foo
. Que, en la primera llamada de la función, corresponden a los parámetros d
, e
y f
. Este mecanismo se aplica a casi todos los 6 puntos anteriores sobre lo que necesita saber sobre los argumentos de funciones en Python. La ubicación del argumento posicional que se pasa a la función cuando se llama juega un papel importante en la asignación de valores a los parámetros de la función.
2. Orden de transferencia de argumentos con nombre a funciones
Los argumentos con nombre se pasan a funciones con los nombres de estos argumentos correspondientes a los nombres que se les asignaron cuando se declaró la función.
def foo(arg1=0, arg2=0, arg3=0):
print(arg1, arg2, arg3)
a, b, c = 1, 2, 3
foo(a,b,c) # 1 2 3
foo(arg1=a, arg2=b, arg3=c) # 1 2 3
foo(arg3=c, arg2=b, arg1=a) # 1 2 3
foo(arg2=b, arg1=a, arg3=c) # 1 2 3
Como puede ver, la función
foo
toma 3 argumentos. Estos argumentos se nombran arg1
, arg2
y arg3
. Preste atención a cómo cambiamos la posición de los argumentos cuando llamamos a la función. Los argumentos con nombre se tratan de manera diferente a los argumentos posicionales, aunque el sistema continúa leyéndolos de izquierda a derecha. Python considera los nombres de los argumentos, no sus posiciones, al asignar los valores apropiados a los parámetros de las funciones. Como resultado, resulta que la función genera lo mismo independientemente de las posiciones de los argumentos que se le pasen. Siempre lo es 1 2 3
.
Tenga en cuenta que los mecanismos descritos en el párrafo 1 siguen funcionando aquí.
3. Asignar valores de argumento predeterminados
Se pueden asignar valores predeterminados a argumentos con nombre. Cuando se usa este mecanismo en una función, ciertos argumentos se vuelven opcionales. La declaración de tales funciones se parece a lo que consideramos en el punto # 2. La única diferencia es cómo se llaman estas funciones.
def foo(arg1=0, arg2=0, arg3=0):
print(arg1, arg2, arg3)
a, b, c = 1, 2, 3
foo(arg1=a) # 1 0 0
foo(arg1=a, arg2=b ) # 1 2 0
foo(arg1=a, arg2=b, arg3=c) # 1 2 3
Tenga en cuenta que en este ejemplo no estamos pasando todos los argumentos a la función como se describe en su declaración. En estos casos, a los parámetros correspondientes se les asignan los valores predeterminados. Continuemos con este ejemplo:
foo(arg2=b) # 0 2 0
foo(arg2=b, arg3=c ) # 0 2 3
foo(arg3=c) # 0 0 3
foo(arg3=c, arg1=a ) # 1 0 3
Estos son ejemplos simples y directos del uso de los mecanismos descritos anteriormente para llamar a funciones con pasarle argumentos con nombre. Ahora compliquemos nuestros experimentos combinando lo que hemos hablado hasta ahora en los puntos # 1, # 2 y # 3:
foo(a, arg2=b) # 1 2 0
foo(a, arg2=b, arg3=c) # 1 2 3
foo(a, b, arg3=c) # 1 2 3
foo(a) # 1 0 0
foo(a,b) # 1 2 0
Aquí, tanto los argumentos posicionales como los nombrados se utilizan al llamar a la función. Cuando se utilizan argumentos posicionales, el orden en el que se especifican sigue desempeñando un papel fundamental para pasar correctamente la entrada a la función.
Aquí me gustaría llamar su atención sobre un detalle notable. Consiste en que los argumentos posicionales no se pueden especificar después de los argumentos con nombre. Aquí hay un ejemplo para ayudarlo a comprender mejor esta idea:
foo(arg1=a, b)
>>>
foo(arg1=a, b)
^
SyntaxError: positional argument follows keyword argument
foo(a, arg2=b, c)
>>>
foo(a, arg2=b, c)
^
SyntaxError: positional argument follows keyword argument
Puedes tomarlo como regla. Los argumentos posicionales no tienen que seguir a los argumentos con nombre al llamar a una función.
4. Organización del procesamiento de conjuntos de argumentos de longitud variable
Aquí hablaremos de construcciones
*args
y **kwargs
. Cuando estas construcciones se utilizan en una declaración de función, esperamos que cuando se llame a la función, los conjuntos de argumentos de longitudes arbitrarias se representen como parámetros args
y kwargs
. Cuando se aplica la construcción *args
, el parámetro args
recibe argumentos posicionales representados como una tupla. Cuando se aplica **kwargs
en el kwargs
otoño, los argumentos con nombre se enumeran en un diccionario.
def foo(*args):
print(args)
a, b, c = 1, 2, 3
foo(a, b, c) # (1, 2, 3)
foo(a, b) # (1, 2)
foo(a) # (1)
foo(b, c) # (2, 3)
Este código prueba que el parámetro
args
almacena una tupla que contiene lo que se pasó a la función cuando se llamó.
def foo(**kwargs):
print(kwargs)
foo(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3}
foo(a=1, b=2) # {'a': 1, 'b': 2}
foo(a=1) # {'a': 1}
foo(b=2, c=3) # {'b': 2, 'c': 3}
El código anterior muestra que el parámetro
kwargs
almacena un diccionario de pares clave-valor que representan los argumentos nombrados que se pasan a la función cuando se llama.
Sin embargo, debe tenerse en cuenta que una función diseñada para aceptar argumentos posicionales no puede pasar argumentos con nombre (y viceversa).
def foo(*args):
print(args)
foo(a=1, b=2, c=3)
>>>
foo(a=1, b=2, c=3)
TypeError: foo() got an unexpected keyword argument 'a'
#########################################################
def foo(**kwargs):
print(kwargs)
a, b, c = 1, 2, 3
foo(a, b, c)
>>>
TypeError: foo() takes 0 positional arguments but 3 were given
Ahora juntemos todo lo que analizamos en los puntos # 1, # 2, # 3 y # 4, y experimentemos con todo esto, examinando diferentes combinaciones de argumentos que se pueden pasar a funciones cuando son llamadas.
def foo(*args,**kwargs):
print(args, kwargs)
foo(a=1,)
# () {'a': 1}
foo(a=1, b=2, c=3)
# () {'a': 1, 'b': 2, 'c': 3}
foo(1, 2, a=1, b=2)
# (1, 2) {'a': 1, 'b': 2}
foo(1, 2)
# (1, 2) {}
Como ves, tenemos una tupla
args
y un diccionario a nuestra disposición kwargs
.
Y aquí hay otra regla. Se basa en el hecho de que la estructura
*args
no se puede utilizar después de la estructura **kwargs
.
def foo(**kwargs, *args):
print(kwargs, args)
>>>
def foo(**kwargs, *args):
^
SyntaxError: invalid syntax
La misma regla se aplica al orden en el que se especifican los argumentos al llamar a funciones. Los argumentos posicionales no deben seguir a argumentos con nombre.
foo(a=1, 1)
>>>
foo(a=1, 1)
^
SyntaxError: positional argument follows keyword argument
foo(1, a=1, 2)
>>>
foo(1, a=1, 2)
^
SyntaxError: positional argument follows keyword argument
Al declarar funciones, puede combinar argumentos posicionales
*args
y de la *kwagrs
siguiente manera:
def foo(var, *args,**kwargs):
print(var, args, kwargs)
foo(1, a=1,) # 1
# 1 () {'a': 1}
foo(1, a=1, b=2, c=3) # 2
# 1 () {'a': 1, 'b': 2, 'c': 3}
foo(1, 2, a=1, b=2) # 3
# 1 (2,) {'a': 1, 'b': 2}
foo(1, 2, 3, a=1, b=2) # 4
# 1 (2, 3) {'a': 1, 'b': 2}
foo(1, 2) # 5
# 1 (2,) {}
Al declarar una función,
foo
asumimos que debe tener un argumento posicional requerido. Es seguido por un conjunto de argumentos posicionales de longitud variable, y este conjunto es seguido por un conjunto de argumentos denominados de longitud variable. Sabiendo esto, podemos "descifrar" fácilmente cada una de las llamadas de función anteriores.
A la
1
función se le pasan argumentos 1
y a=1
. Estos son, respectivamente, argumentos posicionales y con nombre. 2
Es una variedad 1
. Aquí, la longitud del conjunto de argumentos posicionales es cero.
En
3
pasamos funciones 1
, 2
y a=1,b=2
. Esto significa que ahora acepta dos argumentos posicionales y dos argumentos con nombre. Según la declaración de función, resulta que1
tomado como un argumento posicional requerido, 2
entra en un conjunto de argumentos posicionales de longitud variable a=1
y b=2
termina en un conjunto de argumentos de longitud variable denominados.
Para llamar a esta función correctamente, debemos pasarle al menos un argumento posicional. De lo contrario, enfrentaremos un error.
def foo(var, *args,**kwargs):
print(var, args, kwargs)
foo(a=1)
>>>
foo(a=1)
TypeError: foo() missing 1 required positional argument: 'var'
Otra variación de esta función es una función que declara que toma un argumento posicional requerido y un argumento con nombre, seguido de conjuntos de longitud variable de argumentos posicionales y con nombre.
def foo(var, kvar=0, *args,**kwargs):
print(var, kvar, args, kwargs)
foo(1, a=1,) # 1
# 1 0 () {'a': 1}
foo(1, 2, a=1, b=2, c=3) # 2
# 1 0 () {'a': 1, 'b': 2, 'c': 3}
foo(1, 2, 3, a=1, b=2) # 3
# 1 2 () {'a': 1, 'b': 2}
foo(1, 2, 3, 4, a=1, b=2) # 4
# 1 2 (3,) {'a': 1, 'b': 2}
foo(1, kvar=2) # 5
# 1 2 () {}
Las llamadas a esta función se pueden "descifrar" de la misma forma que se hizo al analizar la función anterior.
Al llamar a esta función, se debe pasar al menos un argumento posicional. De lo contrario, encontraremos un error:
foo()
>>>
foo()
TypeError: foo() missing 1 required positional argument: 'var'
foo(1)
# 1 0 () {}
Tenga en cuenta que la llamada
foo(1)
funciona bien. El punto aquí es que si se llama a una función sin especificar un valor para un argumento con nombre, el valor se le asigna automáticamente.
Y aquí hay algunos errores más que se pueden encontrar si esta función se llama incorrectamente:
foo(kvar=1) # 1
>>>
TypeError: foo() missing 1 required positional argument: 'var'
foo(kvar=1, 1, a=1) # 2
>>>
SyntaxError: positional argument follows keyword argument
foo(1, kvar=2, 3, a=2) # 3
>>>
SyntaxError: positional argument follows keyword argument
Preste especial atención al error de tiempo de ejecución
3
.
5. Desempaquetando argumentos
En las secciones anteriores, hablamos sobre cómo recopilar conjuntos de argumentos pasados a funciones en tuplas y diccionarios. Y aquí discutiremos la operación inversa. Es decir, analizaremos el mecanismo que le permite descomprimir los argumentos proporcionados a la entrada de la función.
args = (1, 2, 3, 4)
print(*args) # 1 2 3 4
print(args) # (1, 2, 3, 4)
kwargs = { 'a':1, 'b':2}
print(kwargs) # {'a': 1, 'b': 2}
print(*kwargs) # a b
Puede descomprimir las variables usando la sintaxis
*
y **
. Así es como se utilizan al pasar tuplas, listas y diccionarios a una función.
def foo(a, b=0, *args, **kwargs):
print(a, b, args, kwargs)
tup = (1, 2, 3, 4)
lst = [1, 2, 3, 4]
d = {'e':1, 'f':2, 'g':'3'}
foo(*tup) # foo(1, 2, 3, 4)
# 1 2 (3, 4) {}
foo(*lst) # foo(1, 2, 3, 4)
# 1 2 (3, 4) {}
foo(1, *tup) # foo(1, 1, 2, 3, 4)
# 1 1 (2, 3, 4) {}
foo(1, 5, *tup) # foo(1, 5, 1, 2, 3, 4)
# 1 5 (1, 2, 3, 4) {}
foo(1, *tup, **d) # foo(1, 1, 2, 3, 4 ,e=1 ,f=2, g=3)
# 1 1 (2, 3, 4) {'e': 1, 'f': 2, 'g': '3'}
foo(*tup, **d) # foo(1, 1, 2, 3, 4 ,e=1 ,f=2, g=3)
# 1 2 (3, 4) {'e': 1, 'f': 2, 'g': '3'}
d['b'] = 45
foo(2, **d) # foo(1, e=1 ,f=2, g=3, b=45)
# 2 45 () {'e': 1, 'f': 2, 'g': '3'}
Deconstruya cada una de las llamadas a funciones que se muestran aquí usando el desempaquetado de argumentos y observe cómo se verían las llamadas correspondientes sin usar
*
y **
. Intente comprender qué sucede cuando realiza estas llamadas y cómo se desempaquetan las distintas estructuras de datos.
Si experimenta desempaquetando argumentos, puede encontrar un nuevo error:
foo(1, *tup, b=5)
>>>
TypeError: foo() got multiple values for argument 'b'
foo(1, b=5, *tup)
>>>
TypeError: foo() got multiple values for argument 'b'
Este error ocurre debido a un conflicto entre el argumento nombrado ,,
b=5
y el argumento posicional. Como descubrimos en la sección # 2, el orden de los argumentos nombrados no importa cuando se pasan. Como resultado, se produce el mismo error en ambos casos.
6. Usar argumentos que solo se pueden pasar por nombre (solo palabras clave)
En algunos casos, debe hacer que la función acepte los argumentos con nombre requeridos. Si, al declarar una función, describen argumentos que solo se pueden pasar por nombre, entonces dichos argumentos deben pasarse a ella siempre que se llame.
def foo(a, *args, b):
print(a, args, b)
tup = (1, 2, 3, 4)
foo(*tup, b=35)
# 1 (2, 3, 4) 35
foo(1, *tup, b=35)
# 1 (1, 2, 3, 4) 35
foo(1, 5, *tup, b=35)
# 1 (5, 1, 2, 3, 4) 35
foo(1, *tup, b=35)
# 1 (1, 2, 3, 4) 35
foo(1, b=35)
# 1 () 35
foo(1, 2, b=35)
# 1 (2,) 35
foo(1)
# TypeError: foo() missing 1 required keyword-only argument: 'b'
foo(1, 2, 3)
# TypeError: foo() missing 1 required keyword-only argument: 'b'
Como puede ver, se espera que a la función se le pase necesariamente un argumento con nombre
b
, que, en la declaración de la función, se especifica después *args
. En este caso, en la declaración de una función, simplemente puede usar un símbolo *
, después de lo cual, separados por comas, hay identificadores de argumentos con nombre que se pueden pasar a la función solo por el nombre. Tal función no estaría diseñada para aceptar un conjunto de argumentos posicionales de longitud variable.
def foo(a, *, b, c):
print(a, b, c)
tup = (1, 2, 3, 4)
foo(1, b=35, c=55)
# 1 35 55
foo(c= 55, b=35, a=1)
# 1 35 55
foo(1, 2, 3)
# TypeError: foo() takes 1 positional argument but 3 were given
foo(*tup, b=35)
# TypeError: foo() takes 1 positional argument but 4 positional arguments (and 1 keyword-only argument) were given
foo(1, b=35)
# TypeError: foo() takes 1 positional argument but 4 positional arguments (and 1 keyword-only argument) were given
La función declarada en el ejemplo anterior toma un argumento posicional y dos argumentos con nombre, que solo se pueden pasar por nombre. Esto lleva al hecho de que para que una función se llame correctamente, necesita pasar ambos argumentos con nombre. Después de eso,
*
también puede describir los argumentos nombrados, que reciben los valores predeterminados. Esto nos da cierta libertad al llamar a tales funciones.
def foo(a, *, b=0, c, d=0):
print(a, b, c, d)
foo(1, c=55)
# 1 0 55 0
foo(1, c=55, b=35)
# 1 35 55 0
foo(1)
# TypeError: foo() missing 1 required keyword-only argument: 'c'
Tenga en cuenta que la función se puede llamar normalmente sin pasarle ningún argumento
b
y d
porque se le han dado valores predeterminados.
Salir
Quizás tengamos, de hecho, una historia muy larga sobre discusiones. Espero que los lectores de este material hayan aprendido algo nuevo por sí mismos. Y, por cierto, la historia de los argumentos de funciones en Python continúa. Quizás hablemos de ellos más tarde.
¿Aprendiste algo nuevo sobre los argumentos de funciones en Python de este material?