Funciones

Existe una distinción teórica que me gusta remarcar acerca de las funciones que, si bien al momento de crear una no es relevante, considero importante hacer. Hay un tipo de función que puede categorizarse junto con los condicionales y los bucles: esto es, sirve para agrupar un conjunto de instrucciones bajo un mismo nombre para evitar repetirlas a lo largo del código. El otro, es el concepto de función tal como se conoce en matemática: una función obtiene un valor o un conjunto de valores de entrada, aplica una serie de operaciones y retorna un valor o un conjunto de valores de salida, un resultado.

Como sea, empecemos por un ejemplo simple. Definamos una función dup(n), muy al estilo matemático, que tome como argumento un número y retorne el doble.

def dup(n):
    return n * 2

Introdujimos dos palabras reservadas nuevas. def es empleada siempre que se quiera crear una nueva función, seguida de su nombre (que, al igual que las variables, se escribe en minúscula y separada por guiones bajos) y los argumentos entre paréntesis y separados por comas. return debe estar sucedida por una expresión que será el valor de retorno de la función. Iremos aclarando estos conceptos.

Por el momento, hagamos una prueba:

# Imprime 10 en pantalla.
print(dup(5))

Ahora consideremos esta otra definición.

def saludar(nombre):
    print("Hola", nombre)

saludar("mundo")

En este caso, nuestra función saludar() no retorna ningún valor. Aunque, en efecto, Python le asigna un valor de retorno por defecto llamado None. Se trata de un tipo de dato como los que hemos estado trabajando, pero un tanto más particular, ya que intenta indicar que una variable está vacía.

>>> a = None
>>> b = 3.14
>>> a is None
True
>>> b is not None
True

La única diferencia, como se observa, es que para realizar comparaciones respecto de None utilizamos las palabras reservadas is e is not en lugar de == y !=.

Argumentos

En Python los argumentos de las funciones tienen un comportamiento particular a diferencia de otros lenguajes. Tomemos como ejemplo la siguiente función.

def sumar(a, b):
    return a + b

De ella decimos que tiene dos argumentos posicionales. Llevan ese nombre por cuanto al invocar sumar(7, 5), 7 se corresponde con a por ser el primer argumento, y 5 se corresponde con b por ser el segundo. Si lo invertimos, de modo que llamemos a sumar(5, 7), entonces ahora a == 5 y b == 7.

Además, al invocar a una función con argumentos posicionales, estos siempren deben tener un valor. Por ejemplo, las siguientes llamadas arrojan un error.

# Falta especificar el argumento b.
sumar(7)
# Sobra un argumento.
sumar(7, 5, 3)

Ahora bien, una función puede tener argumentos con valores por defecto, de modo que, al llamarla, si no hemos especificado un valor concreto por argumento, éste toma automáticamente el que la definición le ha asignado por defecto.

def sumar(a, b=5):
    return a + b

# ¡Perfecto! Imprime 12.
print(sumar(7))
# En este caso, b == 10.
print(sumar(5, 10))

Agreguemos, para avanzar un poco más, un tercer argumento c con un valor por defecto.

def sumar(a, b=5, c=10):
    return a + b + c

# Imprime 22 (7 + 5 + 10).
print(sumar(7))

¿Qué ocurre si quiero indicar un valor para el argumento c mientras que b mantenga su valor por defecto? En ese caso, en lugar de pasar el argumento por posición, lo voy a pasar por nombre.

# Imprime 32 (7 + 5 + 20).
print(sumar(7, c=20))

La posibilidad de pasar argumentos por su nombre en la llamada a una función solo puede darse en aquellos que tengan un valor por defecto. De ahí que a estos se los conozca como argumentos por nombre. En estos casos, la posición de los argumentos es indistinta, de modo que el siguiente código es perfectamente válido.

# El orden es indistinto en los argumentos por nombre.
print(sumar(7, c=20, b=10))

(Nótese que, por convención, cuando especificamos el valor de un argumento por nombre no se ubican espacios alrededor del signo igual).

La única restricción es que los argumentos posicionales deben preceder a los argumentos por nombre, tanto en la definición de una función como en la llamada. El siguiente ejemplo no es un código válido de Python.

# ¡¡¡Inválido!!!
def sumar(a=5, b):
    return a + b

Python provee otras herramientas interesantes para trabajar con los argumentos. Por ejemplo, la posibilidad de crear funciones con argumentos infinitos. Véase, para ahondar en ellas, el siguiente artículo: Argumentos en funciones.

Documentación

Cuando diseñamos una función, lo ideal es documentar su comportamiento utilizando comillas triples inmediatamente luego de su definición.

def dup(n):
    """
    Retorna el doble de `n`.
    """
    return n * 2

Cuando la documentación es más bien corta, como la que hemos mostrado, podemos ubicarla por completo en la misma línea, incluso usando comillas simples.

def dup(n):
    "Retorna el doble de `n`."
    return n * 2

Si bien en la documentación de una función podemos escribir lo que se nos antoje, ¡no es lo mismo que un comentario! En realidad, como habrás observado, es una cadena de Python, que luego el intérprete le asignará como un atributo a la función. Para conocer el docstring (este es el término correcto) de una función podemos emplear la función incorporada help().

# Imprime la documentación en pantalla.
help(dup)

Esta función es especialmente útil en la consola, dado que te permite conocer el funcionamiento y la estructura de argumentos de todas las funciones incorporadas.

>>> help(print)
[...]
>>> help(int)
[...]

Perfecto, ya tenemos todo lo necesario para introducirnos en las clases. Si te parece, ¡sigamos!