Asterisco * y Doble Asterisco ** en Python: Qué Son y Cómo Usarlos

Cómo usar asterisco y doble asterisco en Python

A veces resulta confuso encontrarse en Python código que utiliza asteriscos con usos que van más allá de simples operaciones aritméticas. Y es que este operador tiene múltiples facetas y se emplea para cosas muy diversas. Si no quieres volver a perderte con el uso de los asteriscos y los dobles asteriscos en Python, ¡sígueme!

En Python el asterisco * se usa para multiplicar, replicar cadenas de texto, listas o tuplas, aceptar varios parámetros en funciones y desempaquetar listas o tuplas. El doble asterisco ** se usa para calcular potencias, aceptar varios parámetros en funciones y desempaquetar diccionarios.

Ya ves la cantidad de usos que pueden tener. Para aclararte todo esto, en este artículo te voy a contar cada uno de estos usos de manera individual, paso a paso, para lograr que los asteriscos dejen de ser tan misteriosos como parecen.

Asterisco simple * en Python

Te cuento en esta sección los diferentes usos que tiene un asterisco sencillo en el lenguaje. No te preocupes sin son muchas cosas, al final del artículo te dejo una tabla resumen donde podrás verlo todo con un golpe de vista.

El asterisco simple * en Python se utiliza para realizar multiplicaciones (5 * 7), replicar y concatenar varias cadenas de texto ('txt' * 5), listas ([1, 2, 3] * 7) o tuplas ((4, 5) * 3)), aceptar múltiples parámetros en funciones (def f(*args)) y desempaquetar iterables (print(*[1, 2, 3])).

Te lo desgrano poco a poco…

Multiplicaciones

Bueno, este es el uso más evidente y probablemente no necesita explicación. Además sucede que es prácticamente igual en todos los lenguajes de programación.

En este caso, el asterisco es un operador infijo, es decir, que se ubica entre dos operandos. El resultado, como ya sabes, es el de multiplicar dos números.

Y la sintaxis, pues también la conoces: 5 * 3 nos dará como resultado 15, mientras que 2 * 3.41 nos dará como resultado 6.42.

Pasemos a algo más complejo, útil y sorprendente. La replicación de cadenas de texto, listas o tuplas.

Replicar cadenas de texto, listas o tuplas

Este uso resulta de lo más útil y es casi mágico. Si multiplicas cualquier cadena de texto, lista o tupla, llamémosle L por un número entero N utilizando el asterisco, el resultado que obtendrás será el de repetir N veces el contenido de L. Además, todas estas repeticiones se concatenan una a continuación de la otra.

Veamos un ejemplo con una cadena de texto:

>>> 'texto' * 5
'textotextotextotextotexto'

>>> 'Esto es Código Pitón ' * 3
'Esto es Código Pitón Esto es Código Pitón Esto es Código Pitón '

>>> 'abc' * 10
'abcabcabcabcabcabcabcabcabcabc'

Ya ves lo fácil que resulta hacer una operación con cadenas de texto como la que te acabo de contar. Si necesitas manejar cadenas y subcadenas de texto te recomiendo que sigas el siguiente enlace donde te cuento como hacer en Python substring y otras cosas relacionadas.

Lo interesante de esto es que puedes hacer exactamente lo mismo con listas o tuplas. El resultado será el de repetir la lista y concatenar, en el mismo orden de la lista original todos los elementos replicados. Mira:

>>> [1, 2, 3] * 5
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]

>>> [0] * 10
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

>>> (10, 20, 30) * 4
(10, 20, 30, 10, 20, 30, 10, 20, 30, 10, 20, 30)

Genial, ¿verdad? Utilicémoslo en un pequeño ejemplo para inicializar a 0 una matriz o array bidimensional de 10 filas y 10 columnas:

>>> [[0] * 10] * 10
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

¡Cuidado! Cuando estamos replicando una lista cuyos elementos no son tipos básicos, como en este ejemplo, que son listas, no se hace una copia de dichas listas, sino que se copia la referencia a ellas, de forma que todas las listas son la misma. Si modificamos un elemento en una fila, se modifica en todas las demás filas, pues todas son referencias a la misma lista.

Esto es interesante si necesitamos que la matriz sea de solo lectura. Si necesitas poder modificar las filas de manera independiente tendrías que asegurarte de copiar cada una de ellas y no solo la referencia. Podrías hacerlo así: [[0] * 10 for i in range(10)] donde se genera una lista diferente para cada fila.

Aceptar múltiples parámetros en un función

Un problema típico en Python (y en muchos lenguajes) es el de necesitar definir funciones que acepten un número arbitrario de parámetros, es decir, a veces ninguno, otras veces dos parámetros, otras veces siete… los que sean.

Un ejemplo claro de este tipo de funciones es print o zip, que aceptan cualquier número de parámetros. Sobre zip te hablo en otro artículo sobre recorrer dos listas a la vez en Python. En el siguiente ejemplo te muestro esta caratecrística:

>>> print(1)
1

>>> print(1, 2)
1 2

>>> print(1, 2, 3, 4, 5)
1 2 3 4 5

Cada lenguaje tiene su manera particular de dar solución a este problema. Python propone el uso del asterisco de una forma muy sencilla, basta con anteponer un asterisco al nombre del parámetro en el momento de definir la función.

De esta manera, ese parámetro funcionará como una tupla que tendrá tantos valores como argumentos se hayan pasado al invocar a la función. Dicho de otra manera, al invocar a la función se empaquetan los diferentes valores proporcionados en dicha tupla. Vamos a verlo con ejemplos para que se entienda mejor:

def saludar(*nombres): # anteponemos un asterisco al nombre del parámetro

    print(nombres) # vemos como nombres es una tupla con todos los parámetros recibidos (se han empaquetado)

    for nombre in nombres: # recorremos la tupla
        print(f'¡Hola, {nombre}!') # y saludamos


saludar('Juan')

saludar('Juan', 'Laura', 'Lucía')

En este ejemplo, como ves, creamos una función saludar que recibe varios nombres. Para ello tenemos que anteponer al nombre del parámetro nombres un asterisco de la siguiente forma: *nombres. Y eso es todo, a partir de ese momento ya puedes invocar a la función pasando cualquier número de argumentos. Dentro de la función solo tienes que manejar el parámetro como si fuera una tupla (porque lo es) en lugar de ser un solo parámetro individual.

El resultado de ejecutar el código anterior es el siguiente:

('Juan',)
¡Hola, Juan!
('Juan', 'Laura', 'Lucía')
¡Hola, Juan!
¡Hola, Laura!
¡Hola, Lucía!

Ten en cuenta que si usas este método solo puede haber un solo parámetro de este tipo y, además, debe ser el último de todos los parámetros, de lo contrario, Python no sabría como asignar los valores proporcionados al invocar a la función a cada uno de los parámetros. Esto es debido a que los parámetros son posicionales, es decir, importa su posición y, por tanto, el orden en la invocación. Esto hace que los datos proporcionados a una función al invocarla se vayan asignando en orden a los parámetros definidos en dicha función. Ejemplos de definiciones de funciones válidas:

  • def funcion(*ps), que puede recibir 0 o más parámetros (ps).
  • def funcion(p, *ps), que puede recibir 1 (p) o más parámetros (ps).
  • def funcion(p1, p2, p3, *ps), que puede recibir 3 (p1, p2 y p3) o más parámetros (ps).

Ejemplos de definiciones de funciones no válidas:

  • def funcion(*ps, *ps2).
  • def funcion(p1, *ps, p2). Aunque hay un matiz aquí que te cuento a continuación.

Una excepción a esta regla sucede al llamar a funciones indicando el nombre de los parámetros. Tanto si estos tienen un valor por defecto como si no, pueden definirse tras el parámetro con asterisco. Ejemplos:

  • def funcion(*ps, p1=0).
  • def funcion(p1, *ps, p2=0, p3=0).
  • def funcion(p1, *ps, p2, p3=0). En este caso es obligatorio indicar el parámetro p2 con su nombre al invocar a la función de esta manera, por ejemplo: funcion(0, 1, 2, 3, p2=4).

De esta forma nunca puede haber ambigüedad a la hora de llamar a la función, pues Python asigna los valores a los parámetros posicionales en la llamada por orden y los parámetros con nombre según, claro está, su nombre. Los parámetros tomarán su valor por defecto si no se incluyen en la llamada. Los parámetros tras el parámetro con asterisco, tendrán que ser indicados obligatoriamente con su nombre en la llamada si no tienen valor por defecto asignado. Veamos un ejemplo:

def saludar(saludo, *nombres, hora, despedida='Adiós...'):

    for nombre in nombres:
        print(f'{saludo}, {nombre}')

    print(f'Esta es la hora: {hora}')
    print(despedida)

saludar('Buenos días', 'Carlos', 'Manuel', hora='09:15am')
print()
saludar('Buenas noches', 'Roberto', 'Fernando', hora='23:30', despedida='Hasta mañana')

Como puedes ver, el parámetro hora no tiene valor por defecto y está ubicado tras el parámetro múltiple nombres, por lo que no se puede asignar posicionalmente, por tanto, es obligatorio indicar su nombre al invocar a la función.

El código anterior generará la siguiente salida por pantalla. Analízalo con calma:

Buenos días, Carlos
Buenos días, Manuel
Esta es la hora: 09:15am
Adiós...

Buenas noches, Roberto
Buenas noches, Fernando
Esta es la hora: 23:30
Hasta mañana

Indicar el final de los parámetros posicionales

Hay que tener en cuenta que se puede asignar en una llamada un valor a un parámetro con valor por defecto sin necesidad de indicar el nombre, es decir, posicionalmente. Para que esto pueda ser así, es necesario que todos los parámetros posicionales previos reciban un valor aunque tengan uno por defecto. Lo vemos con un ejemplo, donde para darle valor al parámetro p3 necesito dárselo también al p2 aunque este tenga un valor por defecto:

def funcion(p1, p2=10, p3=20):
    print(f'p1 es {p1}')
    print(f'p2 es {p2}')
    print(f'p3 es {p3}')

# llamando a la función pasando p2 y p3 por posicion
funcion(1, 2, 3)

print()

# llamando a la función pasando p3 por nombre
funcion(1, 2, p3=3)

Como puedes ver, ambas llamadas son válidas y el resultado es el mismo.

Supón ahora que quieres obligar a que el parámetro p3 solo se pueda pasar utilizando su nombre, es decir, de la segunda manera, evitando que pueda ser llamada la función y pasado un tercer valor sin indicar su nombre.

Para esto se puede utilizar el pequeño truco de incluir otro parámetro que acepte múltiples valores anteponiendo un asterisco de esta manera: def funcion(p1, p2, *ps, p3=None). Así, si hacemos la llamada indicando un tercer valor sin nombre, este tercer valor (y otros sucesivos si los hubiera) se van a recibir en la función en la tupla ps, asignados por posición, dejando a p3 sin valor. Solo faltaría ahora lanzar un error en el caso de que ps tenga valores, pues son valores a ignorar. Pero esta es una forma un poco «sucia» de forzar a que p3 se proporcione con su nombre.

Python introduce una nueva sintaxis para facilitar este comportamiento. Se trata de utilizar un único y aislado asterisco que indica «a partir de aquí no se aceptan más parámetros por posición», obligando a indicar los parámetros que queden a continuación por nombre de manera obligatoria. Lo vemos:

def funcion(p1, p2, *, p3=None):
    print(f'p1 es {p1}')
    print(f'p2 es {p2}')
    print(f'p3 es {p3}')


# llamando a la función pasando p3 por nombre
funcion(1, 2, p3=3)

# llamando a la función pasando p3 por posición
funcion(1, 2, 3)

El resultado será el siguiente, donde la primera llamada funciona sin problemas y la segunda genera un error.

p1 es 1
p2 es 2
p3 es 3

TypeError: funcion() takes 2 positional arguments but 3 were given

Este comportamiento fue introducido en la propuesta de mejora de Python PEP 3102.

Desempaquetar iterables

Al igual que hemos utilizado el asterisco para empaquetar valores individuales en tuplas al hacer llamadas a funciones, podemos desempaquetar los valores que haya en tuplas, listas, cadenas de texto o cualquier objeto iterable en general (como diccionarios o conjuntos) para poder obtenerlos de manera individual.

La principal utilidad es poder enviar todos estos valores desempaquetados a una función que pueda recibir cualquier número de parámetros (como print o una definida por nosotros mismos tal y como he explicado antes), o para añadir varios valores a otro iterable. La manera de realizar esta operación de desempaquetado es anteponer un asterisco al iterable a desempaquetar.

Veamos el primer caso. Queremos desempaquetar los valores de una lista para hacer una llamada a una función que puede recibir múltiples parámetros. Para hacer esto, simplemente ponemos un asterisco delante de la lista:

>>> lista = [1, 2, 3]
>>> print(lista)
[1, 2, 3]
>>> 
>>> print(*lista)
1 2 3

Fíjate en el ejemplo, si imprimimos el contenido de la variable lista nos muestra toda la lista por pantalla, mientras que si anteponemos un asterisco al nombre de la variable, es como si hubiéramos invocado a la función de la siguiente manera: print(1, 2, 3).

Veamos un ejemplo similar, pero ahora, utilizando otro elemento iterable, como por ejemplo range:

>>> print(*range(5))
0 1 2 3 4
>>> 
>>> print(*range(10, 20, 3))
10 13 16 19

También resulta de mucha utilidad desempaquetar un iterable directamente en una lista o tupla, para añadir varios valores a la vez:

>>> lista = [4, 5, 6]
>>> [1, 2, 3, *lista]
[1, 2, 3, 4, 5, 6]
>>>
>>> letras = 'abc'
>>> (*letras, 'd', 'e', 'f')
('a', 'b', 'c', 'd', 'e', 'f')

Este comportamiento es similar al que sucede, de manera automática cuando hacemos lo siguiente:

>>> lista = [1, 2, 3]
>>> a, b, c = lista
>>> a
1
>>> b
2
>>> c
3

También podemos utilizar el desempaquetado anteponiendo el asterisco a una variable en el lado izquierdo de una asignación para capturar un conjunto de valores de la siguiente manera:

>>> lista = [1, 2, 3, 4, 5]
>>> v1, v2, *vs = lista
>>> v1
1
>>> v2
2
>>> vs
[3, 4, 5]

Ya también podemos hacer lo mismo pero para capturar una porción interior de la lista. Eso sí, solo podremos usar la notación con asterisco una sola vez para evitar ambigüedades:

>>> lista = [1, 2, 3, 4, 5]
>>> v1, *vs, v2 = lista
>>> v1
1
>>> vs
[2, 3, 4]
>>> v2
5

El desempaquetado de iterables (y otras funcionalidades) se introdujo en el documento de propuesta de mejora de Python PEP 448.

Asterisco doble ** en Python

El uso del doble asterisco es todavía más misterioso, pues es un uso todavía menos habitual y que, desde luego, no se ve en muchos otros lenguajes de programación. Te lo aclaro a continuación.

El asterisco doble ** en Python se utiliza para calcular potencias (2 ** 3), aceptar múltiples parámetros con nombre en funciones (def f(**args)), pasar múltiples parámetros con nombre a funciones (f(**diccionario}) y desempaquetar diccionarios en otros diccionarios (diccionario = {**d1, **d2}).

Te lo cuento paso a paso para que no te queden dudas. ¡Vamos!

Cálculo de potencias

Bueno, este es el caso más evidente y más sencillo. Supongo que ni necesita explicación. Por si acaso estás recién llegado a este maravilloso mundo de Python, te pongo un par de ejemplos.

Cuando necesitamos calcular una potencia basta con utilizar el operador infijo ** de esta manera: base ** exponente. Así, para calcular 2 elevando a 3 basta con hacer 2 ** 3. Veamos algunos ejemplos:

>>> 2 ** 3
8
>>> 1.5 ** 2
2.25

Aclarado esto, vamos a ver los casos de uso menos habituales.

Aceptar y pasar múltiples parámetros con nombre en una función

Al igual que sucede con el uso de un único asterisco, también podemos definir funciones que acepten varios parámetros con el uso del doble asterisco. La diferencia aquí es que, en este caso, dichos parámetros siempre tendrán un nombre asociado (no posicionales).

Así, lo que recibimos en este caso es un diccionario, y no una tupla, con diversos parámetros, cada uno con su nombre.

La manera de lograr esto es anteponer dos asteriscos al nombre del parámetro en este formato def funcion(**parametros). Pero vamos a verlo en un ejemplo:

def saludar(**nombres):

    print(nombres) # vemos como tenemos un diccionario en el parámetro nombres

    for parametro in nombres:
        print(f'¡Hola, {nombres[parametro]}!')

Fíjate que lo importante aquí es que estamos empaquetando parámetros con nombre, de forma que, al hacer la llamada, no proporcionamos un diccionario, sino parámetros con su nombre de manera individual, eso sí, todos los que queramos. Para invocar a la función anterior podríamos hacer lo siguiente:

saludar(nombre1='Juan', nombre2='Laura', nombre3='Lucía')

El resultado por pantalla sería el siguiente, donde, como puedes ver, el parámetro nombres es un diccionario:

{'nombre1': 'Juan', 'nombre2': 'Laura', 'nombre3': 'Lucía'}
¡Hola, Juan!
¡Hola, Laura!
¡Hola, Lucía!

Ten en cuenta que puedes definir funciones que reciban múltiples parámetros posicionales y múltiples parámetros con nombre al mismo tiempo. Solo has de considerar que los parámetros con nombre deben ir definidos al final: def funcion(*p_posicionales, **p_con_nombre). Una posible invocación de esta función sería: funcion(1, 2, 3, p1=4, p2=5).

De la misma manera que podemos desempaquetar un iterable anteponiendo un solo asterisco para pasar todos los valores de dicho iterable a una función que acepta múltiples parámetros, como ya te he explicado antes, aquí podemos hacer algo parecido.

Si tenemos un diccionario, donde cada valor tiene un nombre (el nombre, es decir, la clave, ha de ser una cadena de texto), podemos desempaquetarlos al invocar a una función que acepta múltiples parámetros con nombre anteponiendo dos asteriscos. Así, podríamos hacer la llamada de la siguiente manera para obtener el mismo resultado que en el caso anterior:

diccionario = {'nombre1':'juan', 'nombre2':'Laura', 'nombre3':'Lucía'}

saludar(**diccionario)

Desempaquetar diccionarios en otros diccionarios

Esta característica de desempaquetar diccionarios anteponiéndoles dos asteriscos, también resulta útil cuando queremos copiar los valores con sus nombres de un diccionario a otro, pudiendo además, realizar una de estas tres operaciones al mismo tiempo:

  • Fusionar con otro diccionario (o con otros).
  • Añadir nuevos pares nombre – valor.
  • Actualizar alguno de los valores ya existentes

Vamos a ver caso a caso.

Para fusionar dos o más diccionarios, basta con crear un nuevo diccionario con el contenido desempaquetado de los diccionarios de origen. Puedes hacerlo de la siguiente manera:

>>> meses1 = {'01':'Enero', '02':'Febrero', '03':'Marzo'}
>>> meses2 = {'04':'Abril', '05':'Mayo'}
>>> meses = {**meses1, **meses2}
>>> meses
{'01': 'Enero', '02': 'Febrero', '03': 'Marzo', '04': 'Abril', '05': 'Mayo'}

Ten en cuenta que si hay claves repetidas en los diccionarios a fusionar el valor que tenga dicha clave será el que tenga en el diccionario que se añada más tarde.

Para hacer una copia de un diccionario y añadir uno (o más nuevos) valores, podemos hacer lo siguiente:

>>> meses1 = {'01': 'Enero', '02': 'Febrero', '03': 'Marzo', '04': 'Abril', '05': 'Mayo'}
>>> meses = {**meses1, '06': 'Junio'}
>>> meses
{'01': 'Enero', '02': 'Febrero', '03': 'Marzo', '04': 'Abril', '05': 'Mayo', '06': 'Junio'}

Bueno, y a estas alturas ya te imaginas cómo hacer una copia de un diccionario modificando alguna de las claves:

>>> meses1 = {'01': None, '02': 'Febrero', '03': 'Marzo', '04': 'Abril', '05': 'Mayo'}
>>> meses = {**meses1, '01': 'Enero'}
>>> meses
{'01': 'Enero', '02': 'Febrero', '03': 'Marzo', '04': 'Abril', '05': 'Mayo'}

Tabla de resumen

En este artículo has aprendido los diversos usos que tiene el asterisco * y el asterisco doble ** en Python. Sé que son unos cuantos (algunos sorprendentes y muy útiles), así que te hago esta tabla de resumen para que puedas acudir a ella cuando no recuerdes algún detalle.

* / **UsoEjemplos
*Cálculo de multiplicaciones2 * 3
4.5 * 2.8
*Replicar cadenas de texto, listas o tuplas'texto' * 3
[1, 2, 3] * 4
(1, 2) * 5
*Aceptar múltiples parámetros
posicionales en funciones
def funcion(*parametros)
def funcion(p1, p2, *ps)
def función (p1, *ps, p2=0)
*Indicar final de parámetros posicionalesdef funcion(p1, p2, *, p3=None)
*Desempaquetar iterablesprint(*[1, 2, 3])
print(*range(5))
[1, 2, 3, *lista]
v1, v2, *vs = [1, 2, 3, 4, 5]
v1, *vs, v5 = [1, 2, 3, 4, 5]
**Cálculo de potencias2 ** 3
2.5 ** 4
**Aceptar múltiples parámetros con
nombre (no posicionales) en funciones
def funcion(**parametros)
**Pasar los valores de un diccionario
como múltiples parámetros con nombre
funcion(**diccionario)
**Fusionar diccionariosdiccionario = {**dicc1, **dicc2}
**Copiar diccionario y añadir o modificar valoresdiccionario = {**dicc1, 'clave': 'valor'}
Tabla de resumen del uso del asterisco * y del doble asterisco ** en Python

Y nada más, tienes en este artículo mucho material para estudiar y revisar. Acude a él siempre que lo necesites.

Si también te pierdes un poco con el uso de paréntesis ( ), corchetes [ ] y llaves { }, en este artículo te explico para qué y cómo se usan. ¡Échale un vistazo!

La Hoja de Referencia de Python – ¡Gratis!

La Hoja de Referencia de Python - Código Pitón
Consigue trucos, consejos y actualizaciones y, por supuesto, la Hoja de Referencia de Python gratis.



Antes de suscribirte consulta aquí la Información Básica sobre Protección de Datos. Responsable de los datos: Laura Otero Moreira. Finalidad de la recogida y tratamiento de los datos personales: enviarte boletín informativo de Python y comunicaciones comerciales. Legitimación: tu consentimiento. Destinatarios: no se ceden a terceros. Los datos se almacenan en los servidores de marketing (MailRelay). Derechos: podrás ejercer tus derechos de acceso, rectificación, limitación y supresión de datos en info @ codigopiton.com así como presentar una reclamación ante una autoridad de control. Más información en nuestra política de privacidad, encontrarás información adicional sobre la recopilación y el uso de tu información personal, incluida información sobre acceso, conservación, rectificación, eliminación, seguridad y otros temas.