Veremos a continuación algunos temas adicionales referidos a funciones en Python: funciones con número variable de argumentos, funciones anidadas y funciones recursivas.
Hay ocasiones en las que necesitamos crear una función que pueda recibir un número arbitrario de argumentos. La manera de implementar eso en Python es utilizar argumentos con asterisco: *arg
.
Todos los valores que se incluyan en un argumento con asterisco al llamar la función, son guardados en una tupla de argumentos, como se ve en el siguiente programa:
def funcion_1(*args):
'''número variable de argumentos, que genera una tupla'''
print(args)
return None
funcion_1("a")
funcion_1("X", 500, [1, 2, 3])
La tupla de argumentos se puede recorrer como cualquier iterable, para operar sobre cada valor pasado en la llamada a la función:
def funcion_2(*args):
'''número variable de argumentos, se imprime el total y cada uno de ellos'''
print('Se llamó a la función con %d argumento(s):' % len(args))
for arg in args:
print(arg)
print('-' * 77)
return None
funcion_2("a")
funcion_2("X", 500, [1, 2, 3])
En el siguiente programa, la función aplica la operación *=3
a cada uno de los argumentos de la tupla, y lo imprime:
def funcion_3(*args):
'''número variable de argumentos, a cada uno aplica la operación *=3'''
for arg in args:
arg *= 3
print(arg)
return None
n = 5 # variable numérica
s = "ABC" # cadena
t = (1, 2, 3) # tupla
l = ['x', 'y', 'z'] # lista
print("valor inicial de las variables:")
print('n:', n)
print('s:', s)
print('t:', t)
print('l:', l)
print("\nsalida de la función:")
funcion_3(n, s, t, l)
print("\nvalor de las variables después de llamar la función:")
print('n:', n)
print('s:', s)
print('t:', t)
print('l:', l)
Se pueden observar dos aspectos importantes:
=*
) realiza la operación de multiplicación en las variables numéricas, y la de repetición en las variables de tipo iterable (cadena, tupla, lista).En una función, un argumento de número variable se puede combinar con argumentos fijos. En una función se puede incluir un único argumento de número variable, que debe ir al final, después de todos los argumentos fijos.
def funcion_4(fijo1, fijo2, *variable):
print("Argumentos fijos:")
print(fijo1)
print(fijo2)
print("Argumento(s) de número variable:")
for i in variable:
print(i)
print('-' * 77)
funcion_4(1, 2, 3)
funcion_4(1, 2, 3, 4, 5, 6, 7)
Toda función devuelve un valor de algún tipo, y podemos hacer que devuelva una función. Para eso, se define una función dentro de la definición de otra función, a lo que se le llama funciones anidadas.
En el siguiente programa, la función acorde
recibe como argumento una lista de intervalos, y devuelve una función que recibe como argumento una nota fundamental. El nombre de esta nueva función se asigna al llamar la función acorde
. Esta función devuelve una lista de notas con los intervalos pasados a la función acorde
, a partir de la fundamental que recibe como argumento.
def acorde(lista_intervalos):
'''define tipo de acorde con una estructura de intervalos'''
def transporte(nota_fundamental):
'''transporta el tipo de acorde a una fundamental'''
notas = [nota_fundamental + i for i in lista_intervalos]
return notas
return transporte
mayor = acorde([0, 4, 7])
menor = acorde([0, 3, 7])
dominante = acorde([0, 4, 7, 10])
print(mayor(60))
print(menor(60))
print(dominante(67))
La recursión es el proceso de definir algo en función de sí mismo. En programación, una función es recursiva, cuando se invoca a sí misma.
El cuerpo de una función recursiva debe incluir dos cosas:
Para cierto tipo de problemas, la solución por recursión puede ofrecer varias ventajas:
Por otra parte, la recursión puede tener algunos inconvenientes:
En la definición de una función recursiva, se deben seguir estos pasos:
Un caso típico para resolver por recursión, es el cálculo del factorial de un número (n!
), definido como:
n! = 1 x 2 x 3 x .... x n
(Por convención se define que 0! = 1
.)
Lo anterior se puede escribir en reversa como:
n! = n x n-1 x n-2 x ... x 1
Por lo tanto:
n! = n x (n-1)!
Es decir que el factorial de un número n
es igual a ese número multiplicado por el factorial de n-1
. Aprovechando esta propiedad, se puede calcular el factorial mediante una función recursiva.
La siguiente función primero establece como casos base cuando n
vale 0 o 1 (n < 2
),
def factorial(n):
'''calcula el factorial de un número entero'''
if n < 2:
return 1
else:
return n * factorial(n-1)
for i in range(10):
print('%d! = %d' % (i, factorial(i)))
En realidad, para que la función se más robusta, debería en primer término verificar que el número que recibe como argumento se un entero y no sea negativo, ya que la operación factorial solo está definida para números enteros positivos.
El siguiente ejemplo muestra una función con una solución diferente, no por recursión sino por iteración mediante un bucle while
. Esta función sí verifica en primer término que el argumento sea un entero positivo, y en caso contrario imprime un mensaje de error y devuelve el valor -1
.
def factorial_i(n):
'''calcula el factorial de un número entero'''
if type(n) != int or n < 0:
print('Argumento inválido, la operación factorial debe ser un entero positivo.')
return -1
fact = 1
while n > 0:
fact *= n
n -=1
return fact
for i in range(10):
print('%d! = %d' % (i, factorial_i(i)))
Como se puede ver, esta función imprime un mensaje y devuelve el valor -1
cuando el argumento es negativo, o no es un entero.
print(factorial_i(-1))
print(factorial_i(1.1))
Otro problema típico para resolver por recursión, es el cálculo de los términos de la serie de Fibonacci. Los dos primeros términos de esta serie son 0 y 1, y cada término sucesivo se calcula como la suma de los dos términos anteriores.
La siguiente función calcula de manera recursiva el término n
de la serie de Fibonacci. Primero define como casos base cuando n
vale 0 o 1 (n < 1
), y para todo otro n
devuelve la suma de llamar a la propia función con los argumentos n-1
y n-2
. Previamente se verifica que el argumento sea un entero positivo.
def fibonacci(n):
'''calcula el término enésimo de la serie de Fibonacci'''
if type(n) != int or n < 0:
print('Argumento inválido, debe ser un entero positivo.')
return 0
if n < 2:
return n
else:
return fibonacci(n-1) + fibonacci(n-2)
for i in range(10):
print(fibonacci(i))
Como se mencionó anteriormente, Python tiene un límite interno de llamadas recursivas en una función, como manera de proteger la memoria. Se puede conocer este límite mediante la función getrecursionlimit()
que provee el módulo sys
:
import sys
print(sys.getrecursionlimit())
Por lo tanto, si la función recursiva factorial()
definida anteriormente se llama con el argumento 3000
, va a devolver un error, por exceder el límite de profundidad de la recursión.
print(factorial(3000))
El módulo sys
también provee la función setrecursionlimit()
, con la cual se puede modificar ese límite de recursión.
El siguiente bloque primeramente redefine el límite de recursión como 4000, luego de lo cual se puede a la función factorial()
con un argumento de 3000.
sys.setrecursionlimit(4000)
print("El nuevo límite de recursión es %d" % sys.getrecursionlimit())
print(factorial(3000))